两种常用的线程模型
两种线程模型:
????? 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
?
? 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
?