我有一段代码创建了 3 个线程,并期望它们使用整数对象上的同步块(synchronized block)顺序打印。但显然我有时会陷入僵局。见下文:
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 block)不起作用。
最佳答案
声明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 循环在任何同步块(synchronized block)之外。没有任何信息告诉编译器和 JVM 这些线程不能在循环期间将 i
或其指向的对象保存在寄存器或缓存中。结果是,根据时间安排,其中一个线程可能会卡住循环查看不会更改的值。
解决该问题的一种方法是将i
标记为volatile。这会警告编译器它正在用于线程间通信,并且每个线程都需要在 i
发生变化时监视内存内容的变化。
为了完全使用同步来解决它,您需要检查在单个特定对象上同步的 block 中 i
引用的 Integer 的值。 i
对此没有好处,因为它会因装箱/拆箱转换而发生变化。它也可能是一个简单的 int
。
同步块(synchronized block)不能包裹 while 循环,因为那样确实会导致死锁。相反,同步块(synchronized block)必须在循环内。如果对 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();
}
}
}
关于java - 使用 synchronized 顺序执行线程,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25073997/