多线程(三)--多线程间通信

来源:互联网 发布:2015淘宝双十一销售额 编辑:程序博客网 时间:2024/06/03 16:40

一.线程间通信

1.生产者与消费者问题

//多线程经典,卖烤鸭程序--生产者与消费者//定义一个资源类Resourceclass Resource{    private String name;    private int count = 1;    //set()方法给生产者调用    public void set(String name){        this.name = name + count;        //调用set()方法count自增        count++;        System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);    }    //out()方法给消费者调用    public void out(){        System.out.println(Thread.currentThread().getName()+"...消费者........"+this.name);    }}class Producer implements Runnable{    private Resource r;    Producer(Resource r){        this.r = r;    }    public void run(){        //设置生产者不停地生产        while(true){            r.set("烤鸭");        }    }}class Consumer implements Runnable{    private Resource r;    Consumer(Resource r){        this.r = r;    }    public void run(){        //设置消费者不停地消费        while(true){            r.out();        }    }}class  ProducerConsumerDemo{    public static void main(String[] args) {        Resource r = new Resource();        //创建一个生产者和一个消费者        Producer pro = new Producer(r);        Consumer con = new Consumer(r);        //分别将其加入到两个线程中        Thread t0 = new Thread(pro);        Thread t1 = new Thread(con);        t0.start();        t1.start();    }}

运行结果:
这里写图片描述

问题:消费者消费了生产者没有生产的资源

这里写图片描述

分析:set( )方法内操作了共享数据count,而且有多条操作语句,出现了多线程安全问题,消费者的线程可能抢在生产者前打印数据(如图烤鸭1638)

解决:使用同步函数解决多线程安全问题
只需更改Resource类,将set( )函数和out( )函数加上synchronized关键字

class Resource{    private String name;    private int count = 1;    public synchronized voidset(String name){        this.name = name + count;        count++;        System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);    }    public void synchronized out(){        System.out.println(Thread.currentThread().getName()+"...消费者........"+this.name);    }}

运行结果:

这里写图片描述

问题:消费者只能消费最近一次生产的资源
原因:两个方法使用了同一个锁,同一时间只能有一个方法执行,而set( )方法执行时count不断自增,out( )方法执行时count值则一直不变

显然仅仅加一个锁是不能达到我们的预期的,这时就要考虑其他解决办法。即线程间通信

2.线程间通信的概念

多个线程在操作同一个资源,但是操作的动作却不一样
实现方式:
1:将资源封装成对象
2:将线程执行的任务(任务其实就是run方法。)也封装成对象

3.等待唤醒机制:

涉及到的方法:

wait( )
将同步中的线程处于冻结状态,释放了执行权和执行资格,同时将线程对象存储到线程池
notify( )
唤醒线程池中某一个等待线程
notifyAll( )
唤醒线程池中的所有线程

使用前提:

1,这些方法都需要定义在同步中,不使用同步则这些方法没有意义
2,这些方法必须要标示所属的锁。
A锁上的线程被wait了,那这个线程就会被冻结然后保存到A锁的线程池中,只能A锁的notify唤醒

这三个方法都定义在Object类中,因为这三个方法在使用过程中必须要标识所属的锁对象,而锁对象可以是任意对象,所以这些可以被任意对象调用的方法一定定义在Object类中

//使用等待唤醒机制解决生产者和消费者问题class Resource{    private String name;    private int count = 1;    private boolean flag = false;    public synchronized void set(String name){        //如果flag=true,则生产者进入等待状态        while(flag)            try{this.wait();}catch(InterruptedException e){}        this.name = name + count;        count++;        System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);        flag = true;        //执行完后唤醒消费者        this.notify();    }    public synchronized void out(){        //如果flag=false,则消费者进入等待状态        while(!flag)            try{this.wait();}catch(InterruptedException e){}        System.out.println(Thread.currentThread().getName()+"...消费者........"+this.name);        flag = false;        //执行完后唤醒生产者        this.notify();    }}class Producer implements Runnable{    private Resource r;    Producer(Resource r){        this.r = r;    }    public void run(){        while(true){            r.set("烤鸭");        }    }}class Consumer implements Runnable{    private Resource r;    Consumer(Resource r){        this.r = r;    }    public void run(){        while(true){            r.out();        }    }}class  ProducerConsumerDemo{    public static void main(String[] args){        Resource r = new Resource();        Producer pro = new Producer(r);        Consumer con = new Consumer(r);        Thread t0 = new Thread(pro);        Thread t1 = new Thread(con);        t0.start();        t1.start();    }}

执行结果

这里写图片描述

wait和sleep区别:
1.唤醒方式不同
wait:可以指定时间也可以不指定时间,不指定时间则只能由对应的notify或者notifyAll来唤醒。
sleep:必须指定时间,时间到自动从冻结状态转成运行状态(临时阻塞状态)。
2.线程持有的锁的释放
wait:线程会释放执行权,而且线程会释放锁。
Sleep:线程会释放执行权,但是不释放锁。

4.多生产多消费

