简体   繁体   English

Java多线程在同一块中等待并通知

[英]Java multithreading wait and notify in same block

I am working on a small problem where in I need to print numbers sequentially with two threads in alternate fashion. 我正在研究一个小问题,我需要以交替的方式顺序打印两个线程的数字。 Like Thread 1 prints 1, thread 2 prints 2, thread 1 prints 3 and so on... 像线程1打印1,线程2打印2,线程1打印3,依此类推......

So I have created below piece of code but at some point both the threads go into wait state and nothing prints on the console. 所以我创建了下面的代码,但在某些时候,两个线程都进入等待状态,并且没有任何东西在控制台上打印。

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();
            }
        }
    }
}

When observing the console output, I noticed that at some point when one of the thread notifies, the other thread eventually starts even before the notifying thread gets into the wait state and hence at one point both the threads go into wait state. 在观察控制台输出时,我注意到在其中一个线程通知的某个时刻,另一个线程最终在通知线程进入等待状态之前启动,因此在一个点上两个线程都进入等待状态。 Below is the small portion of console output. 下面是控制台输出的一小部分。

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**

Consider this unlucky sequence of events. 考虑一下这个不幸的事件序列。 Thread1 increments the value, sets the flag to true and notifies any threads in the wait set for the lock. Thread1递增该值,将该标志设置为true并通知等待集中的任何线程以进行锁定。 Now Thread0 was already there in the wait set. 现在Thread0已经在等待集中了。 Then Thread0 awakes and it's flag = false. 然后Thread0醒来,它的标志= false。 Then Thread0 exits the while loop and prints the incremented value and notifies any waiting threads. 然后Thread0退出while循环并打印递增的值并通知任何等待的线程。 Then it moves on to the next iteration in the while loop and invokes wait on the lock object. 然后它继续进行while循环中的下一次迭代,并对锁定对象调用wait。 But Thread1 is not in the waiting state, rather it is switched out of the CPU by the scheduler after completing it's synchronized block to give a chance to Thread0. 但是Thread1没有处于等待状态,而是在完成它的同步块之后由调度程序从CPU切换出来以给予Thread0机会。 Thread1 is in runnable state and is given a chance by the schedular back again since there are no runnable threads left. Thread1处于可运行状态,由于没有剩余的可运行线程,因此调度程序有机会再次返回。 Then Tread1 enters the while loop since flag = true, and invokes wait on the same lock object. 然后Tread1进入while循环,因为flag = true,并在同一个锁对象上调用wait。 Now both the threads are in waiting state and there's no one to wake them up. 现在两个线程都处于等待状态,并且没有人将它们唤醒。 So this is a good example of a live lock in a system. 所以这是系统中实时锁定的一个很好的例子。

This happens because the flag is an instance field hence not shared between threads. 发生这种情况是因为标志是一个实例字段,因此不在线程之间共享。 So each thread has it's own copy of the flag. 所以每个线程都有自己的标志副本。 If you mark it as static variable then both threads share that value, hence the issue is solved. 如果将其标记为静态变量,则两个线程共享该值,因此问题得以解决。 Here's how the flag declaration should look. 这是标志声明的外观。

static boolean flag = false;

How does that fix the issue? 这是如何解决这个问题的? Well, consider the same sequence of events. 好吧,考虑相同的事件序列。 And now Thread1 sets the flag value to true before it calls notify on the lock object. 现在,Thread1在锁定对象上调用notify之前将标志值设置为true。 The Thread0 is already in waiting state. Thread0已处于等待状态。 The schedular switches Thread1 off the CPU and gives a chance to Thread0. 调度程序将Thread1从CPU切换下来,并为Thread0提供机会。 It starts running and since the flag = true, it enters the while loop sets the flag to false and invokes wait on the lock object. 它开始运行,因为flag = true,它进入while循环将标志设置为false并调用锁定对象的wait。 Then Thread0 goes into waiting state and schedular gives a chance to Thread1. 然后Thread0进入等待状态,并且调度给了Thread1机会。 Thread1 resumes it's execution and flag = false, hence it exits the while loop, printing the incremented value and notifies the waiting Thread. Thread1恢复执行并且flag = false,因此它退出while循环,打印递增的值并通知等待的Thread。 So there's no live lock now. 所以现在没有活锁。

However I don't see any point of using both synchronized and non-blocking Atomic variables. 但是,我没有看到使用synchronized和非阻塞原子变量的任何意义。 You should NOT use both of them together. 你不应该同时使用它们。 A more better, performant implementation is given below. 下面给出了更好的,高性能的实现。

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();
    }
}

Your flag variable is not shared between the threads, but the logic around that flag is odd anyway. 您的flag变量不在线程之间共享,但该标志周围的逻辑仍然是奇数。 Note that you don't need to use an AtomicInteger when you are using synchronized . 请注意,在使用synchronized时不需要使用AtomicInteger

When using synchronized properly, an ordinary int variable is already sufficient to implement the entire logic: 正确使用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);
                }
            }
        }
    }
}

Of course, creating multiple threads to perform a sequential operation is defeating the actual purpose of concurrent programming. 当然,创建多个线程来执行顺序操作会破坏并发编程的实际目的。

In the code, even if integer is Atomic and shared between Threads, the flag itself it is not. 在代码中,即使整数是Atomic并在Threads之间共享,标志本身也不是。

class Sequence extends Thread{

    private AtomicInteger integer; //shared
    boolean flag=false; //local

    public Sequence(AtomicInteger integer) {
      this.integer=integer;
    }

Which causes a change in one thread not reflecting in another. 这导致一个线程的变化没有反映在另一个线程中。

Proposed solution: 建议的解决方案:

You can solve using Atomic for the flag too and sharing, for example: 您也可以使用Atomic解决标志并进行共享,例如:

import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;

public class StackOverflow {

    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();
    }
}

And the sequence: 顺序:

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();
            }
        }
    }
}

This is part of the output: 这是输出的一部分:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM