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

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

背景

  一个公共类存储互斥资源(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();//唤醒其他阻塞线程
        }
    }
}

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

  测试结果完全没问题。

细节

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

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

  这就要理解一下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到了吗?可以体会一下…

  • 6
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值