线程间通信

与网络通信等进程间通信方式不一样,线程间通信又称为进程内通信,多个线程实现互 斥访问共享资源时会互相发送信号或等待信号,比如线程等待数据到来的通知,线程收 到变量改变的信号等。

单线程间通信

假设我们现在需要两个线程,一个负责生产数据,一个负责消费数据,理想状态下我们 希望一边生产一边消费。

/**
 * 单线程环境下 实现线程间通信
 */
public class ProduceConsumerVersion2 {

    private final Object LOCK  = new Object();
    private int i = 0;

    //标记数据是否生产了数据
    private boolean isProduce = false;

    public void produce(){
        synchronized (LOCK){
            //如果生产者生产了数据等待消费者消费数据,否者生产数据
            if (isProduce){
                try {
                    LOCK.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }else {
                i++;  //生产数据
                System.out.println("p -> "+ i);
                isProduce = true;
                LOCK.notify();
            }
        }
    }


    public void consumer(){
        synchronized (LOCK){
            //如果生产者生产了数据就去消费,否者等待生产
            if (isProduce){
                System.out.println("c -> "+ i);
                isProduce = false;
                LOCK.notify();
            }else {
                try {
                    LOCK.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) {
        ProduceConsumerVersion2 version = new ProduceConsumerVersion2();
        new Thread(()->{
            while (true){
                version.produce();
            }
        }).start();

        new Thread(()->{
            while (true){
                version.consumer();
            }
        }).start();
    }
}

运行结果:

p -> 140604
c -> 140604
p -> 140605
c -> 140605
p -> 140606
c -> 140606
p -> 140607
c -> 140607
p -> 140608
c -> 140608

 结果正是我们希望的结果,生成一个消费一个。

多线程间通信

但是只是在单线程环境下,如同时有多个消费者,多个生产者这样写就不行。接下来我们把main方法改成这样:

 ProduceConsumerVersion version = new ProduceConsumerVersion();
        Stream.of("p1","p2").forEach(p -> new Thread(()->{
            while (true){
                version.produce();
            }
        },p).start());

        Stream.of("c1","c2","c3","c4").forEach(c -> new Thread(()->{
            while (true){
                version.consumer();
            }
        },c).start());
    }

运行结果:

 通过jstack发现这几条线程都处于等待状态

 这是因为在唤醒线程的时候使用的是notify()这个方法,当线程在调用notify()的时候发现多条线程处于waiting状态,他不知道该唤醒谁。所以使用notifyAll()唤醒当前全部处于waiting状态的线程便可以解决此问题。

练习案例

/**
 * 生产者:生产馒头
 * 消费者:消费馒头
 * 缓冲区:存放馒头 10个
 * 达到效果:生产者生产馒头放到缓冲区里,当缓冲区满了,生产者停止生产,消费者开始消费馒头,当缓冲区有位置,生产者继续生产
 */

public class ProduceConsumerVersion4 {
    public static void main(String[] args) {
        Box box = new Box();
        new Thread(new Producer(box)).start();
        new Thread(new Consumer(box)).start();
    }
}

class ManTou{
    private int id;

    public ManTou(int id) {
        this.id = id;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }
}

class Box{

    //盒子容量   10个
    private ManTou[] manTous = new ManTou[10];

    //盒子最后一个馒头的坐标
    private int index = 0;
    public synchronized void push(ManTou manTou){
        //判断馒头合作是否满了 , 满了停止,否则放馒头
        if(this.index == this.manTous.length){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }else {
            try {
                TimeUnit.MILLISECONDS.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            manTous[index++] = manTou;
            System.out.println("Producer 生产了一个馒头 : "+manTou.getId());
            System.out.println("生产一个馒头后:盒子还有"+this.index+"个馒头");
            //唤醒另一个线程去取馒头
            this.notify();
        }

    }

    public synchronized void pop(){
        //判断馒头合作是否为空,是空停止,否则取馒头
        if (this.index == 0 ){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }else {
            System.out.println("Consumer 消费了一个馒头 : "+manTous[--index].getId());
            System.out.println("消费一个馒头后:盒子还有"+this.index+"个馒头");
            this.manTous[index] = null;
            //通知那边可以放馒头了
            this.notify();
        }
    }
}

class Producer implements  Runnable{
    private Box box;
    private int index = 1;

    public Producer(Box box) {
        this.box = box;
    }

    @Override
    public void run() {
        while(true){
            box.push(new ManTou(index++));
        }
    }
}

class Consumer implements Runnable{
    private Box box;

    public Consumer(Box box) {
        this.box = box;
    }

    @Override
    public void run() {
        while(true){
            box.pop();
        }
    }
}

wait 和 sleep 的区别

  • sleep 是属于 Thread 的方法,wait 属于 Object;
  •  sleep 方法不需要被唤醒,wait 需要。
  • sleep 方法不需要 synchronized,wait 需要;
  • sleep 不会释放锁,wait 会释放锁并将线程加入 wait 队列;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值