1

我试图理解 Java 中的“同步块”。我已经编写了非常基本的代码来查看如果我锁定和更改 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”对象?

4

2 回答 2

4

Thread_1 锁定“A”字符串对象并更改它

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

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

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

于 2016-09-06T11:44:37.173 回答
1

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

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

    由于您的吸气剂未同步,因此不受锁的影响。它不会被阻塞,可能在第一个线程持有锁期间运行,即使它在第一个线程释放它的锁后执行,它也可能看不到第一个线程所做的更改。

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

  2. 您锁定Aie on "NONE",但随后更改A为指向不同的字符串。随之而来的下一个字符串会看到(实际上可能会看到)新引用,并锁定新字符串,因此您再次没有两个同步块锁定同一个对象。

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

  3. 你的措辞是,而且很重要:

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

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

于 2016-09-06T11:45:32.397 回答