java线程深度解析(五)——并发模型(生产者-消费者)

2024-09-09 03:48

本文主要是介绍java线程深度解析(五)——并发模型(生产者-消费者),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

http://blog.csdn.net/Daybreak1209/article/details/51378055


三、生产者-消费者模式

    在经典的多线程模式中,生产者-消费者为多线程间协作提供了良好的解决方案。基本原理是两类线程,即若干个生产者和若干个消费者,生产者负责提交用户请求任务(到内存缓冲区),消费者线程负责处理任务(从内存缓冲区中取任务进行处理),两类线程之间通过共享内存缓冲区进行通信。

     共享内存缓冲区的存在避免生产者和消费者直接通信,且允许消费者和生产者执行速度上存在时间差,无论谁快谁慢,都可以通过缓冲区缓解,确保系统正常运行。

    生产者消费者模式中主要角色

生产者:提交用户请求,提取用户任务,并装入内存缓冲区;

消费者:在内存缓冲区中提取并处理任务;

内存缓冲区:缓存生产者提交的任务或数据,供消费者使用;

任务:生产者向内存缓冲区提交的数据结构;

Main:即Client客户端,使用生产者和消费者的客户端。

     下面用代码实现一个基于本模式的求整数平方和的并行计算。

     具体实现采用BlockingQueue充当缓冲区,创建一个任务类PCData,生产者负责创建PCData对象放入缓冲区,消费者负责处理从缓冲区中取出PCData对象进行处理。

(1)Producer生产者线程

[html]  view plain copy
在CODE上查看代码片 派生到我的代码片
  1. /*  
  2.  * 负责创建数据对象PCD并提交到内存缓冲区中  
  3.  */  
  4. public class Producer implements Runnable {  
  5.     private volatile boolean isRunning=true;  
  6.     private BlockingQueue<PCData> queue;  
  7.     private static AtomicInteger count=new AtomicInteger();//总数,原子操作      
  8.     private static final int SLEEPTIME=1000;      
  9.     public  Producer(BlockingQueue<PCData> queue)  
  10.     {  
  11.         this.queue=queue;  
  12.     }  
  13.       
  14.     @Override  
  15.     public void run() {  
  16.         PCData data=null;  
  17.         Random r=new Random();  
  18.         System.out.println("生产者当前线程"+Thread.currentThread().getId());  
  19.           
  20.         try{  
  21.             while(isRunning)  
  22.             {  
  23.                 Thread.sleep(r.nextInt(SLEEPTIME));  
  24.                 data=new PCData(count.incrementAndGet());//构造任务数据  
  25.               
  26.                 System.out.println(count.incrementAndGet());  
  27.                 System.out.println(data +"已进入缓存区");  
  28.                 if(!queue.offer(data,2,TimeUnit.SECONDS))  
  29.                 {  
  30.                     //提交数据到缓冲区  
  31.                     System.err.println(data+"存入失败");  
  32.                 }  
  33.             }  
  34.         }catch(Exception e)  
  35.         {  
  36.             Thread.currentThread().interrupt();  
  37.         }         
  38.     }  
  39.     public void stop()  
  40.     {  
  41.         isRunning=false;  
  42.     }      
  43. }  

(2)Consumer消费者线程:

