Java 多线程程序(生产者和消费者)挂起..?
Java multithreaded program (producer and consumer) hangs..?
我在 Java 中有一个生产者和消费者示例的简单变体。我认为它应该工作正常但它挂起。我起初以为这可能是某种形式的死锁,但当我查看线程转储时,它似乎卡在了无限循环的中间。无论如何,我不明白为什么该程序的行为如此奇怪,并且想知道可能导致挂起的可能情况。
public class ProducerConsumer {
public static void main(String[] args) {
Buffer buffer = new Buffer(7);
Producer producer = new Producer(buffer);
Consumer consumer = new Consumer(buffer);
producer.start();
consumer.start();
}
}
class Buffer{
private char [] buffer;
private int count = 0, in = 0, out = 0;
private final Object object1 = new Object();
Buffer(int size){
buffer = new char[size];
}
public void put (char c) {
while(count == buffer.length) ;
synchronized(object1) {
System.out.println("Producing" + c + "...");
buffer[in] = c;
in = (in + 1) % buffer.length;
count++;
}
}
public char get() {
while(count == 0) ;
char c = buffer[out];
synchronized(object1) {
out = (out + 1) % buffer.length;
count--;
System.out.println("Consuming" + c + "...");
}
return c;
}
}
class Producer extends Thread {
private Buffer buffer;
Producer(Buffer b){
buffer = b;
}
public void run() {
for(int i = 0; i < 10; i++) {
buffer.put((char) ('A' + i%26 ));
}
}
}
class Consumer extends Thread {
private Buffer buffer;
Consumer(Buffer b){
buffer = b;
}
public void run() {
for(int i = 0; i < 10; i++) {
buffer.get();
}
}
}
这是程序输出及其线程转储。
c:\Temp\tt>java ProducerConsumer
ProducingA...
ConsumingA...
ProducingB...
ConsumingB...
ProducingC...
ConsumingC...
ProducingD...
ConsumingD...
ProducingE...
ConsumingE...
ProducingF...
ConsumingF...
ProducingG...
ConsumingG...
ProducingH...
ConsumingH...
ProducingI...
ProducingJ...
2019-12-08 22:28:51
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.231-b11 mixed mode):
"DestroyJavaVM" #13 prio=5 os_prio=0 tid=0x0000000002761800 nid=0x3990 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Thread-1" #12 prio=5 os_prio=0 tid=0x000000003a12a800 nid=0x36f0 runnable [0x000000003b04f000]
java.lang.Thread.State: RUNNABLE
at Buffer.get(ProducerConsumer.java:36)
at Consumer.run(ProducerConsumer.java:71)
"Service Thread" #10 daemon prio=9 os_prio=0 tid=0x000000003a0ff000 nid=0x30a4 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C1 CompilerThread3" #9 daemon prio=9 os_prio=2 tid=0x000000003a05a000 nid=0x3890 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2 CompilerThread2" #8 daemon prio=9 os_prio=2 tid=0x000000003a057000 nid=0x16a4 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2 CompilerThread1" #7 daemon prio=9 os_prio=2 tid=0x000000003a055000 nid=0x4b4 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2 CompilerThread0" #6 daemon prio=9 os_prio=2 tid=0x000000003a04d800 nid=0x4160 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Attach Listener" #5 daemon prio=5 os_prio=2 tid=0x0000000039ff9000 nid=0x1028 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Signal Dispatcher" #4 daemon prio=9 os_prio=2 tid=0x0000000039ff7800 nid=0x4f80 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Finalizer" #3 daemon prio=8 os_prio=1 tid=0x0000000039fe1800 nid=0x21c in Object.wait() [0x000000003a5be000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x000000066c408ed8> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:144)
- locked <0x000000066c408ed8> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:165)
at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:216)
"Reference Handler" #2 daemon prio=10 os_prio=2 tid=0x0000000039fe0800 nid=0x4588 in Object.wait() [0x000000003a4bf000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x000000066c406c00> (a java.lang.ref.Reference$Lock)
at java.lang.Object.wait(Object.java:502)
at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
- locked <0x000000066c406c00> (a java.lang.ref.Reference$Lock)
at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)
"VM Thread" os_prio=2 tid=0x00000000381e9800 nid=0x5418 runnable
"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x0000000002776800 nid=0x2580 runnable
"GC task thread#1 (ParallelGC)" os_prio=0 tid=0x0000000002778000 nid=0x4c98 runnable
"GC task thread#2 (ParallelGC)" os_prio=0 tid=0x000000000277a000 nid=0x560c runnable
"GC task thread#3 (ParallelGC)" os_prio=0 tid=0x000000000277b800 nid=0x3e78 runnable
"GC task thread#4 (ParallelGC)" os_prio=0 tid=0x000000000277d800 nid=0x1f24 runnable
"GC task thread#5 (ParallelGC)" os_prio=0 tid=0x0000000002780000 nid=0x5074 runnable
"GC task thread#6 (ParallelGC)" os_prio=0 tid=0x0000000002783000 nid=0x3d88 runnable
"GC task thread#7 (ParallelGC)" os_prio=0 tid=0x0000000002784000 nid=0x4d18 runnable
"VM Periodic Task Thread" os_prio=2 tid=0x000000003a11c800 nid=0x3f8c waiting on condition
JNI global references: 5
Heap
PSYoungGen total 304640K, used 31335K [0x000000066c400000, 0x0000000681800000, 0x00000007c0000000)
eden space 261120K, 12% used [0x000000066c400000,0x000000066e299c00,0x000000067c300000)
from space 43520K, 0% used [0x000000067ed80000,0x000000067ed80000,0x0000000681800000)
to space 43520K, 0% used [0x000000067c300000,0x000000067c300000,0x000000067ed80000)
ParOldGen total 696320K, used 0K [0x00000003c4c00000, 0x00000003ef400000, 0x000000066c400000)
object space 696320K, 0% used [0x00000003c4c00000,0x00000003c4c00000,0x00000003ef400000)
Metaspace used 2642K, capacity 4492K, committed 4864K, reserved 1056768K
class space used 283K, capacity 388K, committed 512K, reserved 1048576K
如有任何提示,我们将不胜感激。
当你的consumer Thread先于Producer Thread开始消费时,就会出现上述情况。你可以模拟它,但在两者之间睡一会。
Buffer buffer = new Buffer(7);
Producer producer = new Producer(buffer);
Consumer consumer = new Consumer(buffer);
consumer.start();
Thread.sleep(2000);
producer.start();
现在要修复它,您可以按照大家的建议使用 wait 和 notify。
我更新了缓冲区代码。请检查这是否适合您。
class Buffer{
private char [] buffer;
private int count = 0, in = 0, out = 0;
private final Object object1 = new Object();
Buffer(int size){
buffer = new char[size];
}
public void put (char c) throws InterruptedException {
//while(count == buffer.length) ;
synchronized(object1) {
if(count == buffer.length){
object1.wait();
}
System.out.println("Producing" + c + "...");
buffer[in] = c;
in = (in + 1) % buffer.length;
count++;
object1.notify();
}
}
public char get() throws InterruptedException {
//while(count == 0) ;
while (true){
synchronized(object1) {
if(count == 0){
object1.wait();
}
char c = buffer[out];
out = (out + 1) % buffer.length;
count--;
System.out.println("Consuming" + c + "...");
object1.notify();
return c;
}
}
}
}
正如上面评论中提到的,在 while 循环中检查条件非常危险。解决方案之一是有两个条件 Empty 和 Full,当 Consumer 检查计数是否为 0 时,它会在 Empty 上等待,如果 Producer 检查计数是否接近限制,它会在 Full 上等待。然后分别是Consumer在count小于limit时唤醒Producer,Producer在count不为0时唤醒Consumer。
回答你的问题,死锁是如何发生的:
Consumer 线程获取 count 变量的值并想要进行比较,但在比较之前 Producer 线程设法填充缓冲区并进入 while 循环。然后消费者比较对他来说为 0 的计数并卡在循环中。两个线程都在等待,因为对于 Consumer,计数为 0,对于 Producer,计数是缓冲区的大小。
我在同步时的建议是计算每种可能的情况,首先了解哪些操作可以视为原子操作,这意味着两个不同的线程不能同时执行。
希望我分享的知识对您有所帮助。
P.S。
我不确定你是否将 count as AtomicInteger 算作可以防止死锁,但至少你可以尝试这种方法。但是无论如何,防止在多线程中使用 while 循环检查条件。
我在 Java 中有一个生产者和消费者示例的简单变体。我认为它应该工作正常但它挂起。我起初以为这可能是某种形式的死锁,但当我查看线程转储时,它似乎卡在了无限循环的中间。无论如何,我不明白为什么该程序的行为如此奇怪,并且想知道可能导致挂起的可能情况。
public class ProducerConsumer {
public static void main(String[] args) {
Buffer buffer = new Buffer(7);
Producer producer = new Producer(buffer);
Consumer consumer = new Consumer(buffer);
producer.start();
consumer.start();
}
}
class Buffer{
private char [] buffer;
private int count = 0, in = 0, out = 0;
private final Object object1 = new Object();
Buffer(int size){
buffer = new char[size];
}
public void put (char c) {
while(count == buffer.length) ;
synchronized(object1) {
System.out.println("Producing" + c + "...");
buffer[in] = c;
in = (in + 1) % buffer.length;
count++;
}
}
public char get() {
while(count == 0) ;
char c = buffer[out];
synchronized(object1) {
out = (out + 1) % buffer.length;
count--;
System.out.println("Consuming" + c + "...");
}
return c;
}
}
class Producer extends Thread {
private Buffer buffer;
Producer(Buffer b){
buffer = b;
}
public void run() {
for(int i = 0; i < 10; i++) {
buffer.put((char) ('A' + i%26 ));
}
}
}
class Consumer extends Thread {
private Buffer buffer;
Consumer(Buffer b){
buffer = b;
}
public void run() {
for(int i = 0; i < 10; i++) {
buffer.get();
}
}
}
这是程序输出及其线程转储。
c:\Temp\tt>java ProducerConsumer
ProducingA...
ConsumingA...
ProducingB...
ConsumingB...
ProducingC...
ConsumingC...
ProducingD...
ConsumingD...
ProducingE...
ConsumingE...
ProducingF...
ConsumingF...
ProducingG...
ConsumingG...
ProducingH...
ConsumingH...
ProducingI...
ProducingJ...
2019-12-08 22:28:51
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.231-b11 mixed mode):
"DestroyJavaVM" #13 prio=5 os_prio=0 tid=0x0000000002761800 nid=0x3990 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Thread-1" #12 prio=5 os_prio=0 tid=0x000000003a12a800 nid=0x36f0 runnable [0x000000003b04f000]
java.lang.Thread.State: RUNNABLE
at Buffer.get(ProducerConsumer.java:36)
at Consumer.run(ProducerConsumer.java:71)
"Service Thread" #10 daemon prio=9 os_prio=0 tid=0x000000003a0ff000 nid=0x30a4 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C1 CompilerThread3" #9 daemon prio=9 os_prio=2 tid=0x000000003a05a000 nid=0x3890 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2 CompilerThread2" #8 daemon prio=9 os_prio=2 tid=0x000000003a057000 nid=0x16a4 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2 CompilerThread1" #7 daemon prio=9 os_prio=2 tid=0x000000003a055000 nid=0x4b4 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2 CompilerThread0" #6 daemon prio=9 os_prio=2 tid=0x000000003a04d800 nid=0x4160 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Attach Listener" #5 daemon prio=5 os_prio=2 tid=0x0000000039ff9000 nid=0x1028 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Signal Dispatcher" #4 daemon prio=9 os_prio=2 tid=0x0000000039ff7800 nid=0x4f80 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Finalizer" #3 daemon prio=8 os_prio=1 tid=0x0000000039fe1800 nid=0x21c in Object.wait() [0x000000003a5be000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x000000066c408ed8> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:144)
- locked <0x000000066c408ed8> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:165)
at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:216)
"Reference Handler" #2 daemon prio=10 os_prio=2 tid=0x0000000039fe0800 nid=0x4588 in Object.wait() [0x000000003a4bf000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x000000066c406c00> (a java.lang.ref.Reference$Lock)
at java.lang.Object.wait(Object.java:502)
at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
- locked <0x000000066c406c00> (a java.lang.ref.Reference$Lock)
at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)
"VM Thread" os_prio=2 tid=0x00000000381e9800 nid=0x5418 runnable
"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x0000000002776800 nid=0x2580 runnable
"GC task thread#1 (ParallelGC)" os_prio=0 tid=0x0000000002778000 nid=0x4c98 runnable
"GC task thread#2 (ParallelGC)" os_prio=0 tid=0x000000000277a000 nid=0x560c runnable
"GC task thread#3 (ParallelGC)" os_prio=0 tid=0x000000000277b800 nid=0x3e78 runnable
"GC task thread#4 (ParallelGC)" os_prio=0 tid=0x000000000277d800 nid=0x1f24 runnable
"GC task thread#5 (ParallelGC)" os_prio=0 tid=0x0000000002780000 nid=0x5074 runnable
"GC task thread#6 (ParallelGC)" os_prio=0 tid=0x0000000002783000 nid=0x3d88 runnable
"GC task thread#7 (ParallelGC)" os_prio=0 tid=0x0000000002784000 nid=0x4d18 runnable
"VM Periodic Task Thread" os_prio=2 tid=0x000000003a11c800 nid=0x3f8c waiting on condition
JNI global references: 5
Heap
PSYoungGen total 304640K, used 31335K [0x000000066c400000, 0x0000000681800000, 0x00000007c0000000)
eden space 261120K, 12% used [0x000000066c400000,0x000000066e299c00,0x000000067c300000)
from space 43520K, 0% used [0x000000067ed80000,0x000000067ed80000,0x0000000681800000)
to space 43520K, 0% used [0x000000067c300000,0x000000067c300000,0x000000067ed80000)
ParOldGen total 696320K, used 0K [0x00000003c4c00000, 0x00000003ef400000, 0x000000066c400000)
object space 696320K, 0% used [0x00000003c4c00000,0x00000003c4c00000,0x00000003ef400000)
Metaspace used 2642K, capacity 4492K, committed 4864K, reserved 1056768K
class space used 283K, capacity 388K, committed 512K, reserved 1048576K
如有任何提示,我们将不胜感激。
当你的consumer Thread先于Producer Thread开始消费时,就会出现上述情况。你可以模拟它,但在两者之间睡一会。
Buffer buffer = new Buffer(7);
Producer producer = new Producer(buffer);
Consumer consumer = new Consumer(buffer);
consumer.start();
Thread.sleep(2000);
producer.start();
现在要修复它,您可以按照大家的建议使用 wait 和 notify。
我更新了缓冲区代码。请检查这是否适合您。
class Buffer{
private char [] buffer;
private int count = 0, in = 0, out = 0;
private final Object object1 = new Object();
Buffer(int size){
buffer = new char[size];
}
public void put (char c) throws InterruptedException {
//while(count == buffer.length) ;
synchronized(object1) {
if(count == buffer.length){
object1.wait();
}
System.out.println("Producing" + c + "...");
buffer[in] = c;
in = (in + 1) % buffer.length;
count++;
object1.notify();
}
}
public char get() throws InterruptedException {
//while(count == 0) ;
while (true){
synchronized(object1) {
if(count == 0){
object1.wait();
}
char c = buffer[out];
out = (out + 1) % buffer.length;
count--;
System.out.println("Consuming" + c + "...");
object1.notify();
return c;
}
}
}
}
正如上面评论中提到的,在 while 循环中检查条件非常危险。解决方案之一是有两个条件 Empty 和 Full,当 Consumer 检查计数是否为 0 时,它会在 Empty 上等待,如果 Producer 检查计数是否接近限制,它会在 Full 上等待。然后分别是Consumer在count小于limit时唤醒Producer,Producer在count不为0时唤醒Consumer。
回答你的问题,死锁是如何发生的:
Consumer 线程获取 count 变量的值并想要进行比较,但在比较之前 Producer 线程设法填充缓冲区并进入 while 循环。然后消费者比较对他来说为 0 的计数并卡在循环中。两个线程都在等待,因为对于 Consumer,计数为 0,对于 Producer,计数是缓冲区的大小。 我在同步时的建议是计算每种可能的情况,首先了解哪些操作可以视为原子操作,这意味着两个不同的线程不能同时执行。
希望我分享的知识对您有所帮助。
P.S。 我不确定你是否将 count as AtomicInteger 算作可以防止死锁,但至少你可以尝试这种方法。但是无论如何,防止在多线程中使用 while 循环检查条件。