[英]Sequential execution of threads using synchronized
我有一小段代码创建了3个线程,并期望它们使用整数对象上的同步块顺序打印。 但是显然我有时会陷入僵局。 见下文:
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
我遇到死锁(并且日食挂起)的一个示例结果是:
first 1
second 2
有人知道为什么这行不通吗? 我知道我可以使用锁,但是我不知道为什么使用同步块不起作用。
声明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
引用的Integer的值。 i
对此没有好处,因为它会由于装箱/拆箱转换而发生变化。 也可能是一个简单的int
。
同步块无法包装while循环,因为这实际上会导致死锁。 相反,同步块必须在循环内部。 如果对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();
}
}
}
我相信您想使用synchronized(this)
而不是synchronized(i)
。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.