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

一.线程间通信

1.生产者与消费者问题

//多线程经典,卖烤鸭程序--生产者与消费者
//定义一个资源类Resource
class 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();

    }
}

运行结果:
多线程(三)--多线程间通信_第1张图片

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

多线程(三)--多线程间通信_第2张图片

分析: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);

    }
}

运行结果:

多线程(三)--多线程间通信_第3张图片

问题:消费者只能消费最近一次生产的资源
原因:两个方法使用了同一个锁,同一时间只能有一个方法执行,而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();

    }
}

执行结果

多线程(三)--多线程间通信_第4张图片

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();

    }
}

定义多个生产者和消费者

运行结果:

多线程(三)--多线程间通信_第5张图片

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

分析: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();
    }
}

多线程(三)--多线程间通信_第6张图片

二.线程的停止

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

三.Lock接口和Condition接口

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

多线程(三)--多线程间通信_第9张图片

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

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

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

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

多线程(三)--多线程间通信_第10张图片

使用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();

    }
}

你可能感兴趣的:(JavaSE基础,Java多线程,JavaSE基础)