不可变对象的同步(在java中)

代码段 – 1

class RequestObject implements Runnable { private static Integer nRequests = 0; @Override public void run() { synchronized (nRequests) { nRequests++; } } } 

代码段 – 2

 public class Racer implements Runnable { public static Boolean won = false; @Override public void run() { synchronized (won) { if (!won) won = true; } } } 

我在第一个代码片段中遇到了竞争条件。 我明白这是因为我在一个不可变对象(Integer类型)上获得了一个锁。

我已经编写了第二个代码片段,它再次不受“布尔”不可变的影响。 但是这样可行(输出运行中不显示竞争条件)。 如果我已正确理解上一个问题的解决方案,则下面是一种可能出错的方法

  1. 线程1获取对won的对象(比如A)的锁定
  2. 线程2现在试图锁定won指向的对象并进入A的等待队列
  3. 线程1进入同步块,validationA是否为假并通过说won = true (A认为它赢得了比赛)创建了一个新对象(比如说B)。
  4. ‘won’现在指向B.线程1释放对象A上的锁(不再指向won
  5. 现在,在对象A的等待队列中的线程2被唤醒并获得对象A的锁定,该对象仍然是false (不可变的)。 它现在进入同步块并假设它也赢了,这是不正确的。

为什么第二个代码段始终正常工作?

  synchronized (won) { if (!won) won = true; } 

在这里你有一个瞬态竞争条件,你没有注意到它,因为它在第一次执行run方法后就消失了。 之后, won变量不断指向Boolean表示true的相同实例,因此适合用作互斥锁。

这并不是说您应该在实际项目中编写此类代码。 应将所有锁定对象分配给final变量,以确保它们永远不会更改。

对象是否不可变与它是否适合作为synchronized语句中的锁定对象无关。 然而,重要的是,进入同一组关键区域的所有线程都使用相同的对象(因此最好使对象引用final ),但是可以修改对象本身而不影响它的“锁定”。 另外,两个(或更多)不同的synchronized语句可以使用不同的引用变量,并且仍然是互斥的,只要不同的引用变量都引用相同的对象即可。

在上面的例子中,关键区域中的代码将一个对象替换为另一个对象,这是一个问题。 锁定在对象上 ,而不是引用 ,因此更改对象是禁止的。

我在第一个代码片段中遇到了竞争条件。 我明白这是因为我在一个不可变对象(Integer类型)上获得了一个锁。

实际上,这根本不是原因。 获取不可变对象的锁定将“正常”工作。 问题是它可能不会做任何有用的事情……

第一个例子打破的真正原因是你正在锁定错误的东西。 执行此操作时 – nRequests++ – 您实际执行的操作等同于此非primefaces序列:

  int temp = nRequests.integerValue(); temp = temp + 1; nRequests = Integer.valueOf(temp); 

换句话说,您正在为static变量nRequests分配不同的对象引用。

问题是,在您的代码段中,每次对变量进行更新时,线程将在不同的对象上进行同步。 那是因为每个线程都会更改对要锁定的对象的引用

为了正确同步,所有线程都需要锁定同一个对象 ; 例如

 class RequestObject implements Runnable { private static Integer nRequests = 0; private static final Object lock = new Object(); @Override public void run() { synchronized (lock) { nRequests++; } } } 

事实上,第二个例子遇到的问题与第一个问题相同。 您在测试中没有注意到的原因是,从won == falsewon == true的转换仅发生一次……因此潜在竞争条件实际发生的概率要小得多。

实际上,您的第二个代码也不是线程安全的。 请使用下面的代码自行检查(你会发现第一个print语句有时会是2,这意味着synchronized块中有两个线程!)。 底线:代码片段 – 1和代码片段 – 2基本相同,因此不是线程安全的…

 import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicInteger; public class Racer implements Runnable { public static AtomicInteger counter = new AtomicInteger(0); public static Boolean won = false; @Override public void run() { synchronized (won) { System.out.println(counter.incrementAndGet()); //should be always 1; otherwise race condition if (!won) { won = true; try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(counter.decrementAndGet()); //should be always 0; otherwise race condition } } public static void main(String[] args) { int numberOfThreads = 20; ExecutorService executor = Executors.newFixedThreadPool(numberOfThreads); for(int i = 0; i < numberOfThreads; i++) { executor.execute(new Racer()); } executor.shutdown(); } }