多线程通信

线程通信

  • 虽然无法准确控制线程轮换执行,但我们还是可以通过一些机制来保证线程协调执行。比如让AB交替执行,而不能连续执行A或B。

使用synchronized关键字来保证线程同步

为实现这种功能,可以借助Object类的wait(),notify(),notifyAll(),这三个方法必须由同步监视器来调用(同步代码块是括号里的对象,同步方法是调用者)

使用Lock对象来保证同步

  • 如果程序不使用synchronized关键字来保证线程同步,而是直接用Lock对象来保证同步。那么就没有同步监视器了,也就不能用wait,notify
    notifyall方法了(三个方法都必须由同步监视器调用),因此,java提供了一个Condition类来保持协调,Condition可以让得到Lock对象的线程释放Lock对象,也可以唤醒其它正处于等待的线程。
  • Lock替代了同步代码块或同步方法,Condition替代了同步监视器的功能
  • Condition提供了await()让得到Lock对象的线程释放Lock对象,提供了signal(),signalAll()唤醒其它正处于等待此Lock对象的线程。
  • 通过调用Lock对象的newConditiom()方法即可获得lock实例的condition实例

案例

启动两个线程(不包括main线程)交替打印出0~10的奇偶数

方案一

输出的时候判断一下当前需要输出的数是不是自己要负责打印的值,如果是就输出,不是就直接释放锁。
避开了线程交替获取锁的需求,而是两个线程随机获取锁,在输出的时候判断如果不是自己的需求则选择不输出而已,明显没有答到面试官想考察的考点上。而且效率较低,如果同一个线程一直抢到锁,而另一个线程一直没有拿到,就会导致线程做很多无谓的空转。

package com.areio.partsixteen;

public class Test1 {
    private int count = 0;
    private final Object lock = new Object();

    public void turning() {
        Thread even = new Thread(() -> {
            while (count < 10) {
                System.out.println("偶数在锁的前面");
                synchronized (lock) {
                    System.out.println("偶数拿到锁");
                    // 只处理偶数
                    if ((count & 1) == 0) {
                        System.out.println(Thread.currentThread().getName() + ": " + count++);
                    }
                    System.out.println("偶数放开锁,代码块正常结束");
                }
            }
        }, "偶数");
        Thread odd = new Thread(() -> {
            while (count < 10) {
                System.out.println("ji在锁的前面");
                synchronized (lock) {
                    System.out.println("ji数拿到锁");
                    // 只处理奇数
                    if ((count & 1) == 1) {
                        System.out.println(Thread.currentThread().getName() + ": " + count++);
                    }
                    System.out.println("ji数放开锁,代码块正常结束");
                }
            }
        }, "奇数");
        even.start();
        odd.start();
    }

    public static void main(String[] args) {
        Test1 test1=new Test1();
        test1.turning();
    }
}


ji在锁的前面
偶数在锁的前面
ji数拿到锁
ji数放开锁,代码块正常结束
ji在锁的前面
偶数拿到锁
偶数: 0
偶数放开锁,代码块正常结束
偶数在锁的前面
ji数拿到锁
奇数: 1
ji数放开锁,代码块正常结束
ji在锁的前面
偶数拿到锁
偶数: 2
偶数放开锁,代码块正常结束
偶数在锁的前面
ji数拿到锁
奇数: 3
ji数放开锁,代码块正常结束
ji在锁的前面
偶数拿到锁
偶数: 4
偶数放开锁,代码块正常结束
偶数在锁的前面
ji数拿到锁
奇数: 5
ji数放开锁,代码块正常结束
ji在锁的前面
偶数拿到锁
偶数: 6
偶数放开锁,代码块正常结束
偶数在锁的前面
ji数拿到锁
奇数: 7
ji数放开锁,代码块正常结束
ji在锁的前面
偶数拿到锁
偶数: 8
偶数放开锁,代码块正常结束
偶数在锁的前面
ji数拿到锁
奇数: 9
ji数放开锁,代码块正常结束
偶数拿到锁
偶数: 10
偶数放开锁,代码块正常结束


