Java并发编程学习(4):生产者消费者、线程状态转换

生产者-消费者模型

要点

  • 生产者仅负责产生数据结果,不关心数据如何处理
  • 消费者专心处理数据结果
  • 消息队列可以用来平衡生产者与消费者的线程资源
  • 消息队列有容量限制,满时不会再加入数据
  • JDK中各种阻塞队列,采用的就是这种模式

在这里插入图片描述

代码示例

消息队列

消息队列是模型中核心组成部分,控制着入队和出队逻辑。
消息队列存在容量上线,当消息队列满时,如果有线程希望继续向队列中添加消息,则会进入等待状态;同理,当消息队列为空时,如果有线程希望继续从队列中取出消息,也会进入等待状态。
因此,再完成向消息队列中存入/取出消息时,应该调用notifyAll()方法唤醒正在等待的线程。
同时,为了让每个消息都拥有唯一的id,我们专门封装了一个Message类,并将其设计为final类型以防止类中的逻辑被覆盖。

class MessageQueue{

    private final LinkedList<Message> list= new LinkedList<>();
    private int capacity;

    public MessageQueue(int capacity) {
        this.capacity = capacity;
    }

    // 获取消息
    public Message take(){
        // 检查队列是否为空
        synchronized (list) {
            while (list.isEmpty()) {
                log.info("队列为空");
                try {
                    list.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            Message message = list.removeFirst();
            list.notifyAll();
            return message;
        }

    }
    // 存入消息
    public void put(Message message){
        synchronized (list){
            while (list.size() >= capacity){
                log.info("队列已满");
                try {
                    list.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            list.addLast(message);
            list.notifyAll();
        }
    }
}

final class Message{
    private int id;
    private Object value;

    public Message(int id, Object value) {
        this.id = id;
        this.value = value;
    }

    public int getId() {
        return id;
    }

    public Object getValue() {
        return value;
    }
}

我们采用了一个简单的自增id作为生成器的逻辑,为了防止因为异步而其生成相同的id,我们对核心方法genarate()进行了同步处理。

final class IdGenerator{
    private static int id = 1;

    public static synchronized int genarate(){
        return id++;
    }
}

生产者

生产者的主要工作内容为产生消息,并将消息放入到消息队列中。sleepTime变量是为了控制生产者的生产频率:随着产生消息次数的增多,其生产频率会逐渐加快。

Runnable producerTask = () -> {
    long sleepTime = 2000;
    while (true) {
        int id = IdGenerator.genarate();
        Integer value = random.nextInt(1000);
        Message message = new Message(id, value);
        log.info("生成消息:id={},value={}", id, value);
        queue.put(message);
        log.info("成功放入消息:id={}", id);
        try {
            Thread.sleep(sleepTime);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        sleepTime /= 2;
    }
};

消费者

消费者的主要工作内容为从消息队列中拿到消息,并进行处理(这里的工作为打印其内容)。sleepTime变量是为了控制消费者的消费频率:随着处理消息次数的增多,其消费频率会逐渐减慢。

Runnable consumerTask = () -> {
    long sleepTime = 200;
    while (true) {
        Message message = queue.take();
        log.info("获得消息:id={},value={}", message.getId(), message.getValue());
        try {
            Thread.sleep(sleepTime);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        sleepTime += 100;
    }
};

测试方法

在测试方法中,我们一共创建了5个生产者和3个消费者,它们之间相互独立。

public class ProducerAndConsumer {

    public static void main(String[] args) {
        MessageQueue queue = new MessageQueue(5);

        Random random = new Random();

        Runnable producerTask = () -> {
            long sleepTime = 2000;
            while (true) {
                int id = IdGenerator.genarate();
                Integer value = random.nextInt(1000);
                Message message = new Message(id, value);
                log.info("生成消息:id={},value={}", id, value);
                queue.put(message);
                log.info("成功放入消息:id={}", id);
                try {
                    Thread.sleep(sleepTime);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                sleepTime /= 2;
            }
        };

        Runnable consumerTask = () -> {
            long sleepTime = 200;
            while (true) {
                Message message = queue.take();
                log.info("获得消息:id={},value={}", message.getId(), message.getValue());
                try {
                    Thread.sleep(sleepTime);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                sleepTime += 100;
            }
        };

        // 创建5个生产者
        for (int i = 0; i < 5; i++) {
            new Thread(producerTask,"生产者-"+i).start();
        }

        // 创建3个消费者
        for (int i = 0; i < 3; i++) {
            new Thread(consumerTask,"消费者-"+i).start();
        }
    }
}

从如下打印结果中能够看出:程序运行初期,消费者的消费速度快于生产者的生产速度,会出现队列为空的情况;程序运行后期,生产者的生产速度逐渐赶超消费者的消费速度,会出现队列已满的情况。

[165 ms] [INFO][生产者-2] i.k.e.c.p.ProducerAndConsumer : 生成消息:id=3,value=838
[164 ms] [INFO][消费者-0] i.k.e.c.p.MessageQueue : 队列为空
[165 ms] [INFO][生产者-3] i.k.e.c.p.ProducerAndConsumer : 生成消息:id=2,value=255
[164 ms] [INFO][生产者-4] i.k.e.c.p.ProducerAndConsumer : 生成消息:id=5,value=820
[164 ms] [INFO][生产者-1] i.k.e.c.p.ProducerAndConsumer : 生成消息:id=4,value=463
[164 ms] [INFO][生产者-0] i.k.e.c.p.ProducerAndConsumer : 生成消息:id=1,value=383
[168 ms] [INFO][生产者-4] i.k.e.c.p.ProducerAndConsumer : 成功放入消息:id=5
[168 ms] [INFO][生产者-1] i.k.e.c.p.ProducerAndConsumer : 成功放入消息:id=4
[168 ms] [INFO][生产者-0] i.k.e.c.p.ProducerAndConsumer : 成功放入消息:id=1
[168 ms] [INFO][消费者-1] i.k.e.c.p.ProducerAndConsumer : 获得消息:id=3,value=838
[168 ms] [INFO][生产者-3] i.k.e.c.p.ProducerAndConsumer : 成功放入消息:id=2
[168 ms] [INFO][生产者-2] i.k.e.c.p.ProducerAndConsumer : 成功放入消息:id=3
[168 ms] [INFO][消费者-0] i.k.e.c.p.ProducerAndConsumer : 获得消息:id=5,value=820
[168 ms] [INFO][消费者-2] i.k.e.c.p.ProducerAndConsumer : 获得消息:id=2,value=255
[370 ms] [INFO][消费者-1] i.k.e.c.p.ProducerAndConsumer : 获得消息:id=4,value=463
[370 ms] [INFO][消费者-2] i.k.e.c.p.ProducerAndConsumer : 获得消息:id=1,value=383
[371 ms] [INFO][消费者-0] i.k.e.c.p.MessageQueue : 队列为空
[670 ms] [INFO][消费者-2] i.k.e.c.p.MessageQueue : 队列为空
[670 ms] [INFO][消费者-1] i.k.e.c.p.MessageQueue : 队列为空
[2174 ms] [INFO][生产者-2] i.k.e.c.p.ProducerAndConsumer : 生成消息:id=8,value=749
[2174 ms] [INFO][生产者-1] i.k.e.c.p.ProducerAndConsumer : 生成消息:id=7,value=621
[2174 ms] [INFO][生产者-4] i.k.e.c.p.ProducerAndConsumer : 生成消息:id=9,value=987
[2174 ms] [INFO][生产者-0] i.k.e.c.p.ProducerAndConsumer : 生成消息:id=6,value=9
[2174 ms] [INFO][消费者-1] i.k.e.c.p.MessageQueue : 队列为空
[2174 ms] [INFO][消费者-0] i.k.e.c.p.ProducerAndConsumer : 获得消息:id=8,value=749
[2174 ms] [INFO][生产者-2] i.k.e.c.p.ProducerAndConsumer : 成功放入消息:id=8
[2174 ms] [INFO][消费者-2] i.k.e.c.p.MessageQueue : 队列为空
[2174 ms] [INFO][生产者-3] i.k.e.c.p.ProducerAndConsumer : 生成消息:id=10,value=543
[2175 ms] [INFO][生产者-3] i.k.e.c.p.ProducerAndConsumer : 成功放入消息:id=10
[2175 ms] [INFO][生产者-4] i.k.e.c.p.ProducerAndConsumer : 成功放入消息:id=9
[2174 ms] [INFO][生产者-0] i.k.e.c.p.ProducerAndConsumer : 成功放入消息:id=6
[2175 ms] [INFO][生产者-1] i.k.e.c.p.ProducerAndConsumer : 成功放入消息:id=7
[2175 ms] [INFO][消费者-2] i.k.e.c.p.ProducerAndConsumer : 获得消息:id=6,value=9
[2175 ms] [INFO][消费者-1] i.k.e.c.p.ProducerAndConsumer : 获得消息:id=9,value=987
[2484 ms] [INFO][消费者-0] i.k.e.c.p.ProducerAndConsumer : 获得消息:id=10,value=543
[2576 ms] [INFO][消费者-1] i.k.e.c.p.ProducerAndConsumer : 获得消息:id=7,value=621
[2576 ms] [INFO][消费者-2] i.k.e.c.p.MessageQueue : 队列为空
[2892 ms] [INFO][消费者-0] i.k.e.c.p.MessageQueue : 队列为空
[3079 ms] [INFO][消费者-1] i.k.e.c.p.MessageQueue : 队列为空
[3188 ms] [INFO][生产者-0] i.k.e.c.p.ProducerAndConsumer : 生成消息:id=12,value=524
[3188 ms] [INFO][生产者-1] i.k.e.c.p.ProducerAndConsumer : 生成消息:id=11,value=436
[3188 ms] [INFO][消费者-2] i.k.e.c.p.ProducerAndConsumer : 获得消息:id=12,value=524
[3188 ms] [INFO][生产者-1] i.k.e.c.p.ProducerAndConsumer : 成功放入消息:id=11
[3188 ms] [INFO][生产者-4] i.k.e.c.p.ProducerAndConsumer : 生成消息:id=13,value=937
[3188 ms] [INFO][生产者-3] i.k.e.c.p.ProducerAndConsumer : 生成消息:id=15,value=841
[3188 ms] [INFO][消费者-1] i.k.e.c.p.ProducerAndConsumer : 获得消息:id=11,value=436
[3188 ms] [INFO][消费者-0] i.k.e.c.p.MessageQueue : 队列为空
[3188 ms] [INFO][生产者-2] i.k.e.c.p.ProducerAndConsumer : 生成消息:id=14,value=478
[3189 ms] [INFO][生产者-0] i.k.e.c.p.ProducerAndConsumer : 成功放入消息:id=12
[3189 ms] [INFO][生产者-4] i.k.e.c.p.ProducerAndConsumer : 成功放入消息:id=13
[3189 ms] [INFO][生产者-3] i.k.e.c.p.ProducerAndConsumer : 成功放入消息:id=15
[3189 ms] [INFO][生产者-2] i.k.e.c.p.ProducerAndConsumer : 成功放入消息:id=14
[3189 ms] [INFO][消费者-0] i.k.e.c.p.ProducerAndConsumer : 获得消息:id=13,value=937
[3696 ms] [INFO][消费者-2] i.k.e.c.p.ProducerAndConsumer : 获得消息:id=14,value=478
[3696 ms] [INFO][生产者-2] i.k.e.c.p.ProducerAndConsumer : 生成消息:id=18,value=891
[3696 ms] [INFO][生产者-3] i.k.e.c.p.ProducerAndConsumer : 生成消息:id=16,value=120
[3696 ms] [INFO][消费者-0] i.k.e.c.p.ProducerAndConsumer : 获得消息:id=15,value=841
[3696 ms] [INFO][生产者-0] i.k.e.c.p.ProducerAndConsumer : 生成消息:id=17,value=686
[3696 ms] [INFO][生产者-4] i.k.e.c.p.ProducerAndConsumer : 生成消息:id=20,value=27
[3696 ms] [INFO][生产者-2] i.k.e.c.p.ProducerAndConsumer : 成功放入消息:id=18
[3696 ms] [INFO][生产者-1] i.k.e.c.p.ProducerAndConsumer : 生成消息:id=19,value=95
[3696 ms] [INFO][生产者-4] i.k.e.c.p.ProducerAndConsumer : 成功放入消息:id=20
[3696 ms] [INFO][生产者-3] i.k.e.c.p.ProducerAndConsumer : 成功放入消息:id=16
[3696 ms] [INFO][生产者-0] i.k.e.c.p.ProducerAndConsumer : 成功放入消息:id=17
[3696 ms] [INFO][生产者-1] i.k.e.c.p.ProducerAndConsumer : 成功放入消息:id=19
[3804 ms] [INFO][消费者-1] i.k.e.c.p.ProducerAndConsumer : 获得消息:id=18,value=891
[3959 ms] [INFO][生产者-2] i.k.e.c.p.ProducerAndConsumer : 生成消息:id=24,value=134
[3959 ms] [INFO][生产者-1] i.k.e.c.p.ProducerAndConsumer : 生成消息:id=21,value=131
[3959 ms] [INFO][生产者-2] i.k.e.c.p.ProducerAndConsumer : 成功放入消息:id=24
[3959 ms] [INFO][生产者-4] i.k.e.c.p.ProducerAndConsumer : 生成消息:id=25,value=811
[3959 ms] [INFO][生产者-1] i.k.e.c.p.MessageQueue : 队列已满
[3959 ms] [INFO][生产者-3] i.k.e.c.p.ProducerAndConsumer : 生成消息:id=23,value=311
[3959 ms] [INFO][生产者-0] i.k.e.c.p.ProducerAndConsumer : 生成消息:id=22,value=982
[3959 ms] [INFO][生产者-4] i.k.e.c.p.MessageQueue : 队列已满
[3959 ms] [INFO][生产者-0] i.k.e.c.p.MessageQueue : 队列已满
[3959 ms] [INFO][生产者-3] i.k.e.c.p.MessageQueue : 队列已满

park与unpark

功能与用法

方法park()unpark()都是LockSupport类中的方法,其功能分别为暂停当前线程的运行与恢复某个线程的运行。

// 暂停当前线程
LockSupport.park();

// 恢复某个线程的运行
LockSupport.unpark(某个线程对象);

特点

与Object的wait-notify相比:

  • wait()notify()notifyAll()需要与Monitor一起使用,而park()unpark()不必;
  • park()unpark()是以线程为单位来阻塞唤醒线程的,而notify()时随机唤醒,notifyAll()时全体唤醒,没有那么精确;
  • unpark()park()线程不会暂停,而先notifyAll()wait(),线程会暂停。

原理

每个线程都有自己的一个 Parker 对象,由三部分组成 :_counter_cond_mutex

情况1:正常情况下进行park()

在这里插入图片描述

  1. 当前线程调用 Unsafe.park()方法
  2. 检查 _counter,本情况为 0,这时,获得_mutex互斥锁
  3. 线程进入_cond条件变量(进入后会阻塞
  4. 设置_counter = 0

情况2:对阻塞的线程进行unpark()

在这里插入图片描述

  1. 调用 Unsafe.unpark(Thread_0)方法,设置_counter 为 1
  2. 唤醒_cond条件变量中的 Thread_0
  3. Thread_0 恢复运行
  4. 设置 _counter = 0

情况3:对正常线程先进行unpark()park()

在这里插入图片描述

  1. 调用Unsafe.unpark(Thread_0)方法,设置 _counter = 1
  2. 当前线程调用Unsafe.park()方法
  3. 检查_counter,本情况为 1,这时线程无需阻塞,继续运行
  4. 设置 _counter = 0

注意:从此情况可以分析出,如果进行连续多次unpark()操作,但也只会生效一次

线程状态转换

在这里插入图片描述
假设有线程t

情况1:NEW ==> RUNNABLE

当调用t.start()时,NEW ==> RUNNABLE

情况2:RUNNABLE <==> WAITING

线程t使用synchronized(obj)获取了对象锁后

  • 调用obj.wait()方法时,RUNNABLE ==> WAITING
  • 调用obj.notify()obj.notifyAll()t.interrupt()
    • 竞争锁成功,线程t从 WAITING ==> RUNNABLE
    • 竞争锁失败,线程t从 WAITING ==> BLOCKED

情况3:RUNNABLE <==> WAITING

  • 线程t调用其他线程的``join()```方法时,线程t从RUNNABLE ==> WAITING
    • 注意:是线程t在其它线程的监视器上等待
  • 当其他线程运行结束时,或有额外线程调用了t.interrupt()方法,会让线程t从WAITING ==> RUNNABLE

情况4:RUNNABLE <==> WAITING

  • 线程t内调用LockSupport.park()会让它从RUNNABLE ==> WAITING
  • 调用LockSupport.unpark(t)或有额外线程调用了t.interrupt()方法,会让线程t从WAITING ==> RUNNABLE

情况5:RUNNABLE <==> TIMED_WAITING

线程t使用synchronized(obj)获取了对象锁后

  • 调用obj.wait(long n)方法时,RUNNABLE ==> TIMED_WAITING
  • 当线程t等待时间超过n毫秒时,或调用obj.notify()obj.notifyAll()t.interrupt()
    • 竞争锁成功,线程t从 TIMED_WAITING ==> RUNNABLE
    • 竞争锁失败,线程t从 TIMED_WAITING ==> BLOCKED

情况6:RUNNABLE <==> TIMED_WAITING

  • 线程t调用其他线程的``join(long n)```方法时,线程t从RUNNABLE ==> TIMED_WAITING
    • 注意:是线程t在其它线程的监视器上等待
  • 当线程t等待时间超过n毫秒时,当其他线程运行结束时,或有额外线程调用了t.interrupt()方法,会让线程t从TIMED_WAITING ==> RUNNABLE

情况4:RUNNABLE <==> TIMED_WAITING

  • 线程t内调用LockSupport.parkNanos(long nanos)LockSupport.parkUntil(long millis)会让它从RUNNABLE ==> TIMED_WAITING
  • 当线程t等待超时,或有额外线程调用了LockSupport.unpark(t)或者t.interrupt()方法,会让线程t从TIMED_WAITING ==> RUNNABLE

情况8:RUNNABLE <==> TIMED_WAITING

  • 线程t内调用Thread.sleep(long n)会让它从RUNNABLE ==> TIMED_WAITING
  • 当线程t等待时间超过n毫秒时,或有额外线程调用了t.interrupt()方法,会让线程t从TIMED_WAITING ==> RUNNABLE

情况9:RUNNABLE <==> BLOCKED

  • 线程t使用synchronized(obj)获取对象锁时,竞争失败了,从RUNNABLE ==> BLOCKED
  • 持有obj锁的线程释放锁时,会唤醒该对象上所有BLOCKED的线程重新竞争,如果线程t竞争成功,会从BLOCKED ==> RUNNABLE ,其它失败的线程仍然为BLOCKED

情况10:RUNNABLE ==> TERMINATED

当线程所有代码运行完毕,会从RUNNABLE ==> TERMINATED

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值