两种线程模型:
1、生产者-消费者模型 ,就是由一个线程生产任务,而另外一个线程执行任务,二个线程之间有一个共享数据区,这种数据结构可以用队列来表示,但是必须是并发同步的,也就是就共享数据队列同一时间只能允许一个线程进行访问。这种机制叫做同步访问,在JAVA里面用关键字synchorinized 来标识对象是同步并发访问的。
生产者/消费者模式是一种很经典的线程同步模型,很多时候,并不是光保证多个线程对某共享资源操作的互斥性就够了,往往多个线程之间都是有协作的。
2、线程池模型 ,就是说开始由值守线程创建N个工作线程,并启动它们,它们的状态初始为空闲。然后值守线程到工作队列中取出一个工作任务,同时从线程池中取出一空闲线程来执行此工作任务,执行完该任务后,把该工作线程由运行变为空闲状态,这样不断的从工作队列中取出任务由线程池中的空闲线程进行执行完成。线程池模型不用为每个任务都创建一个线程,只需初始时创建N个线程,然后一直用这N个线程去执行工作队列中的任务,大大的减少了线程的启动,终止的开销。
总之,多线程编程的关键是线程类的设计,以及共享数据的设计,同时注意区分哪些是多线程可能访问,哪些是各线程自已私有的,还有就是线程的状态变换,比如挂起的线程何时需要唤醒。等等问题。。
生产者-消费者模型:
实际上,准确说应该是“生产者-消费者-仓储”模型,离开了仓储,生产者消费者模型就显得没有说服力了。
对于此模型,应该明确一下几点:
1、生产者仅仅在仓储未满时候生产,仓满则停止生产。
2、消费者仅仅在仓储有产品时候才能消费,仓空则等待。
3、当消费者发现仓储没产品可消费时候会通知生产者生产。
4、生产者在生产出可消费产品时候,应该通知等待的消费者去消费。
此模型将要结合java.lang.Object的wait与notify、notifyAll方法来实现以上的需求。这是非常重要的。
/** * 仓库 */ public class Godown { public static final int max_size = 100; //最大库存量 public int curnum; //当前库存量 Godown(){ } Godown(int curnum){ this.curnum = curnum; } /** * 生产指定数量的产品 * @param neednum */ public synchronized void produce(int neednum) { //测试是否需要生产 while (neednum + curnum > max_size) { System.out.println("要生产的产品数量" + neednum + "超过剩余库存量" + (max_size - curnum) + ",暂时不能执行生产任务!"); try { //当前的生产线程等待 wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //满足生产条件,则进行生产,这里简单的更改当前库存量 curnum += neednum; System.out.println("已经生产了" + neednum + "个产品,现仓储量为" + curnum); //唤醒在此对象监视器上等待的所有线程 notifyAll(); } /** * 消费指定数量的产品 * @param neednum */ public synchronized void consume(int neednum) { //测试是否可消费 while (curnum < neednum) { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //满足消费条件,则进行消费,这里简单的更改当前库存量 curnum -= neednum; System.out.println("已经消费了" + neednum + "个产品,现仓储量为" + curnum); //唤醒在此对象监视器上等待的所有线程 notifyAll(); } }
/** * 生产者 */ public class Producer extends Thread{ private int neednum; //生产产品的数量 private Godown godown; //仓库 Producer(){ } Producer(int neednum,Godown godown){ this.neednum = neednum; this.godown = godown; } public void run() { //生产指定数量的产品 godown.produce(neednum); } }
/** * 消费者 */ public class Consumer extends Thread{ private int neednum; private Godown godown; Consumer(){ } Consumer(int neednum, Godown godown){ this.neednum = neednum; this.godown = godown; } public void run(){ //消费指定数量的产品 godown.consume(neednum); } }
public class ThreadTest { public static void main(String[] args) { Godown godown = new Godown(30); Consumer c1 = new Consumer(50, godown); Consumer c2 = new Consumer(20, godown); Consumer c3 = new Consumer(30, godown); Producer p1 = new Producer(10, godown); Producer p2 = new Producer(10, godown); Producer p3 = new Producer(10, godown); Producer p4 = new Producer(10, godown); Producer p5 = new Producer(10, godown); Producer p6 = new Producer(10, godown); Producer p7 = new Producer(80, godown); Producer p8 = new Producer(10, godown); c1.start(); //wait c2.start(); c3.start(); //wait p1.start(); p2.start(); //p2执行完后,仓储量为30,唤醒等待线程时,c3在等待且消费数量满足要求,故又执行c3 //已经生产了10个产品,现仓储量为30 //已经消费了30个产品,现仓储量为0 p3.start(); p4.start(); p5.start(); p6.start(); p7.start(); //wait //要生产的产品数量80超过剩余库存量60,暂时不能执行生产任务! p8.start(); } }
结果:
已经消费了20个产品,现仓储量为10
已经生产了10个产品,现仓储量为20
已经生产了10个产品,现仓储量为30
已经消费了30个产品,现仓储量为0
已经生产了10个产品,现仓储量为10
已经生产了10个产品,现仓储量为20
已经生产了10个产品,现仓储量为30
已经生产了10个产品,现仓储量为40
要生产的产品数量80超过剩余库存量60,暂时不能执行生产任务!
已经生产了10个产品,现仓储量为50
要生产的产品数量80超过剩余库存量50,暂时不能执行生产任务!
已经消费了50个产品,现仓储量为0
已经生产了80个产品,现仓储量为80
API例子:
/*Usage example, based on a typical producer-consumer scenario. * Note that a <tt>BlockingQueue</tt> can safely be used with multiple * producers and multiple consumers. * / class Producer implements Runnable { private final BlockingQueue queue; Producer(BlockingQueue q) { queue = q; } public void run() { try { while (true) { queue.put(produce()); } } catch (InterruptedException ex) { ... handle ...} } Object produce() { ... } } class Consumer implements Runnable { private final BlockingQueue queue; Consumer(BlockingQueue q) { queue = q; } public void run() { try { while (true) { consume(queue.take()); } } catch (InterruptedException ex) { ... handle ...} } void consume(Object x) { ... } } class Setup { void main() { BlockingQueue q = new SomeQueueImplementation(); Producer p = new Producer(q); Consumer c1 = new Consumer(q); Consumer c2 = new Consumer(q); new Thread(p).start(); new Thread(c1).start(); new Thread(c2).start(); } }
example2:
假设有这样一种情况,有一个桌子,桌子上面有一个盘子,盘子里只能放一颗鸡蛋,A专门往盘子里放鸡蛋,如果盘子里有鸡蛋,则一直等到盘子里没鸡蛋,B专门从盘子里拿鸡蛋,如果盘子里没鸡蛋,则等待直到盘子里有鸡蛋。其实盘子就是一个互斥区,每次往盘子放鸡蛋应该都是互斥的,A的等待其实就是主动放弃锁,B 等待时还要提醒A放鸡蛋。
如何让线程主动释放锁
很简单,调用锁的wait()方法就好。wait方法是从Object来的,所以任意对象都有这个方法。
声明一个盘子,只能放一个鸡蛋
import java.util.ArrayList; import java.util.List; public class Plate { List<Object> eggs = new ArrayList<Object>(); public synchronized Object getEgg() { if (eggs.size() == 0) { try { wait(); } catch (InterruptedException e) { } } Object egg = eggs.get(0); eggs.clear();// 清空盘子 notify();// 唤醒阻塞队列的某线程到就绪队列 System.out.println("拿到鸡蛋"); return egg; } public synchronized void putEgg(Object egg) { if (eggs.size() > 0) { try { wait(); } catch (InterruptedException e) { } } eggs.add(egg);// 往盘子里放鸡蛋 notify();// 唤醒阻塞队列的某线程到就绪队列 System.out.println("放入鸡蛋"); } static class AddThread extends Thread{ private Plate plate; private Object egg=new Object(); public AddThread(Plate plate){ this.plate=plate; } public void run(){ for(int i=0;i<5;i++){ plate.putEgg(egg); } } } static class GetThread extends Thread{ private Plate plate; public GetThread(Plate plate){ this.plate=plate; } public void run(){ for(int i=0;i<5;i++){ plate.getEgg(); } } } public static void main(String args[]){ try { Plate plate=new Plate(); Thread add=new Thread(new AddThread(plate)); Thread get=new Thread(new GetThread(plate)); add.start(); get.start(); add.join(); get.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("测试结束"); } }
结果:
放入鸡蛋
拿到鸡蛋
放入鸡蛋
拿到鸡蛋
放入鸡蛋
拿到鸡蛋
放入鸡蛋
拿到鸡蛋
放入鸡蛋
拿到鸡蛋
测试结束
声明一个Plate对象为plate,被线程A和线程B共享,A专门放鸡蛋,B专门拿鸡蛋。假设
1 开始,A调用plate.putEgg方法,此时eggs.size()为0,因此顺利将鸡蛋放到盘子,还执行了notify()方法,唤醒锁的阻塞队列的线程,此时阻塞队列还没有线程。
2 又有一个A线程对象调用plate.putEgg方法,此时eggs.size()不为0,调用wait()方法,自己进入了锁对象的阻塞队列。
3 此时,来了一个B线程对象,调用plate.getEgg方法,eggs.size()不为0,顺利的拿到了一个鸡蛋,还执行了notify()方法,唤醒锁的阻塞队列的线程,此时阻塞队列有一个A线程对象,唤醒后,它进入到就绪队列,就绪队列也就它一个,因此马上得到锁,开始往盘子里放鸡蛋,此时盘子是空的,因此放鸡蛋成功。
4 假设接着来了线程A,就重复2;假设来料线程B,就重复3。
整个过程都保证了放鸡蛋,拿鸡蛋,放鸡蛋,拿鸡蛋。
参考:http://www.iteye.com/topic/806990
Example3:
题目为:
有一个南北向的桥,只能容纳一个人,现桥的两边分别有10人和12人,编制一个多线程序让这些人到达对岸,每个人用一个线程表示,桥为共享资源。在过桥的过程中显示谁在过桥及其走向
论坛链接:http://www.iteye.com/topic/1041415
解法如下:
桥是共享资源,采用单例
public enum Direction { North2Sourth{ String getInfo(){ return "北向南走"; } }, Sourth2North{ String getInfo(){ return "南向北走"; } }; abstract String getInfo(); }
public class Person implements Runnable { /** * 方向 */ private Direction direction; /** * 姓名*标志 */ private String name; /** * 持有一个桥的引用 */ private static final Bridge BRIDGE=Bridge.getIntance(); public Direction getDirection() { return direction; } public void setDirection(Direction direction) { this.direction = direction; } public String getName() { return name; } public void setName(String name) { this.name = name; } public void run() { BRIDGE.display(this); } }
public class Bridge { private static class BridgeContainer{ private static Bridge intance=new Bridge(); } private Bridge(){ } public synchronized void display(Person p) { DateFormat df = new SimpleDateFormat("HH:mm:ss"); String date = df.format(System.currentTimeMillis()); System.out.println("时间:" + date+"过桥人为" +p.getName()+"方向是:"+ p.getDirection().getInfo() ); try { /** * 模拟过桥过程 */ Thread.sleep(2000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } public static Bridge getIntance(){ return BridgeContainer.intance; } }
public class Client { public static void main(String[] args) { /** * 模拟在南边的12个 */ Person[] p1s=new Person[12]; for(int i=0;i<12;i++){ p1s[i]=new Person(); p1s[i].setName("n"+i+"号"); p1s[i].setDirection(Direction.North2Sourth); new Thread(p1s[i]).start(); } /** * 模拟在北边的10个 */ Person[] p2s=new Person[10]; for(int i=0;i<10;i++){ p2s[i]=new Person(); p2s[i].setName("s"+i+"号"); p2s[i].setDirection(Direction.Sourth2North); new Thread(p2s[i]).start(); } } }
运行结果如下:
时间:13:09:21过桥人为n0号方向是:北向南走 时间:13:09:23过桥人为s9号方向是:南向北走 时间:13:09:25过桥人为s8号方向是:南向北走 时间:13:09:27过桥人为s7号方向是:南向北走 时间:13:09:29过桥人为s5号方向是:南向北走 时间:13:09:31过桥人为s6号方向是:南向北走 时间:13:09:33过桥人为s4号方向是:南向北走 时间:13:09:35过桥人为n10号方向是:北向南走 时间:13:09:37过桥人为s0号方向是:南向北走 时间:13:09:39过桥人为n11号方向是:北向南走 时间:13:09:41过桥人为s2号方向是:南向北走 时间:13:09:43过桥人为s3号方向是:南向北走 时间:13:09:45过桥人为n8号方向是:北向南走 时间:13:09:47过桥人为n6号方向是:北向南走 时间:13:09:49过桥人为n9号方向是:北向南走 时间:13:09:51过桥人为n2号方向是:北向南走 时间:13:09:53过桥人为s1号方向是:南向北走 时间:13:09:55过桥人为n4号方向是:北向南走 时间:13:09:57过桥人为n7号方向是:北向南走 时间:13:09:59过桥人为n5号方向是:北向南走 时间:13:10:01过桥人为n1号方向是:北向南走 时间:13:10:03过桥人为n3号方向是:北向南走
2、线程池
在什么情况下使用线程池?
1.单个任务处理的时间比较短
2.将需处理的任务的数量大
使用线程池的好处:
1.减少在创建和销毁线程上所花的时间
2.如不使用线程池,有可能造成系统因创建大量线程而消耗完内存
http://xtu-xiaoxin.iteye.com/blog/647580
http://hi.baidu.com/fgfd0/blog/item/1fef52df03ba281f4954033b.html
相关推荐
线程池 epoll 总结了一两种常用的线程模型, 归纳了进程间通讯与线程同步的最佳实践,以期用简单规范的方式开发多线程程序。
两种面向对象的C++线程模型
kafka学习过程,maven工程,包含基础过程、提升过程。可供大家学习一下,里面有详细注释,一个groupid多个Consumer来消费消息和一个Consumer且有多个线程消费
例子中,生产者负责将1到1000的整数写入缓冲区,而消费者负责从同一个缓冲区中读取写入的整数并打印...因为生产者和消费者是两个同时运行的线程,并且要使用同一个缓冲区进行数据交换,因此必须利用一种机制进行同步。
是我自己写的多线程的服务器 客户端发 服务器收 用两种方式 多线程 select 还有一种是链表的
掌握两种基本的同步原语就可以满足各种多线程同步的功能需求,还能写出更易用的同步设施。掌握一种进程间通信方式和一种多线程网络编程模型就足以应对日常开发任务,编写运行于公司内网环境的分布式服务系统。
掌握两种基本的同步原语就可以满足各种多线程同步的功能需求,还能写出更易用的同步设施。掌握一种进程间通信方式和一种多线程网络编程模型就足以应对日常开发任务,编写运行于公司内网环境的分布式服务统。
掌握两种基本的同步原语就可以满足各种多线程同步的功能需求,还能写出更易用的同步设施。掌握一种进程间通信方式和一种多线程网络编程模型就足以应对日常开发任务,编写运行于公司内网环境的分布式服务系统。
Java线程的实现主要采用1:1的线程模型,即每个Java线程直接映射到一个操作系统原生线程。这种模型在主流平台上的主流商用Java虚拟机中普遍使用。以HotSpot为例,它的每一个Java线程都直接映射到一个操作系统原生...
Boost.Asio 有两种支持多线程的方式,第一种方式比较简单:在多线程的场景下,每个线程都持有一个io_service,并且每个线程都调用各自的io_service的run()方法。 另一种支持多线程的方式:全局只分配一个io_...
在命令式编程中,线程之间的通信机制有两种:共享内存和消息传递。在共享内存的并发模型里,线程之间共享程序的公共状态,通过写- 读内存中的公共状态进行隐式通信。在消息传递的并发模型里,线程之间没有公共状态,...
在命令式编程中,线程之间的通信机制有两种:共享内存和消息传递。 Java语言的并发采用的是共享内存模型,Java线程之间的通信总是隐式进行,整个通信过程对程序员完全透明。Java线程之间的通信由Java内存模型简称JMM...
一个多线程的文件拷贝工具的实现,文件操作接口使用了内存映射模型的方式实现,可以指定线程数量,可以在拷贝过程中查看整体...bin目录下对应Debug和Release两种方式的可执行文件; setup目录下包含应用程序的安装文件.
基于java打造的深度学习框架,帮助你快速搭建神经网络,实现训练或测试模型,支持多线程运算,框架目前支持BP神经网络、卷积神经网络、vgg16、resnet、yolo等模型的构建,目前引擎最新版本支持CUDA和CUDNN两种GPU...
在两路 (2S) 英特尔® 至强® 处理器 5100 系列平台(四核)上对七个较大的客户模拟模型的测试,显示出工程计算性能提升 3.8 倍至 3.9 倍。针对边缘图的优化使速度提升了 3% 到 44%。展望未来,MSC.Software 将继续...
通信是指线程之间以何种机制来交换信息,主要有两种:共享内存和消息传递 2、线程之间如何完成同步(这里的线程指的是并发执行的活动实体) Java内存模型是共享内存的并发模型,线程之间主要通过读-写共享变量来完成...
文章目录一、多线程的生命周期及五种基本状态二、Java多...本篇文章将分为两部分第一部分是讲解多线程基础、第二部分讲解Java内存模型。 一、多线程的生命周期及五种基本状态 Java多线程生命周期,首先看下面这张经典
在并发编程中,我们需要处理两个关键问题:线程之间如何通信及线程之间如何同步(这里的线程是指并发执行的活动实体)。通信是指线程之间以何种机制...在命令式编程中,线程之间的通信机制有两种:共享内存和消息传递。
Android提供了Invalidate方法实现界面刷新,但是Invalidate不能直接在线程中调用,因为他是违背了单线程模型:Android UI操作并不是线程安全的,并且这些操作必须在UI线程中调用。 Android程序中可以使用的界面...
实验结果证明, 异构多线程结构程序在实时性任务时五个软件线程需四个硬件线程支持, 而无实时性任务只需两个硬件线程支持。提出的多线程MAC层协议编程模型能够达到根据网络负载特征动态控制处理器性能的目的。