繁体   English   中英

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循环中的下一次迭代,并对锁定对象调用wait。 但是Thread1没有处于等待状态,而是在完成它的同步块之后由调度程序从CPU切换出来以给予Thread0机会。 Thread1处于可运行状态,由于没有剩余的可运行线程,因此调度程序有机会再次返回。 然后Tread1进入while循环,因为flag = true,并在同一个锁对象上调用wait。 现在两个线程都处于等待状态,并且没有人将它们唤醒。 所以这是系统中实时锁定的一个很好的例子。

发生这种情况是因为标志是一个实例字段,因此不在线程之间共享。 所以每个线程都有自己的标志副本。 如果将其标记为静态变量,则两个线程共享该值,因此问题得以解决。 这是标志声明的外观。

static boolean flag = false;

这是如何解决这个问题的? 好吧,考虑相同的事件序列。 现在,Thread1在锁定对象上调用notify之前将标志值设置为true。 Thread0已处于等待状态。 调度程序将Thread1从CPU切换下来,并为Thread0提供机会。 它开始运行,因为flag = true,它进入while循环将标志设置为false并调用锁定对象的wait。 然后Thread0进入等待状态,并且调度给了Thread1机会。 Thread1恢复执行并且flag = false,因此它退出while循环,打印递增的值并通知等待的Thread。 所以现在没有活锁。

但是,我没有看到使用synchronized和非阻塞原子变量的任何意义。 你不应该同时使用它们。 下面给出了更好的,高性能的实现。

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

您的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);
                }
            }
        }
    }
}

当然,创建多个线程来执行顺序操作会破坏并发编程的实际目的。

在代码中,即使整数是Atomic并在Threads之间共享,标志本身也不是。

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

顺序:

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

暂无
暂无

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

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