代码段 - 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;
}
}
}
我在第一个代码片段中遇到了竞争条件。我understood这是因为我在一个不可变对象(Integer类型)上获得了一个锁。
我已经编写了第二个代码片段,它再次不受“布尔”不可变的影响。但是这样可行(输出运行中不显示竞争条件)。如果我已正确理解previous question的解决方案,则以下是可能出现问题的方法
won
won
指向的对象并进入A的等待队列won = true
(A认为它赢得了比赛)创建了一个新对象(比如说B)。 won
不再指向)false
(不可变的)。它现在进入同步块并假设它也赢了,这是不正确的。为什么第二个代码段始终正常工作?
答案 0 :(得分:7)
synchronized (won)
{
if (!won)
won = true;
}
这里有一个瞬态竞争条件,你没有注意到它,因为它在第一次执行run
方法后消失了。之后,won
变量始终指向代表Boolean
的{{1}}的同一个实例,因此可以正确地用作互斥锁。
这并不是说您应该在实际项目中编写此类代码。应将所有锁定对象分配给true
变量,以确保它们永远不会更改。
答案 1 :(得分:2)
我在第一个代码片段中遇到了竞争条件。我明白这是因为我在一个不可变对象(Integer类型)上获得了一个锁。
实际上,这根本不是原因。获取不可变对象的锁定将“正常”工作。问题是它可能不会做任何有用的事情......
第一个例子打破的真正原因是你正在锁定错误的东西。执行此操作时 - nRequests++
- 您实际执行的操作等同于此非原子序列:
int temp = nRequests.integerValue();
temp = temp + 1;
nRequests = Integer.valueOf(temp);
换句话说,您要为static
变量nRequests
分配不同的对象引用。
问题是,在您的代码段中,每次对变量进行更新时,线程将在不同的对象上进行同步。那是因为每个线程都会将引用更改为要锁定的对象。
为了正确同步,所有线程都需要锁定同一个对象; e.g。
class RequestObject implements Runnable
{
private static Integer nRequests = 0;
private static final Object lock = new Object();
@Override
public void run()
{
synchronized (lock)
{
nRequests++;
}
}
}
事实上,第二个例子遇到的问题与第一个问题相同。您在测试中没有注意到的原因是从won == false
到won == true
的转换只发生一次......所以潜在竞争条件实际发生的可能性要小得多。< / p>
答案 2 :(得分:2)
对象是否不可变与它是否适合作为synchronized
语句中的锁对象无关。然而, 重要的是,进入同一组关键区域的所有线程都使用相同的对象(因此,最好使对象引用{{1但是,对象本身可以修改而不会影响它的“锁定”。此外,两个(或更多)不同的final
语句可以使用不同的引用变量,并且仍然是互斥的,只要不同的引用变量都引用相同的对象。
在上面的例子中,关键区域中的代码将一个对象替换为另一个对象,这是一个问题。锁定在对象上,而不是引用,因此更改对象是禁止的。
答案 3 :(得分:0)
实际上,您的第二个代码也不是线程安全的。请使用下面的代码自行检查(你会发现第一个print语句有时会是2,这意味着synchronized块中有两个线程!)。 底线:代码段 - 1&amp;代码片段 - 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();
}
}