[html]  view plain copy
在CODE上查看代码片 派生到我的代码片
  1. /*  
  2.  * 从缓冲区中获取PCData对象  
  3.  */  
  4. public class Consumer implements Runnable{  
  5.     private BlockingQueue<PCData> queue;  
  6.     private static final int SLEEPTIME=1000;  
  7.       
  8.     public Consumer(BlockingQueue<PCData> queue)  
  9.     {  
  10.         this.queue=queue;  
  11.     }  
  12.       
  13.     public void run()  
  14.     {  
  15.         System.out.println("消费者开始取数据,当前线程ID:"+Thread.currentThread().getId());  
  16.         Random r=new Random();  
  17.         try  
  18.         {  
  19.             while(true)  
  20.             {  
  21.                 PCData data=queue.take();  
  22.                 if(null!=data)  
  23.                 {  
  24.                     System.out.println("从缓冲区中获取数据"+data.getData());  
  25.                     int re=data.getData()*data.getData();//计算平方  
  26.                     System.out.println(MessageFormat.format("{0}*{1}={2}",data.getData(),data.getData(),re));  
  27.                     System.out.println("本数据对象处理完毕");  
  28.                     Thread.sleep(r.nextInt(SLEEPTIME));  
  29.                 }  
  30.                           
  31.             }  
  32.         }catch(Exception e)  
  33.         {  
  34.             Thread.currentThread().interrupt();  
  35.         }  
  36.     }  
  37. }  

(3)PCData共享数据模型:

[html]  view plain copy
在CODE上查看代码片 派生到我的代码片
  1. public final class PCData {  
  2.     private final int intData;  
  3.     public PCData(int d)  
  4.     {  
  5.         intData=d;  
  6.     }  
  7.     public PCData(String d)  
  8.     {  
  9.         intData=Integer.valueOf(d);  
  10.     }  
  11.       
  12.     public int getData()  
  13.     {  
  14.         return intData;  
  15.     }  
  16.       
  17.     @Override  
  18.     public String toString()  
  19.     {  
  20.         return "data:"+intData;  
  21.     }     
  22. }  

(4)Main函数:

[html]  view plain copy
在CODE上查看代码片 派生到我的代码片
  1. public class Client {  
  2.     public static void main(String[] args) throws InterruptedException {  
  3.         //建立缓冲区  
  4.         BlockingQueue<PCData> queue=new LinkedBlockingDeque<PCData>(10);  
  5.         //建立3个生产者  
  6.         Producer p1=new Producer(queue);  
  7.         Producer p2=new Producer(queue);  
  8.         Producer p3=new Producer(queue);  
  9.         //建立3个消费者  
  10.         Consumer c1=new Consumer(queue);  
  11.         Consumer c2=new Consumer(queue);  
  12.         Consumer c3=new Consumer(queue);  
  13.           
  14.         //创建线程池  
  15.         ExecutorService threadPool=Executors.newCachedThreadPool();  
  16.         threadPool.execute(p1);//启动生产者线程  
  17.         threadPool.execute(p2);  
  18.         threadPool.execute(p3);  
  19.           
  20.         threadPool.execute(c1);//启动消费者线程  
  21.         threadPool.execute(c2);  
  22.         threadPool.execute(c3);  
  23.           
  24.         Thread.sleep(10*1000);  
  25.         //停止生产  
  26.         p1.stop();  
  27.         p2.stop();  
  28.         p3.stop();//当消费者处理完缓冲区中所有数据,程序执行完毕  
  29.           
  30.         Thread.sleep(3000);  
  31.         threadPool.shutdown();    
  32.     }  
  33. }  

执行结果:

消费者开始取数据,当前线程ID:13
生产者当前线程10
生产者当前线程11
生产者当前线程12
消费者开始取数据,当前线程ID:14
消费者开始取数据,当前线程ID:15
2
data:1已进入缓存区
从缓冲区中获取数据1
1*1=1
本数据对象处理完毕    

总结:

     从执行结果可以看出,当客户端程序启动三个生产者、消费者线程时,生产者开始创建数据对象,缓冲区中数据个数为1,自增加一打印出2,传入1消费者开始进行平方处理,打印出平方结果,消费者线程处理完毕。依次循环操作,生产者线程关闭,直到消费者将缓冲区中的数据全部处理完毕时,程序运行结束。

     生产者-消费者模式能够很好的对生产者线程和消费者线程进行解耦,优化了系统结构。同时由于共享缓冲区的作用,允许两类线程存在执行速度上的差异,一定程度上缓解了性能瓶颈对系统运行的影响。


这篇关于java线程深度解析(五)——并发模型(生产者-消费者)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1150078

