Java多线程-生产者和消费者

标签(空格分隔): java thread


简介:
随着操作系统的不断更新迭代,多线程编程已经变的十分常见,java虚拟机的多线程一般也是建立在操作系统本地native线程之上的,从而不必自己管理线程间的切换,直接交由本机操作系统进行调度和管理。对于java程序来说,它是运行在虚拟机上的,由于线程间很多资源都是共享的,比如全局数据等等,因此,线程间的同步也就不是那么复杂的。java也提供了许多实现线程同步的方法,我们这里主要分析wait和notify。

问题:
在一个盒子里,能放置一定数量的物体A,同一时间只能准许一个人往箱子里放物体或者一个人从盒子中取出物品。当盒子满了,再往里面放物体的人需要等待。当盒子空了,从盒子中取出物品的人需要等待。
这就是一个典型的生产者和消费者模型,下面在代码的基础上模拟这个过程。

  1. 盒子对象
public class DataManager {

  private static final int LIMIT = 20;
  private List<String> datas = new ArrayList<>();

  public synchronized void put( String data ) {
    while ( datas.size() == LIMIT ) {
      // size limit, need wait.
      try {
        // 处于wait等待的线程会暂时释放锁,当其被唤醒时,会重新获得锁
        wait();
      } catch ( InterruptedException e ) {
        e.printStackTrace();
      }
      // 线程被唤醒后,重新检查条件
    }
    datas.add( data );
    // 唤醒所有等待的线程
    notifyAll();
  }

  public synchronized String get() {

    while ( datas.size() == 0 ) {
      // datas is empty, need wait.
      try {
        wait();
      } catch ( InterruptedException e ) {
        e.printStackTrace();
      }
    }
    String data = datas.get( 0 );
    datas.remove( 0 );
    notifyAll();
    return data;
  }

}

DataManger展示了盒子这个模型,这里需要理解wait和notify以及notifyAll这几个方法。

2.生产者模型

public class Producer implements Runnable {

  static int count = 0;
  private DataManager mDataManager;

  public Producer( DataManager dataManager ) {
    mDataManager = dataManager;
  }

  @Override
  public void run() {
    while ( true ) {
      try {
        sleep( 100 );
      } catch ( InterruptedException e ) {
        e.printStackTrace();
      }
      String data = "data - " + count++;
      mDataManager.put( data );
      System.out.println( "Producer " + Thread.currentThread().getName() + ", put -> " + data );
    }
  }
}

3.消费者模型

public class Consumer implements Runnable {

  private DataManager mDataManager;

  public Consumer( DataManager dataManager ) {
    mDataManager = dataManager;
  }

  @Override
  public void run() {
    while ( true ) {
      try {
        sleep( 100 );
      } catch ( InterruptedException e ) {
        e.printStackTrace();
      }
      String data = mDataManager.get();
      System.out.println( "Consumer " + Thread.currentThread().getName() + ", get -> " + data );
    }
  }
}

Client端代码

public class Client {

  public static void main( String[] args ) {
    DataManager dataManager = new DataManager();
    for ( int i = 0; i < 20; i++ ) {
      new Thread( new Producer( dataManager ) ).start();
      new Thread( new Consumer( dataManager ) ).start();
    }
  }

}

总结:
整体结构来说是比较简单的,所有生产者Producer和所有消费者Consumer共享同一个盒子DataManager。同时对盒子进行存取操作。对于上述代码,应该重点理解wait、notify以及notifyAll它们作用的对象,它们三者必须在同步块中使用,同时理解wait会释放对象锁,这点和sleep有区别,而被唤醒后又会重新持有对象锁,这个是线程同步的关键,也是理解这些代码的关键。当然java还有许多api提供了线程同步的方法,比如锁机制,其实两者在原理上是大同小异,都是基于本机操作系统的线程同步机制。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 给大家分享我收藏的几个不错的 github 项目,内容都还是不错的,如果觉得有帮助,可以顺便给个 star。计算机...
    程序员欧阳阅读 2,004评论 3 81
  • 下面是我自己收集整理的Java线程相关的面试题,可以用它来好好准备面试。 参考文档:-《Java核心技术 卷一》-...
    阿呆变Geek阅读 14,987评论 14 507
  • 快一周没有更新【糊糊】了,一天天忙成狗,毕竟一个人撑起了一个部门的世界(最近来了一枚小鲜肉,忽略不计);就是这样天...
    huhusunshine阅读 401评论 0 1
  • 几日没发朋友圈 亲问何故不打油 心中惶恐报平安 没有病来亦无忧 打油并非我想打 情感触动才有油 最恨年少不读书 而...
    美线阅读 251评论 3 3
  • 人员调整问题,增加培训,人员配置不够。 卖场问题,建议性销售。主推产品。会员价产品更新。 理货不及时。跟近式服务。...
    贝壳里的海0阅读 145评论 0 0