线程交互是比较复杂的问题,SCJP要求不很基础:给定一个场景,编写代码来恰当使用等待、通知和通知所有线程。
一、线程交互的基础知识
SCJP所要求的线程交互知识点需要从java.lang.Object的类的三个方法来学习:
void notify()——唤醒在此对象监视器上等待的单个线程。 void notifyAll()——唤醒在此对象监视器上等待的所有线程。 void wait()——导致当前的线程等待,直到其他线程调用此对象的 notify()方法或 notifyAll()方法。
当然,wait()还有另外两个重载方法:
void wait(longtimeout)——导致当前的线程等待,直到其他线程调用此对象的 notify()方法或 notifyAll()方法,或者超过指定的时间量。 void wait(longtimeout, int nanos)——导致当前的线程等待,直到其他线程调用此对象的 notify()方法或 notifyAll()方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量。
以上这些方法是帮助线程传递线程关心的时间状态。
关于等待/通知,要记住的关键点是:
必须从同步环境内调用wait()、notify()、notifyAll()方法。线程不能调用对象上等待或通知的方法,除非它拥有那个对象的锁。
wait()、notify()、notifyAll()都是Object的实例方法。与每个对象具有锁一样,每个对象可以有一个线程列表,他们等待来自该信号(通知)。线程通过执行对象上的wait()方法获得这个等待列表。从那时候起,它不再执行任何其他指令,直到调用对象的notify()方法为止。如果多个线程在同一个对象上等待,则将只选择一个线程(不保证以何种顺序)继续执行。如果没有线程等待,则不采取任何特殊操作。
下面看个例子就明白了:
/** * 计算输出其他线程锁计算的数据 */ public class ThreadA { public static void main(String[] args) { ThreadB b=new ThreadB(); //启动计算线程 b.start(); //线程A拥有b对象上的锁。线程为了调用wait()或notify()方法,该线程必须是那个对象锁的拥有者 synchronized (b) { try { System.out.println("等待对象b完成计算......"); b.wait(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("b对象计算的总和是:" + b.total); } } } /** * 计算1+2+3+...+100的和 */ public class ThreadB extends Thread { int total; public void run(){ synchronized (this) { for (int i=0;i<101;i++){ total+=i; } //(完成计算了)唤醒在此对象监视器上等待的单个线程,在本例中线程A被唤醒 notify(); } } }
执行结果:
等待对象b完成计算......
b对象计算的总和是:5050
千万注意:
当在对象上调用wait()方法时,执行该代码的线程立即放弃它在对象上的锁。然而调用notify()时,并不意味着这时线程会放弃其锁。如果线程荣然在完成同步代码,则线程在移出之前不会放弃锁。因此,只要调用notify()并不意味着这时该锁变得可用。
二、多个线程在等待一个对象锁时候使用notifyAll()
在多数情况下,最好通知等待某个对象的所有线程。如果这样做,可以在对象上使用notifyAll()让所有在此对象上等待的线程冲出等待区,返回到可运行状态。
举个例子:
/** * 计算线程 */ public class Calculator extends Thread { int total; @Override public void run() { synchronized (this) { for(int i=0;i<101;i++){ total+=i; } } //通知所有在此对象上等待的线程 notifyAll(); } } /** * 获取计算结果并输出 */ public class ReaderResult extends Thread { Calculator c; public ReaderResult(Calculator c) { this.c = c; } public void run(){ synchronized (c) { try { System.out.println(Thread.currentThread() + "等待计算结果......"); c.wait(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread()+ "计算结果为:" + c.total); } } public static void main(String[] args) { Calculator calculator=new Calculator(); //启动三个线程,分别获取计算结果 new ReaderResult(calculator).start(); new ReaderResult(calculator).start(); new ReaderResult(calculator).start(); //启动计算线程 calculator.start(); } }
执行结果:
Thread[Thread-1,5,main]等待计算结果...... Thread[Thread-2,5,main]等待计算结果...... Thread[Thread-3,5,main]等待计算结果...... Exception in thread"Thread-0" java.lang.IllegalMonitorStateException atjava.lang.Object.notifyAll(Native Method) attest.Calculator.run(Calculator.java:15) Thread[Thread-3,5,main]计算结果为:5050 Thread[Thread-2,5,main]计算结果为:5050 Thread[Thread-1,5,main]计算结果为:5050
运行结果表明,程序中有异常,并且多次运行结果可能有多种输出结果。这就是说明,这个多线程的交互程序还存在问题。究竟是出了什么问题,需要深入的分析和思考,下面将做具体分析。
实际上,上面这个代码中,我们期望的是读取结果的线程在计算线程调用notifyAll()之前等待即可。但是,如果计算线程先执行,并在读取结果线程等待之前调用了notify()方法,那么又会发生什么呢?这种情况是可能发生的。因为无法保证线程的不同部分将按照什么顺序来执行。幸运的是当读取线程运行时,它只能马上进入等待状态----它没有做任何事情来检查等待的事件是否已经发生。 ----因此,如果计算线程已经调用了notifyAll()方法,那么它就不会再次调用notifyAll(),----并且等待的读取线程将永远保持等待。这当然是开发者所不愿意看到的问题。
因此,当等待的事件发生时,需要能够检查notifyAll()通知事件是否已经发生。
通常,解决上面问题的最佳方式是利用某种循环,该循环检查某个条件表达式,只有当正在等待的事情还没有发生的情况下,它才继续等待。
Java线程:线程的调度-休眠
Java线程调度是Java多线程的核心,只有良好的调度,才能充分发挥系统的性能,提高程序的执行效率。
这里要明确的一点,不管程序员怎么编写调度,只能最大限度的影响线程执行的次序,而不能做到精准控制。
线程休眠的目的是使线程让出CPU的最简单的做法之一,线程休眠时候,会将CPU资源交给其他线程,以便能轮换执行,当休眠一定时间后,线程会苏醒,进入准备状态等待执行。
线程休眠的方法是Thread.sleep(long millis)和Thread.sleep(long millis, int nanos),均为静态方法,那调用sleep休眠的哪个线程呢?简单说,哪个线程调用sleep,就休眠哪个线程。
/** * Java线程:线程的调度-休眠 */ public class TestSleep { public static void main(String[] args) { Thread t1=new MyThread1(); Thread t2=new Thread(new MyRunnable()); t1.start(); t2.start(); } } class MyThread1 extends Thread{ @Override public void run() { for(int i=0;i<3;i++){ System.out.println("线程1第"+i+"次执行!"); try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } } } } class MyRunnable implements Runnable{ @Override public void run() { for(int i=0;i<3;i++){ System.out.println("线程2第"+i+"次执行!"); try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } } } }
执行结果:
线程1第0次执行!
线程2第0次执行!
线程2第1次执行!
线程1第1次执行!
线程2第2次执行!
线程1第2次执行!
从上面的结果输出可以看出,无法精准保证线程执行次序。
Java线程:线程的调度-优先级
与线程休眠类似,线程的优先级仍然无法保障线程的执行次序。只不过,优先级高的线程获取CPU资源的概率较大,优先级低的并非没机会执行。
线程的优先级用1-10之间的整数表示,数值越大优先级越高,默认的优先级为5。
在一个线程中开启另外一个新线程,则新开线程称为该线程的子线程,子线程初始优先级与父线程相同。
/** * Java线程:线程的调度-优先级 */ public class TestPriority { public static void main(String[] args) { Thread t1=new MyThread1(); Thread t2=new Thread(new MyRunnable()); t1.setPriority(10); t2.setPriority(1); t1.start(); t2.start(); } } class MyThread1 extends Thread{ @Override public void run() { for(int i=0;i<10;i++){ System.out.println("线程1第"+i+"次执行!"); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } } class MyRunnable implements Runnable{ @Override public void run() { for(int i=0;i<10;i++){ System.out.println("线程2第"+i+"次执行!"); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } }
执行结果:
线程1第0次执行!
线程1第1次执行!
线程1第2次执行!
线程2第0次执行!
线程1第3次执行!
线程2第1次执行!
线程1第4次执行!
线程2第2次执行!
线程1第5次执行!
线程2第3次执行!
线程1第6次执行!
线程2第4次执行!
线程1第7次执行!
线程2第5次执行!
线程1第8次执行!
线程2第6次执行!
线程1第9次执行!
线程2第7次执行!
线程2第8次执行!
线程2第9次执行!
Java线程:线程的调度-让步
线程的让步含义就是使当前运行着线程让出CPU资源,但是让给谁不知道,仅仅是让出,线程状态回到可运行状态。
线程的让步使用Thread.yield()方法,yield()为静态方法,功能是暂停当前正在执行的线程对象,并执行其他线程。
/** * Java线程:线程的调度-让步 */ public class Test { public static void main(String[] args) { Thread t1=new MyThread1(); Thread t2=new Thread(new MyRunnable()); t1.start(); t2.start(); } } class MyThread1 extends Thread{ @Override public void run() { for(int i=0;i<10;i++){ System.out.println("线程1第"+i+"次执行!"); } } } class MyRunnable implements Runnable{ @Override public void run() { for(int i=0;i<10;i++){ System.out.println("线程2第"+i+"次执行!"); Thread.yield(); } } }
执行结果:
线程2第0次执行!
线程1第0次执行!
线程1第1次执行!
线程1第2次执行!
线程1第3次执行!
线程1第4次执行!
线程1第5次执行!
线程1第6次执行!
线程1第7次执行!
线程1第8次执行!
线程1第9次执行!
线程2第1次执行!
线程2第2次执行!
线程2第3次执行!
线程2第4次执行!
线程2第5次执行!
线程2第6次执行!
线程2第7次执行!
线程2第8次执行!
线程2第9次执行!
Java线程:线程的调度-合并
线程的合并的含义就是将几个并行线程的线程合并为一个单线程执行,应用场景是当一个线程必须等待另一个线程执行完毕才能执行时可以使用join方法。
join为非静态方法,定义如下:
void join()——等待该线程终止。 void join(longmillis)——等待该线程终止的时间最长为 millis毫秒。 void join(longmillis,int nanos)——等待该线程终止的时间最长为 millis毫秒 + nanos 纳秒。
/** * Java线程:线程的调度-合并 */ public class Test { public static void main(String[] args) { Thread t1=new MyThread1(); t1.start(); for (int i = 0; i < 20; i++) { System.out.println("主线程第" + i +"次执行!"); if (i>2) { try { ///t1线程合并到主线程中,主线程停止执行过程,转而执行t1线程,直到t1执行完毕后继续。 t1.join(); } catch (InterruptedException e) { e.printStackTrace(); } } } } } class MyThread1 extends Thread{ @Override public void run() { for(int i=0;i<10;i++){ System.out.println("线程1第"+i+"次执行!"); } } }
执行结果:
主线程第0次执行!
主线程第1次执行!
主线程第2次执行!
主线程第3次执行!
线程1第0次执行!
线程1第1次执行!
线程1第2次执行!
线程1第3次执行!
线程1第4次执行!
线程1第5次执行!
线程1第6次执行!
线程1第7次执行!
线程1第8次执行!
线程1第9次执行!
主线程第4次执行!
主线程第5次执行!
主线程第6次执行!
主线程第7次执行!
主线程第8次执行!
主线程第9次执行!
主线程第10次执行!
主线程第11次执行!
主线程第12次执行!
主线程第13次执行!
主线程第14次执行!
主线程第15次执行!
主线程第16次执行!
主线程第17次执行!
主线程第18次执行!
主线程第19次执行!
Java线程:线程的调度-守护线程
守护线程与普通线程写法上基本么啥区别,调用线程对象的方法setDaemon(true),则可以将其设置为守护线程。
守护线程使用的情况较少,但并非无用,举例来说,JVM的垃圾回收、内存管理等线程都是守护线程。还有就是在做数据库应用时候,使用的数据库连接池,连接池本身也包含着很多后台线程,监控连接个数、超时时间、状态等等。
setDaemon方法的详细说明:
public final void setDaemon(boolean on)将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java虚拟机退出。
该方法必须在启动线程前调用。
该方法首先调用该线程的 checkAccess方法,且不带任何参数。这可能抛出 SecurityException(在当前线程中)。
参数:on - 如果为true,则将该线程标记为守护线程。
抛出:
IllegalThreadStateException- 如果该线程处于活动状态。
SecurityException- 如果当前线程无法修改该线程。
另请参见:
isDaemon(),checkAccess()
/** * Java线程:线程的调度-守护线程 */ public class Test { public static void main(String[] args) { Thread t1=new MyCommon(); Thread t2=new Thread(new MyDaemon()); t2.setDaemon(true);//设置为守护线程 t2.start(); t1.start(); } } class MyCommon extends Thread{ @Override public void run() { for(int i=0;i<5;i++){ System.out.println("线程1第"+i+"次执行!"); try { Thread.sleep(7); } catch (InterruptedException e) { e.printStackTrace(); } } } } class MyDaemon implements Runnable{ @Override public void run() { for (long i = 0; i < 9999999L; i++) { System.out.println("后台线程第" + i +"次执行!"); try { Thread.sleep(7); } catch (InterruptedException e) { e.printStackTrace(); } } } }
执行结果:
线程1第0次执行!
后台线程第0次执行!
后台线程第1次执行!
线程1第1次执行!
后台线程第2次执行!
线程1第2次执行!
后台线程第3次执行!
线程1第3次执行!
后台线程第4次执行!
线程1第4次执行!
后台线程第5次执行!
后台线程第6次执行!
后台线程第7次执行!
后台线程第8次执行!
后台线程第9次执行!
后台线程第10次执行!
从上面的执行结果可以看出:
前台线程是保证执行完毕的,后台线程还没有执行完毕就退出了。
实际上:JRE判断程序是否执行结束的标准是所有的前台执线程行完毕了,而不管后台线程的状态,因此,在使用后台县城时候一定要注意这个问题。
Java线程:线程的同步-同步方法
线程的同步是保证多线程安全访问竞争资源的一种手段。
线程的同步是Java多线程编程的难点,往往开发者搞不清楚什么是竞争资源、什么时候需要考虑同步,怎么同步等等问题,当然,这些问题没有很明确的答案,但有些原则问题需要考虑,是否有竞争资源被同时改动的问题?
在本部分之前,请参阅《Java线程:线程的同步与锁》部分,本部分是在此基础上所写的。
对于同步,在具体的Java代码中需要完成一下两个操作:
把竞争访问的资源标识为private;
同步哪些修改变量的代码,使用synchronized关键字同步方法或代码。
当然这不是唯一控制并发安全的途径。
synchronized关键字使用说明
synchronized只能标记非抽象的方法,不能标识成员变量。
为了演示同步方法的使用,构建了一个信用卡账户,起初信用额为100w,然后模拟透支、存款等多个操作。显然银行账户User对象是个竞争资源,而多个并发操作的是账户方法oper(int x),当然应该在此方法上加上同步,并将账户的余额设为私有变量,禁止直接访问。
/** * Java线程:线程的同步 */ public class Test { public static void main(String[] args) { User u = new User("张三", 100); MyThread t1 = new MyThread("线程A", u, 20); MyThread t2 = new MyThread("线程B", u, -60); MyThread t3 = new MyThread("线程C", u, -80); MyThread t4 = new MyThread("线程D", u, -30); MyThread t5 = new MyThread("线程E", u, 32); MyThread t6 = new MyThread("线程F", u, 21); t1.start(); t2.start(); t3.start(); t4.start(); t5.start(); t6.start(); } } class MyThread extends Thread { private User u; private int y = 0; MyThread(String name, User u, int y) { super(name); this.u = u; this.y = y; } public void run() { u.oper(y); } } class User { private String code; private int cash; User(String code, int cash) { this.code = code; this.cash = cash; } public String getCode() { return code; } public void setCode(String code) { this.code = code; } /** * 业务方法 * @param x 添加x万元 */ public synchronized void oper(int x) { try { Thread.sleep(10L); this.cash += x; System.out.println(Thread.currentThread().getName() + "运行结束,增加“" + x + "”,当前用户账户余额为:" + cash); Thread.sleep(10L); } catch (InterruptedException e) { e.printStackTrace(); } } @Override public String toString() { return "User{" + "code='" + code + '\'' + ",cash=" + cash + '}'; } }
执行结果:
线程A运行结束,增加“20”,当前用户账户余额为:120 线程F运行结束,增加“21”,当前用户账户余额为:141 线程D运行结束,增加“-30”,当前用户账户余额为:111 线程B运行结束,增加“-60”,当前用户账户余额为:51 线程E运行结束,增加“32”,当前用户账户余额为:83 线程C运行结束,增加“-80”,当前用户账户余额为:3
反面教材,不同步的情况,也就是去掉oper(int x)方法的synchronized修饰符,然后运行程序,结果如下:
线程F运行结束,增加“21”,当前用户账户余额为:121 线程D运行结束,增加“-30”,当前用户账户余额为:91 线程B运行结束,增加“-60”,当前用户账户余额为:31 线程E运行结束,增加“32”,当前用户账户余额为:63 线程A运行结束,增加“20”,当前用户账户余额为:3 线程C运行结束,增加“-80”,当前用户账户余额为:-17
很显然,上面的结果是错误的,导致错误的原因是多个线程并发访问了竞争资源u,并对u的属性做了改动。
可见同步的重要性。
注意:
通过前文可知,线程退出同步方法时将释放掉方法所属对象的锁,但还应该注意的是,同步方法中还可以使用特定的方法对线程进行调度。这些方法来自于java.lang.Object类。
void notify() 唤醒在此对象监视器上等待的单个线程。 void notifyAll() 唤醒在此对象监视器上等待的所有线程。 void wait() 导致当前的线程等待,直到其他线程调用此对象的 notify()方法或 notifyAll()方法。 void wait(long timeout) 导致当前的线程等待,直到其他线程调用此对象的 notify()方法或 notifyAll()方法,或者超过指定的时间量。 void wait(long timeout,int nanos) 导致当前的线程等待,直到其他线程调用此对象的 notify()方法或 notifyAll()方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量。
结合以上方法,处理多线程同步与互斥问题非常重要,著名的生产者-消费者例子就是一个经典的例子,任何语言多线程必学的例子。
Java线程:线程的同步-同步块
对于同步,除了同步方法外,还可以使用同步代码块,有时候同步代码块会带来比同步方法更好的效果。
追其同步的根本的目的,是控制竞争资源的正确的访问,因此只要在访问竞争资源的时候保证同一时刻只能一个线程访问即可,因此Java引入了同步代码快的策略,以提高性能。
在上个例子的基础上,对oper方法做了改动,由同步方法改为同步代码块模式,程序的执行逻辑并没有问题。
/** * Java线程:线程的同步-同步代码块 */ public class Test { public static void main(String[] args) { User u = new User("张三", 100); MyThread t1 = new MyThread("线程A", u, 20); MyThread t2 = new MyThread("线程B", u, -60); MyThread t3 = new MyThread("线程C", u, -80); MyThread t4 = new MyThread("线程D", u, -30); MyThread t5 = new MyThread("线程E", u, 32); MyThread t6 = new MyThread("线程F", u, 21); t1.start(); t2.start(); t3.start(); t4.start(); t5.start(); t6.start(); } } class MyThread extends Thread{ private User u; private int y = 0; MyThread(String name, User u, int y) { super(name); this.u = u; this.y = y; } public void run() { u.oper(y); } } class User { private String code; private int cash; User(String code, int cash) { this.code = code; this.cash = cash; } public String getCode() { return code; } public void setCode(String code) { this.code = code; } /** * 业务方法 * @param x 添加x万元 */ public void oper(int x) { try { Thread.sleep(10L); synchronized (this) { this.cash += x; System.out.println(Thread.currentThread().getName() + "运行结束,增加“" + x + "”,当前用户账户余额为:" + cash); } Thread.sleep(10L); } catch (InterruptedException e) { e.printStackTrace(); } } @Override public String toString() { return "User{" + "code='" + code + '\'' + ",cash=" + cash + '}'; } }
执行结果:
线程B运行结束,增加“-60”,当前用户账户余额为:40 线程D运行结束,增加“-30”,当前用户账户余额为:10 线程F运行结束,增加“21”,当前用户账户余额为:31 线程E运行结束,增加“32”,当前用户账户余额为:63 线程C运行结束,增加“-80”,当前用户账户余额为:-17 线程A运行结束,增加“20”,当前用户账户余额为:3
注意:
在使用synchronized关键字时候,应该尽可能避免在synchronized方法或synchronized块中使用sleep或者yield方法,因为synchronized程序块占有着对象锁,你休息那么其他的线程只能一边等着你醒来执行完了才能执行。不但严重影响效率,也不合逻辑。
同样,在同步程序块内调用yeild方法让出CPU资源也没有意义,因为你占用着锁,其他互斥线程还是无法访问同步程序块。当然与同步程序块无关的线程可以获得更多的执行时间。
Java线程:并发协作-生产者消费者模型
对于多线程程序来说,不管任何编程语言,生产者和消费者模型都是最经典的。就像学习每一门编程语言一样,Hello World!都是最经典的例子。
实际上,准确说应该是“生产者-消费者-仓储”模型,离开了仓储,生产者消费者模型就显得没有说服力了。
对于此模型,应该明确一下几点:
生产者仅仅在仓储未满时候生产,仓满则停止生产。
消费者仅仅在仓储有产品时候才能消费,仓空则等待。
当消费者发现仓储没产品可消费时候会通知生产者生产。
生产者在生产出可消费产品时候,应该通知等待的消费者去消费。
此模型将要结合java.lang.Object的wait与notify、notifyAll方法来实现以上的需求。这是非常重要的。
/** * Java线程:并发协作-生产者消费者模型 */ public class Test { 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); c1.start(); c2.start(); c3.start(); p1.start(); p2.start(); p3.start(); p4.start(); p5.start(); p6.start(); p7.start(); } } /** * 仓库 */ class Godown{ public static final int max_size=100;//最大库存量 public int curnum;//当前库存量 Godown() { } Godown(int curnum){ this.curnum=curnum; } /** * 生产指定数量的产品 */ 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(); } /** * 消费指定数量的产品 */ public synchronized void consume(int neednum){ //测试是否可以消费 while(curnum<neednum){ try { //当前的生产线程等待 wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //满足消费条件,则进行消费,这里简单的更改当前库存 curnum-=neednum; System.out.println("已经消费了" + neednum +"个产品,现仓储量为" + curnum); //唤醒在此对象监视器上等待的所有线程 notifyAll(); } } //生产者 class Producer extends Thread{ private int neednum;//生产产品的数量 private Godown godown;//仓库 Producer(int neednum, Godown godown) { this.neednum = neednum; this.godown = godown; } public void run(){ //生产指定数量的产品 godown.produce(neednum); } } //消费者 class Consumer extends Thread{ private int neednum;//消费产品的数量 private Godown godown;//仓库 Consumer(int neednum, Godown godown) { this.neednum = neednum; this.godown = godown; } public void run(){ //消费指定数量的产品 godown.consume(neednum); } }
执行结果:
已经消费了20个产品,现仓储量为10 已经生产了10个产品,现仓储量为20 已经生产了10个产品,现仓储量为30 已经生产了10个产品,现仓储量为40 要生产的产品数量80超过剩余库存量60,暂时不能执行生产任务! 已经消费了30个产品,现仓储量为10 已经生产了10个产品,现仓储量为20 已经生产了10个产品,现仓储量为30 已经生产了10个产品,现仓储量为40 要生产的产品数量80超过剩余库存量60,暂时不能执行生产任务!
说明:
对于本例,要说明的是当发现不能满足生产或者消费条件的时候,调用对象的wait方法,wait方法的作用是释放当前线程的所获得的锁,并调用对象的notifyAll()方法,通知(唤醒)该对象上其他等待线程,使得其继续执行。这样,整个生产者、消费者线程得以正确的协作执行。
notifyAll() 方法,起到的是一个通知作用,不释放锁,也不获取锁。只是告诉该对象上等待的线程“可以竞争执行了,都醒来去执行吧”。
本例仅仅是生产者消费者模型中最简单的一种表示,本例中,如果消费者消费的仓储量达不到满足,而又没有生产者,则程序会一直处于等待状态,这当然是不对的。实际上可以将此例进行修改,修改为,根据消费驱动生产,同时生产兼顾仓库,如果仓不满就生产,并对每次最大消费量做个限制,这样就不存在此问题了,当然这样的例子更复杂,更难以说明这样一个简单模型。
Java线程:并发协作-死锁
线程发生死锁可能性很小,即使看似可能发生死锁的代码,在运行时发生死锁的可能性也是小之又小。
发生死锁的原因一般是两个对象的锁相互等待造成的。
在《Java线程:线程的同步与锁》部分,简述了死锁的概念与简单例子,但是所给的例子是不完整的,这里给出一个完整的例子。
/** * Java线程:并发协作-死锁 */ public class Test { public static void main(String[] args) { DeadlockRisk dead = new DeadlockRisk(); MyThread t1 = new MyThread(dead, 1, 2); MyThread t2 = new MyThread(dead, 3, 4); MyThread t3 = new MyThread(dead, 5, 6); MyThread t4 = new MyThread(dead, 7, 8); t1.start(); t2.start(); t3.start(); t4.start(); } } class MyThread extends Thread { private DeadlockRisk dead; private int a, b; MyThread(DeadlockRisk dead, int a, int b) { this.dead = dead; this.a = a; this.b = b; } @Override public void run() { dead.read(); dead.write(a, b); } } class DeadlockRisk { private static class Resource { public int value; } private Resource resourceA = new Resource(); private Resource resourceB = new Resource(); public int read() { synchronized (resourceA) { System.out.println("read():" + Thread.currentThread().getName() + "获取了resourceA的锁!"); synchronized (resourceB) { System.out.println("read():" + Thread.currentThread().getName() + "获取了resourceB的锁!"); return resourceB.value + resourceA.value; } } } public void write(int a, int b) { synchronized (resourceB) { System.out.println("write():" + Thread.currentThread().getName() + "获取了resourceA的锁!"); synchronized (resourceA) { System.out.println("write():" + Thread.currentThread().getName() + "获取了resourceB的锁!"); resourceA.value = a; resourceB.value = b; } } } }
执行结果:
read():Thread-1获取了resourceA的锁! read():Thread-1获取了resourceB的锁! write():Thread-1获取了resourceA的锁! write():Thread-1获取了resourceB的锁! read():Thread-3获取了resourceA的锁! read():Thread-3获取了resourceB的锁! write():Thread-3获取了resourceA的锁! write():Thread-3获取了resourceB的锁! read():Thread-2获取了resourceA的锁! read():Thread-2获取了resourceB的锁! write():Thread-2获取了resourceA的锁! write():Thread-2获取了resourceB的锁! read():Thread-0获取了resourceA的锁! read():Thread-0获取了resourceB的锁! write():Thread-0获取了resourceA的锁! write():Thread-0获取了resourceB的锁!