相关文章

Spring Boot循环依赖原理、解决方案与最佳实践(全解析)

《SpringBoot循环依赖原理、解决方案与最佳实践(全解析)》循环依赖指两个或多个Bean相互直接或间接引用,形成闭环依赖关系,:本文主要介绍SpringBoot循环依赖原理、解决方案与最... 目录一、循环依赖的本质与危害1.1 什么是循环依赖?1.2 核心危害二、Spring的三级缓存机制2.1 三

C#中async await异步关键字用法和异步的底层原理全解析

《C#中asyncawait异步关键字用法和异步的底层原理全解析》:本文主要介绍C#中asyncawait异步关键字用法和异步的底层原理全解析,本文给大家介绍的非常详细,对大家的学习或工作具有一... 目录C#异步编程一、异步编程基础二、异步方法的工作原理三、代码示例四、编译后的底层实现五、总结C#异步编程

在Spring Boot中浅尝内存泄漏的实战记录

《在SpringBoot中浅尝内存泄漏的实战记录》本文给大家分享在SpringBoot中浅尝内存泄漏的实战记录,结合实例代码给大家介绍的非常详细,感兴趣的朋友一起看看吧... 目录使用静态集合持有对象引用,阻止GC回收关键点:可执行代码:验证:1,运行程序(启动时添加JVM参数限制堆大小):2,访问 htt

SpringBoot集成Milvus实现数据增删改查功能

《SpringBoot集成Milvus实现数据增删改查功能》milvus支持的语言比较多,支持python,Java,Go,node等开发语言,本文主要介绍如何使用Java语言,采用springboo... 目录1、Milvus基本概念2、添加maven依赖3、配置yml文件4、创建MilvusClient

浅析Java中如何优雅地处理null值

《浅析Java中如何优雅地处理null值》这篇文章主要为大家详细介绍了如何结合Lambda表达式和Optional,让Java更优雅地处理null值,感兴趣的小伙伴可以跟随小编一起学习一下... 目录场景 1:不为 null 则执行场景 2:不为 null 则返回,为 null 则返回特定值或抛出异常场景

SpringMVC获取请求参数的方法

《SpringMVC获取请求参数的方法》:本文主要介绍SpringMVC获取请求参数的方法,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下... 目录1、通过ServletAPI获取2、通过控制器方法的形参获取请求参数3、@RequestParam4、@

SpringBoot应用中出现的Full GC问题的场景与解决

《SpringBoot应用中出现的FullGC问题的场景与解决》这篇文章主要为大家详细介绍了SpringBoot应用中出现的FullGC问题的场景与解决方法,文中的示例代码讲解详细,感兴趣的小伙伴可... 目录Full GC的原理与触发条件原理触发条件对Spring Boot应用的影响示例代码优化建议结论F

springboot项目中常用的工具类和api详解

《springboot项目中常用的工具类和api详解》在SpringBoot项目中,开发者通常会依赖一些工具类和API来简化开发、提高效率,以下是一些常用的工具类及其典型应用场景,涵盖Spring原生... 目录1. Spring Framework 自带工具类(1) StringUtils(2) Coll

SpringBoot条件注解核心作用与使用场景详解

《SpringBoot条件注解核心作用与使用场景详解》SpringBoot的条件注解为开发者提供了强大的动态配置能力,理解其原理和适用场景是构建灵活、可扩展应用的关键,本文将系统梳理所有常用的条件注... 目录引言一、条件注解的核心机制二、SpringBoot内置条件注解详解1、@ConditionalOn

通过Spring层面进行事务回滚的实现

《通过Spring层面进行事务回滚的实现》本文主要介绍了通过Spring层面进行事务回滚的实现,包括声明式事务和编程式事务,具有一定的参考价值,感兴趣的可以了解一下... 目录声明式事务回滚:1. 基础注解配置2. 指定回滚异常类型3. ​不回滚特殊场景编程式事务回滚:1. ​使用 TransactionT