两种线程模型:
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
相关推荐
在多线程服务器中,为了提高并发处理能力,可以采用以下两种典型的线程模型: - **One Loop Per Thread (OLPT)**:在这种模型中,每个线程都有一个事件循环(event loop),用来处理特定的任务。例如,一个线程专门...
在多线程编程领域,Java作为一门广泛使用的编程语言,其内置的多线程模型一直是学习和应用的重点。本文将深入探讨Java多线程模型的相关知识点,包括线程与进程的区别、线程的实现原理、线程的创建方法以及线程的阻塞...
这两种模型在处理并发请求、资源管理和性能优化方面各有特点。 **进程服务器模型** 进程服务器模型(Process Server Model)是早期的服务器架构,它基于单一职责原则,每个服务请求都会创建一个新的进程来处理。当...
### Java同步线程模型分析与改进 #### 一、引言 随着软件系统变得越来越复杂,多线程编程成为提高程序性能和响应性的关键手段之一。Java作为一种广泛使用的编程语言,自诞生以来就内置了对多线程的支持。然而,...
4. **线程本地存储**:C++09还引入了线程本地存储(Thread Local Storage, TLS),这是一种存储机制,可以在每个线程中独立维护变量的副本,减少了线程间通信的开销。 #### 五、案例分析 假设有一个简单的例子,其中...
【多线程模型】是指在一个进程中同时存在多个执行线程的编程模型,这种模型能够提高计算机系统的效率,尤其是在处理I/O密集型任务时。在操作系统中,进程是资源分配的基本单位,而线程是调度的基本单位。每个进程都...
在IT领域,多线程是程序设计中的一个重要概念,它允许程序同时执行多个任务,从而提高了系统的并发性和效率。...实际开发时,根据需求选择合适的线程模型,合理调度和管理线程,能够优化程序性能,提升用户体验。
在本文中,我们将深入探讨Apache Kafka的两种线程消费方式,这是基于提供的标题"Kafka Demo,两种线程消费方式"。Kafka是一种分布式流处理平台,广泛用于实时数据处理和消息传递。在这个示例中,我们将关注如何使用...
【标题】"Kafka Demo 两种线程消费方式"展示了在Kafka中处理消息的两种常见线程模型,这是理解Kafka消费者工作原理的关键部分。Kafka是一个分布式流处理平台,用于构建实时数据管道和应用,它允许生产者发布消息到...
在实际应用中,选择哪种线程模型取决于服务器的预期负载、系统资源和性能要求。线程池模型通常能提供更稳定、高效的并发处理,但需要合理设置线程池参数以达到最佳效果。 总结来说,多线程模型在Socket编程中起着...
在这个主题中,我们将深入探讨线程的概念,线程的分类,以及两种重要的操作系统——Linux和Solaris的线程模型。 一、线程分类 线程是操作系统中执行的基本单元,它们共享同一进程的内存空间和资源。线程的分类主要...
在计算机科学领域,多线程模型是用于管理和执行并发任务的一种设计模式,它允许多个线程在同一时间片内运行,提高系统资源的利用率和程序的响应速度。本资料包"多线程模型大搜集大全"显然是一个珍贵的资源集合,涵盖...
要在线程中使用这个列表,有两种主要方法: 1. 将列表作为参数传递给线程方法。你可以创建一个自定义委托,将列表作为参数传递,例如`Dim threadDelegate As New ThreadStart(Function() YourMethod(listArr))`。 ...
通过对Java同步线程模型和多进程模式的对比分析,我们发现Java同步线程模型能够发挥更大的优越性,两者的差异性主要体现在以下几个方面:对进程而言,其两个单位之间是相互分开的,而对线程而言,其每个执行单元具有...
本文将深入探讨两种主要的面向对象的C++线程模型:标准库中的`std::thread`和Boost库的`boost::thread`。 首先,让我们了解C++11引入的标准库`<thread>`。这个库提供了一个`std::thread`类,用于创建和管理线程。...
在深入探讨Netty的Reactor线程模型源码之前,我们需要先理解Reactor模式的基本概念。 Reactor模式是一种设计模式,常用于处理并发I/O事件。在多路复用I/O(如epoll、kqueue)中,Reactor模式是关键组成部分,它负责...
这两种方法都接受一个委托,该委托包含了需要在UI线程上执行的代码。`Dispatcher` 会根据当前的优先级队列安排这些操作,确保UI更新始终优先。 WPF的线程模型分为以下几种主要情况: 1. **主线程操作**:所有UI...
Dubbo的线程模型主要包括两个部分:消费者端和服务提供者端。在消费者端,Dubbo通过一个NIO线程池接收来自网络的请求,并将请求分发到业务线程池中处理。服务提供者端则负责处理这些请求,通常会有一个工作线程池来...
线程的实现方式主要有两种:用户级线程(User-Level Thread, ULT)和内核级线程(Kernel-Level Thread, KLT,又称“内核支持的线程”)。 用户级线程的实现主要依赖于编程语言提供的线程库。在早期的操作系统中,...