方案二(交替获取锁)

package com.areio.partsixteen;

public class Test2 {
    private int count = 0;
    private final Object lock = new Object();
    private boolean flag=true;//flag=true代表此时是偶数要取,flag=false代表此时是奇数要取。0是偶数要取,故初始值为true

    public void turning() {
        Thread even = new Thread(() -> {//偶数线程
            while (count <= 10) {
                System.out.println(Thread.currentThread().getName()+"在锁的前面");
                synchronized (lock) {
                    System.out.println(Thread.currentThread().getName()+"取得锁进入代码块");
                    try {
                        // 如果当前是奇数要取,则方法堵塞
                        if (!flag) {
                            System.out.println(Thread.currentThread().getName()+"发现当前是奇数要取,则wait释放锁");
                            lock.wait();
                            System.out.println(Thread.currentThread().getName()+"重新获得锁");
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("偶数: " + count++);
                    flag=false;
                    //唤醒在等待lock实例的其他所有线程(本程序只有2+1个线程)
                    System.out.println(Thread.currentThread().getName()+"唤醒其他线程");
                    lock.notifyAll();

                }
            }
        },"偶数线程--");


        Thread odd = new Thread(() -> {//奇数
            while (count <= 10) {
                System.out.println(Thread.currentThread().getName()+"在锁的前面");
                synchronized (lock) {
                    System.out.println(Thread.currentThread().getName()+"取得锁进入代码块,此时count="+count);
                    try {

                        if (flag) {
                            //wait()抛出check异常
                            System.out.println(Thread.currentThread().getName()+"发现当前是偶数要取,则wait释放锁");
                            //wait没被唤醒之前不能做任何操作,只能堵塞在这
                            lock.wait();
                            System.out.println(Thread.currentThread().getName()+"重新获得锁");
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //加个判断或者在while处修改为count<=9;因为可能读了该线程进入while时读取count=10之后cpu被强占了,导致该线程重新拿到cpu时count已经变成了11,
                    if (count<=10){
                        System.out.println("奇数: " + count++);
                        flag=true;
                        System.out.println(Thread.currentThread().getName()+"唤醒其他线程");
                        lock.notifyAll();
                    }

                }
            }
        },"奇数线程--");
        even.start();
        odd.start();
    }

    public static void main(String[] args) throws InterruptedException {
        Test2 test2=new Test2();
        test2.turning();
    }
}



奇数线程–在锁的前面
偶数线程–在锁的前面
奇数线程–取得锁进入代码块,此时count=0
奇数线程–发现当前是偶数要取,则wait释放锁
偶数线程–取得锁进入代码块
偶数: 0
偶数线程–唤醒其他线程
偶数线程–在锁的前面
奇数线程–重新获得锁
奇数: 1
奇数线程–唤醒其他线程
奇数线程–在锁的前面
偶数线程–取得锁进入代码块
偶数: 2
偶数线程–唤醒其他线程
偶数线程–在锁的前面
奇数线程–取得锁进入代码块,此时count=3
奇数: 3
奇数线程–唤醒其他线程
奇数线程–在锁的前面
偶数线程–取得锁进入代码块
偶数: 4
偶数线程–唤醒其他线程
偶数线程–在锁的前面
奇数线程–取得锁进入代码块,此时count=5
奇数: 5
奇数线程–唤醒其他线程
奇数线程–在锁的前面
偶数线程–取得锁进入代码块
偶数: 6
偶数线程–唤醒其他线程
偶数线程–在锁的前面
奇数线程–取得锁进入代码块,此时count=7
奇数: 7
奇数线程–唤醒其他线程
奇数线程–在锁的前面
偶数线程–取得锁进入代码块
偶数: 8
偶数线程–唤醒其他线程
偶数线程–在锁的前面
奇数线程–取得锁进入代码块,此时count=9
奇数: 9
奇数线程–唤醒其他线程
奇数线程–在锁的前面
偶数线程–取得锁进入代码块
偶数: 10
偶数线程–唤醒其他线程
奇数线程–取得锁进入代码块,此时count=11

Process finished with exit code 0


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值