A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

Shala

初级黑马

  • 黑马币:31

  • 帖子:8

  • 精华:0

© Shala 初级黑马   /  2018-8-2 23:02  /  858 人查看  /  0 人回复  /   0 人收藏 转载请遵从CC协议 禁止商业使用本文

Condition是JDK1.5之后出现的接口,用来解决线程之间的通信问题。
通过接口Lock的newCondition方法来创建一个Condition的实例对象
例如:
​        Lock lock = new ReentrantLock();
​        Condition con = lock.newCondition();
Condition通过调用await()方法,和single()/singleAll()方法来阻塞和唤醒线程。与Object中的wait()和notify()/notifyAll()类似。都是用来解决线程之间的通信问题。
await()必须用在lock的包围中
首先介绍一下Lock的基本用法。//在介绍Condition之前,先介绍一下Lock的基本用法
//这里有一个资源类,有一个篮子,打算创建俩个线程,一个线程向篮子里放鸡蛋,一个线程从篮子里拿鸡蛋
//输出要求:
//放鸡蛋
//拿鸡蛋
//放鸡蛋
//拿鸡蛋...往复
class Plant{
    //创建一个篮子
    private ArrayList<String> plant = new ArrayList<>();
    //创建锁
    Lock lock = new ReentrantLock();


