Java 多线程在同一块中等待和通知
Java multithreading wait and notify in same block
我正在解决一个小问题,我需要用两个线程以交替方式顺序打印数字。就像线程 1 打印 1,线程 2 打印 2,线程 1 打印 3 等等...
所以我创建了下面的一段代码,但在某些时候两个线程都进入等待状态并且控制台上没有任何打印。
import java.util.concurrent.atomic.AtomicInteger;
public class MultiPrintSequence {
public static void main(String[] args) {
AtomicInteger integer=new AtomicInteger(0);
Sequence sequence1=new Sequence(integer);
Sequence sequence2=new Sequence(integer);
sequence1.start();
sequence2.start();
}
}
class Sequence extends Thread{
private AtomicInteger integer;
boolean flag=false;
public Sequence(AtomicInteger integer) {
this.integer=integer;
}
@Override
public void run() {
while(true) {
synchronized (integer) {
while (flag) {
flag=false;
try {
System.out.println(Thread.currentThread().getName()+" waiting");
integer.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+" "+integer.incrementAndGet());
flag = true;
System.out.println(Thread.currentThread().getName()+" notifying");
integer.notify();
}
}
}
}
观察控制台输出时,我注意到在某个时刻,当其中一个线程发出通知时,另一个线程最终甚至在通知线程进入等待状态之前就启动了,因此在某一时刻两个线程都进入了等待状态状态。下面是控制台输出的一小部分。
Thread-1 510
Thread-1 notifying
Thread-1 waiting
Thread-0 511
Thread-0 notifying
Thread-0 waiting
Thread-1 512
Thread-1 notifying
Thread-1 waiting
**Thread-0 513
Thread-0 notifying
Thread-1 514
Thread-1 notifying
Thread-1 waiting
Thread-0 waiting**
想想这一系列不幸的事件。 Thread1 递增该值,将标志设置为 true 并通知等待集中的所有线程获取锁。现在 Thread0 已经在等待集中了。然后 Thread0 醒来,它的标志 = false。然后 Thread0 退出 while 循环并打印增加的值并通知所有等待的线程。然后它继续进行 while 循环中的下一次迭代并调用锁对象上的等待。但是 Thread1 并未处于等待状态,而是在完成其同步块后被调度程序切换出 CPU 以给 Thread0 机会。 Thread1 处于可运行状态,并且由于没有剩余可运行线程,调度程序再次给它机会。然后 Tread1 进入 while 循环,因为 flag = true,并在同一个锁对象上调用等待。现在两个线程都处于等待状态,没有人可以唤醒它们。所以这是系统中活锁的一个很好的例子。
发生这种情况是因为标志是一个实例字段,因此不会在线程之间共享。所以每个线程都有自己的标志副本。如果将其标记为静态变量,则两个线程共享该值,因此问题得到解决。标志声明应该是这样的。
static boolean flag = false;
如何解决这个问题?好吧,考虑相同的事件顺序。现在 Thread1 在调用锁定对象上的通知之前将标志值设置为 true。 Thread0 已经处于等待状态。调度程序将 Thread1 关闭 CPU 并为 Thread0 提供机会。它开始 运行 并且由于标志 = true,它进入 while 循环将标志设置为 false 并调用锁对象上的等待。然后 Thread0 进入等待状态,调度给 Thread1 一个机会。 Thread1 恢复执行并且 flag = false,因此它退出 while 循环,打印增加的值并通知等待线程。所以现在没有活锁。
但是我看不出同时使用 synchronized
和 non-blocking 原子变量有什么意义。你不应该同时使用它们。下面给出了一个更好、更高效的实现。
public class Sequence extends Thread {
private static final Object lock = new Object();
private static int integer = 0;
static boolean flag = false;
@Override
public void run() {
while (true) {
synchronized (lock) {
while (flag) {
flag = false;
try {
System.out.println(Thread.currentThread().getName() + " waiting");
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + " " + ++integer);
flag = true;
System.out.println(Thread.currentThread().getName() + " notifying");
lock.notify();
}
}
}
public static void main(String[] args) {
Sequence sequence1=new Sequence();
Sequence sequence2=new Sequence();
sequence1.start();
sequence2.start();
}
}
在代码中,即使整数是原子的并且在线程之间共享,标志本身也不是。
class Sequence extends Thread{
private AtomicInteger integer; //shared
boolean flag=false; //local
public Sequence(AtomicInteger integer) {
this.integer=integer;
}
这会导致一个线程中的更改不会反映在另一个线程中。
建议的解决方案:
你也可以解决使用 Atomic 作为标志并共享,例如:
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
public class Whosebug {
public static void main(String[] args) {
AtomicInteger integer=new AtomicInteger(0);
AtomicBoolean flag=new AtomicBoolean(true);
Sequence sequence1=new Sequence(integer, flag);
Sequence sequence2=new Sequence(integer, flag);
sequence1.start();
sequence2.start();
}
}
和序列:
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
class Sequence extends Thread{
private final AtomicInteger integer;
private AtomicBoolean flag;
public Sequence(AtomicInteger integer, AtomicBoolean flag) {
this.integer=integer;
this.flag=flag;
}
@Override
public void run() {
while(true) {
synchronized (integer) {
while (flag.get()) {
flag.set(false);
try {
System.out.println(Thread.currentThread().getName()+" waiting");
integer.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+" "+integer.incrementAndGet());
flag.set(true);
System.out.println(Thread.currentThread().getName()+" notifying");
integer.notify();
}
}
}
}
这是输出的一部分:
Thread-1 8566
Thread-1 notifying
Thread-1 waiting
Thread-0 8567
Thread-0 notifying
Thread-0 waiting
Thread-1 8568
Thread-1 notifying
Thread-1 waiting
Thread-0 8569
Thread-0 notifying
Thread-0 waiting
您的 flag
变量未在线程之间共享,但无论如何围绕该标志的逻辑很奇怪。请注意,当您使用 synchronized
.
时,您不需要使用 AtomicInteger
正确使用synchronized
时,一个普通的int
变量就足以实现整个逻辑:
public class MultiPrintSequence {
public static void main(String[] args) {
final Sequence sequence = new Sequence();
new Thread(sequence).start();
new Thread(sequence).start();
}
}
class Sequence implements Runnable {
private final Object lock = new Object();
private int sharedNumber;
@Override
public void run() {
synchronized(lock) {
for(;;) {
int myNum = ++sharedNumber;
lock.notify();
System.out.println(Thread.currentThread()+": "+myNum);
while(sharedNumber == myNum) try {
lock.wait();
} catch (InterruptedException ex) {
throw new AssertionError(ex);
}
}
}
}
}
当然,创建多个线程来执行顺序操作违背了并发编程的实际目的。
我正在解决一个小问题,我需要用两个线程以交替方式顺序打印数字。就像线程 1 打印 1,线程 2 打印 2,线程 1 打印 3 等等...
所以我创建了下面的一段代码,但在某些时候两个线程都进入等待状态并且控制台上没有任何打印。
import java.util.concurrent.atomic.AtomicInteger;
public class MultiPrintSequence {
public static void main(String[] args) {
AtomicInteger integer=new AtomicInteger(0);
Sequence sequence1=new Sequence(integer);
Sequence sequence2=new Sequence(integer);
sequence1.start();
sequence2.start();
}
}
class Sequence extends Thread{
private AtomicInteger integer;
boolean flag=false;
public Sequence(AtomicInteger integer) {
this.integer=integer;
}
@Override
public void run() {
while(true) {
synchronized (integer) {
while (flag) {
flag=false;
try {
System.out.println(Thread.currentThread().getName()+" waiting");
integer.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+" "+integer.incrementAndGet());
flag = true;
System.out.println(Thread.currentThread().getName()+" notifying");
integer.notify();
}
}
}
}
观察控制台输出时,我注意到在某个时刻,当其中一个线程发出通知时,另一个线程最终甚至在通知线程进入等待状态之前就启动了,因此在某一时刻两个线程都进入了等待状态状态。下面是控制台输出的一小部分。
Thread-1 510
Thread-1 notifying
Thread-1 waiting
Thread-0 511
Thread-0 notifying
Thread-0 waiting
Thread-1 512
Thread-1 notifying
Thread-1 waiting
**Thread-0 513
Thread-0 notifying
Thread-1 514
Thread-1 notifying
Thread-1 waiting
Thread-0 waiting**
想想这一系列不幸的事件。 Thread1 递增该值,将标志设置为 true 并通知等待集中的所有线程获取锁。现在 Thread0 已经在等待集中了。然后 Thread0 醒来,它的标志 = false。然后 Thread0 退出 while 循环并打印增加的值并通知所有等待的线程。然后它继续进行 while 循环中的下一次迭代并调用锁对象上的等待。但是 Thread1 并未处于等待状态,而是在完成其同步块后被调度程序切换出 CPU 以给 Thread0 机会。 Thread1 处于可运行状态,并且由于没有剩余可运行线程,调度程序再次给它机会。然后 Tread1 进入 while 循环,因为 flag = true,并在同一个锁对象上调用等待。现在两个线程都处于等待状态,没有人可以唤醒它们。所以这是系统中活锁的一个很好的例子。
发生这种情况是因为标志是一个实例字段,因此不会在线程之间共享。所以每个线程都有自己的标志副本。如果将其标记为静态变量,则两个线程共享该值,因此问题得到解决。标志声明应该是这样的。
static boolean flag = false;
如何解决这个问题?好吧,考虑相同的事件顺序。现在 Thread1 在调用锁定对象上的通知之前将标志值设置为 true。 Thread0 已经处于等待状态。调度程序将 Thread1 关闭 CPU 并为 Thread0 提供机会。它开始 运行 并且由于标志 = true,它进入 while 循环将标志设置为 false 并调用锁对象上的等待。然后 Thread0 进入等待状态,调度给 Thread1 一个机会。 Thread1 恢复执行并且 flag = false,因此它退出 while 循环,打印增加的值并通知等待线程。所以现在没有活锁。
但是我看不出同时使用 synchronized
和 non-blocking 原子变量有什么意义。你不应该同时使用它们。下面给出了一个更好、更高效的实现。
public class Sequence extends Thread {
private static final Object lock = new Object();
private static int integer = 0;
static boolean flag = false;
@Override
public void run() {
while (true) {
synchronized (lock) {
while (flag) {
flag = false;
try {
System.out.println(Thread.currentThread().getName() + " waiting");
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + " " + ++integer);
flag = true;
System.out.println(Thread.currentThread().getName() + " notifying");
lock.notify();
}
}
}
public static void main(String[] args) {
Sequence sequence1=new Sequence();
Sequence sequence2=new Sequence();
sequence1.start();
sequence2.start();
}
}
在代码中,即使整数是原子的并且在线程之间共享,标志本身也不是。
class Sequence extends Thread{
private AtomicInteger integer; //shared
boolean flag=false; //local
public Sequence(AtomicInteger integer) {
this.integer=integer;
}
这会导致一个线程中的更改不会反映在另一个线程中。
建议的解决方案:
你也可以解决使用 Atomic 作为标志并共享,例如:
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
public class Whosebug {
public static void main(String[] args) {
AtomicInteger integer=new AtomicInteger(0);
AtomicBoolean flag=new AtomicBoolean(true);
Sequence sequence1=new Sequence(integer, flag);
Sequence sequence2=new Sequence(integer, flag);
sequence1.start();
sequence2.start();
}
}
和序列:
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
class Sequence extends Thread{
private final AtomicInteger integer;
private AtomicBoolean flag;
public Sequence(AtomicInteger integer, AtomicBoolean flag) {
this.integer=integer;
this.flag=flag;
}
@Override
public void run() {
while(true) {
synchronized (integer) {
while (flag.get()) {
flag.set(false);
try {
System.out.println(Thread.currentThread().getName()+" waiting");
integer.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+" "+integer.incrementAndGet());
flag.set(true);
System.out.println(Thread.currentThread().getName()+" notifying");
integer.notify();
}
}
}
}
这是输出的一部分:
Thread-1 8566
Thread-1 notifying
Thread-1 waiting
Thread-0 8567
Thread-0 notifying
Thread-0 waiting
Thread-1 8568
Thread-1 notifying
Thread-1 waiting
Thread-0 8569
Thread-0 notifying
Thread-0 waiting
您的 flag
变量未在线程之间共享,但无论如何围绕该标志的逻辑很奇怪。请注意,当您使用 synchronized
.
AtomicInteger
正确使用synchronized
时,一个普通的int
变量就足以实现整个逻辑:
public class MultiPrintSequence {
public static void main(String[] args) {
final Sequence sequence = new Sequence();
new Thread(sequence).start();
new Thread(sequence).start();
}
}
class Sequence implements Runnable {
private final Object lock = new Object();
private int sharedNumber;
@Override
public void run() {
synchronized(lock) {
for(;;) {
int myNum = ++sharedNumber;
lock.notify();
System.out.println(Thread.currentThread()+": "+myNum);
while(sharedNumber == myNum) try {
lock.wait();
} catch (InterruptedException ex) {
throw new AssertionError(ex);
}
}
}
}
}
当然,创建多个线程来执行顺序操作违背了并发编程的实际目的。