对象锁定 Java

Object lock in Java

我正在尝试理解 Java 中的 "synchronized block"。我已经编写了非常基本的代码来查看如果我锁定并更改 thread_1 中的对象并通过另一种方法从另一个 thread_2 (竞争条件)访问它会发生什么。但是我很难理解这种行为,因为我期望 Thread_1 会先更改值,然后 Thread_2 会访问新值,但结果并不像我预期的那样。

public class Example {

public static void main(String[] args){

  final Counter counter = new Counter();

  Thread  threadA = new Thread(new Runnable() {

     @Override
     public void run() {
         System.out.println("THREAD_1_START");
         counter.add(1);
         System.out.println("THREAD_1_END");
     }
  });
  Thread  threadB = new Thread(new Runnable() {

     @Override
     public void run() {
         System.out.println("THREAD_2_START");
         System.out.println("GET_A_BY_THREAD_2:"+counter.getA(2));
         System.out.println("THREAD_2_END");

     }
 });
  threadA.start();
  threadB.start();
 }
}

public class Counter{
String A = "NONE";

public void add(long value){
    synchronized (A) {
        System.out.println("LOCKED_BY_"+value);

        for(int i = 0; i < 1000000000; i++ ){}

        setA("THREAD_"+value);
        System.out.println("GET_A_BY_THREAD:"+getA(value));
    }
}

public void setA(String A){
        System.out.println("Counter.setA()");
        this.A = A;
        System.out.println("Counter.setA()_end");
}

public String getA(long value){
    System.out.println("Counter.getA()_BY_"+value);
    return this.A;
}
}

输出是:

THREAD_1_START
THREAD_2_START
LOCKED_BY_1
Counter.getA()_BY_2
GET_A_BY_THREAD_2:NONE
THREAD_2_END
Counter.setA()
Counter.setA()_end
Counter.getA()_BY_1
GET_A_BY_THREAD:THREAD_1
THREAD_1_END

Thread_1 锁定 "A" 字符串对象并更改它,但 Thread_2 可以在它更改之前读取值。当"A"处于锁定状态时,thread_2如何访问"A"对象?

Thread_1 locks the "A" string object and changes it

不,Thread_1 锁定 "NONE" 字符串对象,创建一个新的 String 对象,并用对这个新对象的引用覆盖 A 字段。 Thread_2 现在可以获取它的空闲锁,但是在您当前的代码中 getA 方法甚至不会尝试获取它。

您必须对所有 访问使用锁定,而不仅仅是写入。因此 getA 也必须包含同步块。

一般规则是永远不要使用可变实例字段进行锁定。这样做不会为您提供任何有用的保证。因此,您的 A 字段应该是 final 并且您必须删除所有不同意的代码。

你的代码和你对它的解释有很多问题。

  1. 同步块仅同步使用同一监视器的块(在您的情况下为A)。

    由于您的 getter 未同步,因此不受锁定影响。 它不会被阻塞,可能 运行 在第一个线程持有锁期间,甚至当它在第一个线程释放它的锁后执行时,它可能看不到第一个线程所做的更改。

    将同步添加到 getter 以解决此问题。

  2. 您锁定 A,即 "NONE",然后更改 A 以指向不同的字符串。下一个 String 会看到(实际上可能会看到)新的引用,并锁定新的 String,所以你没有两个同步块锁定同一个对象。

    您几乎总是想锁定包含同步块的实例或其 class。这正是您使用同步(静态)方法而不是同步块时得到的结果。

  3. 你的措辞很重要:

    您锁定了对象 A 并更改了 Counter 的实例。

    您不能锁定对象,也不能更改字符串。您只能更改引用以指向新的字符串。