我有一段代码片段可以创建3个线程,并希望它们使用整数对象上的synchronized块顺序打印。但显然我有时会遇到僵局。见下文:
public class SequentialExecution implements Runnable {
private Integer i = 1;
public void run() {
String tmp = Thread.currentThread().getName();
if (tmp.equals("first")) {
synchronized(i) {
first();
i = 2;
}
} else if (tmp.equals("second")) {
while (i != 2);
synchronized(i) {
second();
i = 3;
}
} else {
while (i != 3);
synchronized(i) {
third();
}
}
}
public void first() {
System.out.println("first " + i);
}
public void second() {
System.out.println("second " + i);
}
public void third() {
System.out.println("third " + i);
}
public static void main(String[] args) {
//create 3 threads and call first(), second() and third() sequentially
SequentialExecution se = new SequentialExecution();
Thread t1 = new Thread(se, "first");
Thread t2 = new Thread(se, "second");
Thread t3 = new Thread(se, "third");
t3.start();
t2.start();
t1.start();
}
}
我期待(有时候得到)的结果是:
first 1
second 2
third 3
我遇到死锁(和eclipse挂起)的一个示例结果是:
first 1
second 2
任何人都知道为什么这不起作用?我知道我可以使用锁,但我不知道为什么使用synchronized块不起作用。
答案 0 :(得分:4)
将i
声明为volatile
:private volatile Integer i = 1;
。这会警告编译器它不能对i
应用某些优化。每次引用时都必须从内存中读取它,以防另一个线程更改它。
我同意user3582926在this
而不是i
上同步的答案中的建议,因为i
引用的对象随着程序的运行而变化。使程序工作既不必要也不充分,但它确实使它成为一个更好,更清晰的程序。
我已通过将主要方法更改为:
来测试每项更改 public static void main(String[] args) throws InterruptedException {
// create 3 threads and call first(), second() and third() sequentially
for (int i = 0; i < 1000; i++) {
SequentialExecution se = new SequentialExecution();
Thread t1 = new Thread(se, "first");
Thread t2 = new Thread(se, "second");
Thread t3 = new Thread(se, "third");
t3.start();
t2.start();
t1.start();
t1.join();
t2.join();
t3.join();
}
}
没有死锁。存在内存订单问题。
第二个和第三个线程中的while循环位于任何同步块之外。没有什么能告诉编译器和JVM这些线程在循环期间不能将i
或它指向的对象保留在寄存器或缓存中。结果是,根据时间的不同,其中一个线程可能会陷入循环,看着一个不会改变的值。
解决问题的一种方法是标记i
volatile。这会警告编译器它正在用于线程间通信,并且每当i
更改时,每个线程都需要监视内存内容的更改。
为了完全使用同步来解决它,您需要检查在单个特定对象上同步的块内i
引用的整数的值。 i
对此没有好处,因为它会因装箱/拆箱转换而发生变化。它可能是一个简单的int
。
synchronized块不能包装while循环,因为这确实会导致死锁。相反,synchronized块必须在循环内。如果对i
的更新在同一对象上同步,则会强制更新对while循环内的测试可见。
这些注意事项会导致以下基于同步的版本。我正在使用一个执行1000次运行的main方法,如果其中任何一个运行中的任何线程挂起,它本身就会挂起。
public class SequentialExecution implements Runnable {
private int i = 1;
public void run() {
String tmp = Thread.currentThread().getName();
if (tmp.equals("first")) {
synchronized (this) {
first();
i = 2;
}
} else if (tmp.equals("second")) {
while (true) {
synchronized (this) {
if (i == 2) {
break;
}
}
}
synchronized (this) {
second();
i = 3;
}
} else {
while (true) {
synchronized (this) {
if (i == 3) {
break;
}
}
}
synchronized (this) {
third();
}
}
}
public void first() {
System.out.println("first " + i);
}
public void second() {
System.out.println("second " + i);
}
public void third() {
System.out.println("third " + i);
}
public static void main(String[] args) throws InterruptedException {
// create 3 threads and call first(), second() and third() sequentially
for (int i = 0; i < 1000; i++) {
SequentialExecution se = new SequentialExecution();
Thread t1 = new Thread(se, "first");
Thread t2 = new Thread(se, "second");
Thread t3 = new Thread(se, "third");
t3.start();
t2.start();
t1.start();
t1.join();
t2.join();
t3.join();
}
}
}
答案 1 :(得分:2)
我认为您希望使用synchronized(this)
代替synchronized(i)
。