public class ProducerConsumerDemo {    public static void main(String[] args){        Resource r = new Resource();        Producer pro = new Producer(r);        Consumer con = new Consumer(r);        //分别定义两个生产者和两个消费者        Thread t0 = new Thread(pro);        Thread t1 = new Thread(pro);        Thread t2 = new Thread(con);        Thread t3 = new Thread(con);        t0.start();        t1.start();        t2.start();        t3.start();    }}

定义多个生产者和消费者

运行结果:

这里写图片描述

现象:程序停止不动,不在继续向下执行,但是却不是死锁,因为程序中没有同步的嵌套

分析:set( )和get( )方法中唤醒线程使用的是notify( )方法,只能唤醒一个线程。所以可能出现一个生产者线程唤醒了另一个生产者线程,而flag值是true,被唤醒的这个生产者线程经过while判断后也进入了wait等待状态,然后程序中就没有可以执行的线程了,程序停止。

解决:使用notifyAll( )方法,将所有线程都唤醒

class Resource{    private String name;    private int count = 1;    private boolean flag = false;    public synchronized void set(String name){        while(flag)            try{this.wait();}catch(InterruptedException e){}        this.name = name + count;        count++;        System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);        flag = true;        //只需将notify更改为notifyAll        this.notifyAll();    }    public synchronized void out(){        while(!flag)            try{this.wait();}catch(InterruptedException e){}        System.out.println(Thread.currentThread().getName()+"...消费者........"+this.name);        flag = false;        this.notifyAll();    }}

这里写图片描述

二.线程的停止

1.使用stop方法(已过时)
这里写图片描述
2.结束run( )方法
run方法里一般会定义循环,所以只要结束循环即可。
第一种方式:定义循环的结束标记。
第二种方式:如果线程处于了冻结状态,是不可能读到标记的,这时就需要通过Thread类中的interrupt方法,将其冻结状态强制清除。让线程恢复具备执行资格的状态,让线程可以读到标记,并结束。
这里写图片描述

三.Lock接口和Condition接口

解决线程安全问题使用同步的形式(同步代码块和同步函数),其实最终使用的都是锁机制。线程进入同步就是具备了锁,执行完毕离开同步,就是释放了锁。获取锁,释放锁的动作是锁对象的内容。在面向对象思想的指导下,将锁单独定义为对象并封装对锁的操作。Java的设计者专门开发了一个Lock接口,将获取锁,释放锁等操作都定义到了这个接口中

这里写图片描述

同步是隐示的锁操作,而Lock接口是显示的锁操作,Lock接口的出现就替代了同步。

在之前的版本中使用Object类中wait、notify、notifyAll的方式来完成的。那是因为同步中的锁是任意对象,所以操作锁的等待唤醒的方法都定义在Object类中。

而现在锁是指定对象Lock。所以查找等待唤醒机制方式需要通过Lock接口来完成。而Lock接口中并没有直接操作等待唤醒的方法,而是将这些方式又单独封装到了Condition接口中。

Condition接口将Object中的三个方法进行单独的封装。并提供了功能一致的方法 await( )、signal( )、signalAll( )

这里写图片描述

使用Lock接口实现多生产多消费

import java.util.concurrent.locks.*;class Resource{    private String name;    private int count = 1;    private boolean flag = false;    //创建一个Lock接口的实现类ReentrantLock的对象    Lock lock = new ReentrantLock();    //通过lock锁创建两个监听器,分别监听生产者和消费者    Condition producer_con = lock.newCondition();    Condition consumer_con = lock.newCondition();    public void set(String name){        //获取锁(相当于加同步)        lock.lock();        try{            while(flag)            //监听器调用await()方法使线程进入等待状态            try{producer_con.await();}catch(InterruptedException e){}            this.name = name + count;            count++;            System.out.println(Thread.currentThread().getName()+"...生产者5.0..."+this.name);            flag = true;            //唤醒消费者的线程            consumer_con.signal();        }        finally{            //操作执行完毕,释放锁            lock.unlock();        }    }    public void out(){        lock.lock();        try{            while(!flag)            try{cousumer_con.await();}catch(InterruptedException e){}               System.out.println(Thread.currentThread().getName()+"...消费者.5.0......."+this.name);            flag = false;            producer_con.signal();        }        finally{            lock.unlock();        }    }}class Producer implements Runnable{    private Resource r;    Producer(Resource r){        this.r = r;    }    public void run(){        while(true){            r.set("烤鸭");        }    }}class Consumer implements Runnable{    private Resource r;    Consumer(Resource r){        this.r = r;    }    public void run(){        while(true){            r.out();        }    }}class  ProducerConsumerDemo2{    public static void main(String[] args) {        Resource r = new Resource();        Producer pro = new Producer(r);        Consumer con = new Consumer(r);        Thread t0 = new Thread(pro);        Thread t1 = new Thread(pro);        Thread t2 = new Thread(con);        Thread t3 = new Thread(con);        t0.start();        t1.start();        t2.start();        t3.start();    }}
原创粉丝点击