    //放鸡蛋
    public void put(){
        while(true){
            lock.lock();
            try{
                System.out.println(Thread.currentThread().getName()+"正在放鸡蛋...");
                plant.add("egg");
                Thread.sleep(1000);
                System.out.println(Thread.currentThread().getName()+"放入了一个鸡蛋");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally{
                lock.unlock();
            }
        }

    }
    //拿鸡蛋
    public void get(){
        while(true){
            lock.lock();
            try{
                if(plant.size()!=0){
                    System.out.println(Thread.currentThread().getName()+"正在拿鸡蛋...");
                    //等待1秒钟
                    Thread.sleep(1000);
                    plant.remove("egg");
                    System.out.println(Thread.currentThread().getName()+"拿走了一个鸡蛋");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally{
                lock.unlock();
            }
        }

    }
}//创建测试类
public class Test{
   public static void main(String[] args){
        Plant p = new Plant();
        new Thread(new Runnable() {
            @Override
            public void run() {
                p.put();
            }
        },"放鸡蛋线程").start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                p.get();
            }
        },"拿鸡蛋线程").start();
    }
}//运行结果
放鸡蛋线程正在放鸡蛋...
放鸡蛋线程放入了一个鸡蛋
拿鸡蛋线程正在拿鸡蛋...
拿鸡蛋线程拿走了一个鸡蛋
放鸡蛋线程正在放鸡蛋...
放鸡蛋线程放入了一个鸡蛋
放鸡蛋线程正在放鸡蛋...
放鸡蛋线程放入了一个鸡蛋
放鸡蛋线程正在放鸡蛋...
​        从上面的运行结果可以看出,lock同步锁做到了和synchronized同步代码块一样的功能,可以让俩个线程对同一个资源操作时达到同步的效果。但是输出结果并不是我们想要的,输出结果出现连续拿鸡蛋好几次。要想使拿鸡蛋和放鸡蛋的线程交替运行,需要线程之间的通信。
线程直线同步通信
​       
class Plant{
    //创建一个篮子
    private ArrayList<String> plant = new ArrayList<>();
    //创建锁
    Lock lock = new ReentrantLock();
    //创建Condition对象
    Condition con = lock.newCondition();
    //用来判断当前是否该put线程执行
    boolean isPut = true;
    //放鸡蛋
    public void put(){
        while(true){
            lock.lock();
            try{
                while(!isPut){
                        con.await();//当不是put线程执行时,那么put线程就进入阻塞等待
                }
                System.out.println(Thread.currentThread().getName()+"正在放鸡蛋...");
                plant.add("egg");
                Thread.sleep(1000);
                System.out.println(Thread.currentThread().getName()+"放入了一个鸡蛋");
                //put线程执行完毕,修改isPut的值为false
                isPut = false;
                //唤醒get的线程
                con.signal();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally{
                lock.unlock();
            }
        }

    }
    //拿鸡蛋---原理同放鸡蛋
    public void get(){
        while(true){
            lock.lock();
            try{
                while(isPut){
                        con.await();
                }
                if(plant.size()!=0){
                    System.out.println(Thread.currentThread().getName()+"正在拿鸡蛋...");
                    //等待1秒钟
                    Thread.sleep(1000);
                    plant.remove("egg");
                    System.out.println(Thread.currentThread().getName()+"拿走了一个鸡蛋");
                    isPut = true;
                    con.signal();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally{
                lock.unlock();
            }
        }

    }运行结果
放鸡蛋线程正在放鸡蛋...
放鸡蛋线程放入了一个鸡蛋
拿鸡蛋线程正在拿鸡蛋...
拿鸡蛋线程拿走了一个鸡蛋
放鸡蛋线程正在放鸡蛋...
放鸡蛋线程放入了一个鸡蛋
拿鸡蛋线程正在拿鸡蛋...
拿鸡蛋线程拿走了一个鸡蛋
放鸡蛋线程正在放鸡蛋...
放鸡蛋线程放入了一个鸡蛋
拿鸡蛋线程正在拿鸡蛋...
拿鸡蛋线程拿走了一个鸡蛋
放鸡蛋线程正在放鸡蛋...
放鸡蛋线程放入了一个鸡蛋
​        上述做到了俩个线程,一个拿鸡蛋线程,一个放鸡蛋线程的同步通信工作(使用synchronized和wait也能实现)。假设我现在有6个线程,3个放鸡蛋线程,3个拿鸡蛋线程,也要实现上述的同步通信功能又该如何实现呢?
//没有修改上述代码,修改了测试类代码,直接进行测试
public class Demo2 {
    public static void main(String[] args){
        Plant p = new Plant();
        for (int i=0;i<3;i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    p.put();
                }
            },"放鸡蛋线程").start();
        }
        for (int i=0;i<3;i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    p.get();
                }
            },"拿鸡蛋线程").start();
        }
    }
}
但是上述代码在测试中会出现锁死现象。
​        因为6个线程的条件锁对象(即 Condition对象)都是相同的,所以当isPut为true是,3条拿鸡蛋的线程全部进入等待状态,3条放鸡蛋线程开始抢cpu资源当有一条线程抢到cpu资源后,其他俩条put线程释放锁。执行完程序,put线程将isPut修改为false,唤醒一条get线程,于是put线程全部进入等待状态,当前6条线程中只剩下1条get线程处于运行状态,接着这条get线程将isPut修改为true,然后唤醒一条等待条件锁对象的线程,出现锁死的现象就在这里,因为被唤醒的线程时5条线程中随机的,这里如果唤醒了一条get线程,由于isPut的值为true,那么这条线程也进入了等待状态,那么所有的线程都进入了等待状态,程序锁死。
​        解决方法如下
class Plant{
    //创建一个篮子
    private ArrayList<String> plant = new ArrayList<>();
    //创建锁
    Lock lock = new ReentrantLock();
    Condition put = lock.newCondition();//创建放线程的条件锁对象
    Condition get = lock.newCondition();//创建拿线程的条件锁对象
    boolean isPut = true;
    //放鸡蛋
    public void put(){
        while(true){
            lock.lock();
            try{
                while(!isPut){
                        put.await();
                }
                System.out.println(Thread.currentThread().getName()+"正在放鸡蛋...");
                plant.add("egg");
                Thread.sleep(1000);
                System.out.println(Thread.currentThread().getName()+"放入了一个鸡蛋");
                isPut = false;
                get.signal();//唤醒一条拿线程
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally{
                lock.unlock();
            }
        }

    }
    //拿鸡蛋
    public void get(){
        while(true){
            lock.lock();
            try{
                while(isPut){
                        get.await();
                }
                if(plant.size()!=0){
                    System.out.println(Thread.currentThread().getName()+"正在拿鸡蛋...");
                    //等待1秒钟
                    Thread.sleep(1000);
                    plant.remove("egg");
                    System.out.println(Thread.currentThread().getName()+"拿走了一个鸡蛋");
                    isPut = true;
                    put.signal();//唤醒一条放线程
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally{
                lock.unlock();
            }
        }

    }
}运行结果
放鸡蛋线程正在放鸡蛋...
放鸡蛋线程放入了一个鸡蛋
拿鸡蛋线程正在拿鸡蛋...
拿鸡蛋线程拿走了一个鸡蛋
放鸡蛋线程正在放鸡蛋...
放鸡蛋线程放入了一个鸡蛋
拿鸡蛋线程正在拿鸡蛋...
拿鸡蛋线程拿走了一个鸡蛋
放鸡蛋线程正在放鸡蛋...
放鸡蛋线程放入了一个鸡蛋
拿鸡蛋线程正在拿鸡蛋...
拿鸡蛋线程拿走了一个鸡蛋
放鸡蛋线程正在放鸡蛋...
放鸡蛋线程放入了一个鸡蛋


0 个回复

您需要登录后才可以回帖 登录 | 加入黑马