我有两条线要卖票。
public class MyThread {
public static void main(String[] args) {
Ticket ticket = new Ticket();
Thread thread1 = new Thread(()->{
for (int i = 0; i < 30; i++) {
ticket.sell();
} }, "A");
thread1.start();
Thread thread2 = new Thread(()->{
for (int i = 0; i < 30; i++) {
ticket.sell();
} }, "B");
thread2.start();
}
}
class Ticket {
private Integer num = 20 ;
private Object obj = new Object();
public void sell() {
// why shouldn't I use "num" as a monitor object ?
// I thought "num" is unique among two threads.
synchronized ( num ) {
if (this.num >= 0) {
System.out.println(Thread.currentThread().getName() + " sells " + this.num + "th ticket");
this.num--;
}
}
}
}
如果我使用num
作为监视器对象,输出将是错误的。
但是,如果我使用obj
作为监视器对象,输出将是正确的。
使用num
和使用obj
有什么区别?
===============================================
如果我使用(Object)num
作为监视器对象,为什么它仍然不能工作呢?
class Ticket {
private int num = 20 ;
private Object obj = new Object();
public void sell() {
// Can I use (Object)num as a monitor object ?
synchronized ( (Object)num ) {
if (this.num >= 0) {
System.out.println(Thread.currentThread().getName() + " sells " + this.num + "th ticket");
this.num--;
}
}
}
}
发布于 2021-07-30 10:10:37
这里的基本问题是。
关于Java内存模型--即线程在执行Java程序时所看到的值--最重要的是 relationship。
在同步块的特定情况下,在退出同步块之前在一个线程中执行的操作发生在另一个线程中的同步块内操作之前-因此,如果第一个线程在该同步块内增加一个变量,则第二个线程将看到该更新的值。
这超出了众所周知的事实,即同步块一次只能由一个线程输入:一次只能输入一个线程,然后您就可以看到前面的线程做了什么。
// Thread 1 // Thread 2
synchronized (monitor) {
num = 1
} // Exiting monitor
// *happens before*
// entering monitor
synchronized (monitor) {
int n = num; // Guaranteed to see n = 1 (provided no other thread has entered a block synchronized on monitor and changed it first).
}
这个保证有一个非常重要的警告:只有当同步块的两次执行使用--相同的监视器--时,它才有效。这不是同一个变量,而是堆上相同的实际具体对象(变量没有监视器,它们只是指向堆中值的指针)。
因此,如果您在同步块内重新分配监视器:
synchronized (num) {
if (num > 0) {
num--; // This is the same as `num = Integer.valueOf(num.intValue() - 1);`
}
}
然后是销毁了发生的事件--在保证之前,因为下一个到达同步块的线程是进入另一个对象(*)的监视器。
一旦你这么做了,你的程序的行为就会被定义不清:如果你运气好,它就会以明显的方式失败;如果你非常不走运,它似乎会起作用,然后在稍后的某个日期神秘地开始失败。
你的密码被破坏了。
这也不是Integer
所特有的:这段代码也会有同样的问题。
// Assume `Object someObject = new Object();` is defined as a field.
synchronized (someObject) {
someObject = new Object();
}
(*)实际上,在新对象的关系之前,仍然会发生这样的情况:它不是针对这个同步块中的东西,而是针对在其他一些使用对象作为监视器的同步块中发生的事情。本质上,这是不可能的,这意味着什么,所以你可以认为它是“坏的”。
正确的方法是在不能重新分配的字段上同步(而不仅仅是不)重新分配。您可以简单地在this
上同步(不能重新分配):
synchronized (this) {
if (num > 0) {
num--; // This is the same as `num = Integer.valueOf(num.intValue() - 1);`
}
}
现在,在块内重新分配num
并不重要,因为不再对其进行同步。你得到了发生之前的保证,因为你总是在同一件事上同步。
但是,请注意,您必须始终从同步块中访问num
--例如,如果您有一个getter来获取剩余的票数,则必须在this
上进行同步,才能得到发生,然后才能保证sell()
方法中更改的值在该getter中是可见的。
这是可行的,但它可能不是完全可取的:任何访问您的Ticket
实例的引用的人也可以在它上同步。这意味着它们可能会锁住您的代码。
相反,引入纯用于锁定的私有字段是一种常见的做法:这是obj
字段提供给您的。对代码的唯一修改应该是使其为final
(并给它一个比obj
更好的名称):
private final Object obj = new Object();
这不能在类之外访问,因此恶意客户端不能直接为您造成死锁。
同样,这不能在同步块(或其他任何地方)内重新分配,因此在通过重新分配保证之前,没有破坏发生的风险。
发布于 2021-07-30 06:00:59
Integer
是一个装箱的值。它包含一个基本的int
,编译器处理int
的自动装箱/自动装箱。正因为如此,this.num--
语句实际上是:
num=Integer.valueOf(num.intValue()-1)
也就是说,一旦执行更新,包含锁的num
实例就会丢失。
https://stackoverflow.com/questions/68586090
复制相似问题