当前位置:   article > 正文

多线程保证并发顺序执行的细节_多线程中变量怎么保证执行顺序

多线程中变量怎么保证执行顺序

多线程保证并发顺序执行的细节

背景

  一个公共类存储互斥资源(3方法,分别打印1,2,3),基于同一个该类实例,创建三个并发线程t1,t2,t3,t1调用实例的printFirst()方法打印1,t2调用实例的printSecond()方法打印2,t3调用实例的printThird()方法打印3,如何保证三个并发线程顺序执行成功打印出1,2,3呢?

实现

  这个问题思路很简单,在JUC当中属于入门级别了,面试中常问的“3个并发线程顺序打印1-100”也和这个差不多。

  创建变量flag作为互斥变量(标记线程号),当flag=1,则表示当前方法只轮到线程1执行,flag=2,则表示当前方法只轮到线程2执行,flag=3,则表示当前方法只轮到线程3执行,以此保证打印方法的顺序性。例如当线程3提前执行了,发现当前公共类中的flag=1(即线程1还没执行打印),那么就说明来早了,得候着。

  于是乎直接在公共类中创建一把锁,并使用synchronized 关键字,来做到这个效果,如下所示

public class ShareClass {
    private int flag = 1; //标记下一个应该执行的线程号
    private final Object obj = new Object();//创建一把锁

    public void printFirst() throws InterruptedException {
        synchronized(obj){
            while(this.flag != 1){
                obj.wait();//来早了就等着,阻塞在此
            }
            System.out.println("1");
            this.flag = 2;//1已经打印好了,修改下一个应该执行的线程号
            obj.notifyAll();//唤醒其他阻塞线程
        }
    }
    public void printSecond() throws InterruptedException {
        synchronized(obj){
            while(this.flag != 2){
                obj.wait();//来早了就等着,阻塞在此
            }
            System.out.println("2");
            this.flag = 3;//2已经打印好了,修改下一个应该执行的线程号
            obj.notifyAll();//唤醒其他阻塞线程
        }
    }
    public void printThird() throws InterruptedException {
        synchronized(obj){
            while(this.flag != 3){
                obj.wait();//来早了就等着,阻塞在此
            }
            System.out.println("3");
            obj.notifyAll();//唤醒其他阻塞线程
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

  创建main方法测试试试

public class TestDemo{
    public static void main(String[] args) throws InterruptedException {
        final ShareClass s= new ShareClass();
        Thread t1 = new Thread(){
            @Override
            public void run() {
                try {
                    s.printFirst();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        Thread t2 = new Thread(){
            @Override
            public void run() {
                try {
                    s.printSecond();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        Thread t3 = new Thread(){
            @Override
            public void run() {
                try {
                    s.printThird();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        t3.start();
        t2.start();
        t1.start();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38

  测试结果完全没问题。

细节

  为什么下面的代码中,要用 while 而不是 if ? if不行吗?

synchronized(obj){
    while(this.flag != 1){
        obj.wait();//来早了就等着,阻塞在此
    }
    this.flag = 2;//1已经打印好了,修改下一个应该执行的线程号
    obj.notifyAll();//唤醒其他阻塞线程
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

  这就要理解一下obj.wait();这句代码的含义了,这句代码的意思是阻塞当前线程,释放锁,等待被唤醒再重新执行,而被唤醒的线程需要重新竞争锁对象,重新获得锁后则是从wait处继续往下执行

  那么假如把 while 换成 if 的话,我们来考虑以下情况:

  假设线程3先拿到锁,此时发现flag=1,来早了,开始阻塞,释放锁。

  接着线程1抢到了锁,发现此时flag=1,正常打印1,并且将flag修改为2,并唤醒其他阻塞线程,线程执行完毕,关键点来了。

  此时线程3被唤醒,和线程2重新竞争锁,假设此时线程3再次抢到了锁,它会直接往下执行代码,“从wait处继续往下执行”,此时flag=2,原本应由线程2来打印,但是此时跳过判断了,直接打印出了3,接着再次释放锁,线程执行完毕。

  此时当锁落到线程2手中时,3已经被打印了,于是打印出了1,3,2的情况出来了。

  所以判断flag处使用 while 而不是 if 这个细节,你get到了吗?可以体会一下…

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小丑西瓜9/article/detail/197784
推荐阅读
相关标签
  

闽ICP备14008679号