- 浏览: 9082 次
- 性别:
- 来自: 北京
文章分类
最新评论
我今天要分析的问题很简单,使用三个线程,要求线程1打印“AAA”,然后线程2打印“BBB”,然后线程3打印“CCC”,重复10次。
使用 java api。用尽量多的方式去实现以上功能。
今天先上第一种吧。完全基于块的synchronized。
这种写法在效率上不好的一点是即使thx在 id == lock.target的判断中失败,这个thx还是会在while循环中恬不知耻的继续去试图占有锁,而在 lock.target在被其他线程改写之前,thx的这种尝试是必然失败的,所以一个改进就是在 id = lock.target失败后再写一个分支,让thx在对象lock上wait,同时释放锁,让那些可能会在当前状况下打印的线程去占有锁,这个 right thread 通过了 id == lock.target的判断后,执行完操作,就唤醒在lock上wait的线程,因为现在的lock.target已经改变,其他线程可以去竞争这把锁了。
代码如下
好了,以上就是synchronized块的使用,它的好处自然是明显的,就是simplicity和safety,synchronized是隐式的获取对象的监视器所以是简单的,并可以主动释放所以是安全的。
当然,synchronized(obj) 有不好的地方,它只有一组java.lang.Object实现的监视器方法,wait(),notify(),natifyAll(),而且释放必须在同一个程序块内。
好了,以上的废话是为了引出java api中另外的同步方式:
java.util.concurrent.locks.Condition
java.util.concurrent.locks.Lock
java.util.concurrent.locks.ReentrantLock
ReentrantLock 可重入锁,Condition是定义在锁上“监视器方法”,被称为条件,类似于wait(),notify,notifyAll,但是Condition可以有多个(在一个锁上)。
详细的可以参照java api文档。
相信大家很容易理解这个部分的api,下面给出一个错误的想实现本文target的代码,如果想检测自己concurrent编程能力的朋友可以自己阅读来差错,不愿意读的可以简略跳过,看说明就好
说明:以上代码运行的结果实际上是不可控的,(或多或少打印部分内容,然后发生死锁,程序不能正常退出),原因在于什么呢?
请看code中存在a.await(),b,await(),c.await(),想象一下,因为jvm对每个线程调度是随机的,那么如果在某次 x.signal()被调用的时候,x.await()线程还没被jvm所调度到,然后在x.await()被jvm调度到的时候,唯一可以唤醒它的x.signal()已经过去了,那么x.await()就醒不来了,一但jvm随机的发生对本例子的线程灾难性的调度顺序,那么就发生了deadlock。
怎么去避开这个问题呢,请记住一般x.await()要放在条件判断语句中,比如
while(somecondition),if(somecondition),因为signal就像一个流星,它对本进程的影响只在于它被调用的一瞬,因为调度问题如果没有被捕捉到,就永远的走了,所以signal线程还必须去修改一个somecondition,把signal曾经活过的信息保留下来,让await线程可以判断somecondition来决定是不是该await,从而避免了死锁,更细粒化的控制了线程的concurrent。
好了以下就是在x.await()的判断,来作为本文的对“AAABBBCCC....”打印的第三种正确实现。
以下是第四种方法基于mutex的Semaphore,使用的是 java.util.concurrent.Semaphore API。
对于Semaphore和线程锁,个人的感觉是别混用,否则很容易出现死锁,原因是sem.acquire()会阻塞线程但是不释放线程锁,使其他sem.release()的线程得不到线程锁,从而发生死锁,个人觉得如果在确定使用Semaphore的java线程要实现“线程锁”的功能可以使用一个Semaphore mutex,mutex的值为0,1。
到此 这个问题应该是要截稿了。。好了 鞠躬 下台。。。
使用 java api。用尽量多的方式去实现以上功能。
今天先上第一种吧。完全基于块的synchronized。
package learn; class MyLock{ int target = 0; String strprint[] = {"AAA", "BBB", "CCC"}; } class MethodToPrint implements Runnable{ private int id, count = 10; private MyLock lock; MethodToPrint(int id, MyLock lock){ this.id = id; this.lock = lock; } public void run(){ while(count > 0){ synchronized(lock){ if(id == lock.target){ count--; System.out.println(lock.strprint[id]); lock.target = (lock.target +1) % 3; } } } } } class Go{ public static void main(String args[]){ MyLock lock = new MyLock(); Thread th1 = new Thread( new MethodToPrint(0, lock)); Thread th2 = new Thread( new MethodToPrint(1, lock)); Thread th3 = new Thread( new MethodToPrint(2, lock)); th1.start(); th2.start(); th3.start(); } }
这种写法在效率上不好的一点是即使thx在 id == lock.target的判断中失败,这个thx还是会在while循环中恬不知耻的继续去试图占有锁,而在 lock.target在被其他线程改写之前,thx的这种尝试是必然失败的,所以一个改进就是在 id = lock.target失败后再写一个分支,让thx在对象lock上wait,同时释放锁,让那些可能会在当前状况下打印的线程去占有锁,这个 right thread 通过了 id == lock.target的判断后,执行完操作,就唤醒在lock上wait的线程,因为现在的lock.target已经改变,其他线程可以去竞争这把锁了。
代码如下
package learn; class MyLock{ int target = 0; String strprint[] = {"AAA", "BBB", "CCC"}; } class MethodToPrint implements Runnable{ private int id, count = 10; private MyLock lock; MethodToPrint(int id, MyLock lock){ this.id = id; this.lock = lock; } public void run(){ while(count > 0){ synchronized(lock){ if(id == lock.target){ count--; System.out.println(lock.strprint[id]); lock.target = (lock.target +1) % 3; lock.notifyAll(); } else{ try{ lock.wait(); }catch(InterruptedException e){ throw new RuntimeException(e); } } } } } } class Go{ public static void main(String args[]){ MyLock lock = new MyLock(); Thread th1 = new Thread( new MethodToPrint(0, lock)); Thread th2 = new Thread( new MethodToPrint(1, lock)); Thread th3 = new Thread( new MethodToPrint(2, lock)); th1.start(); th2.start(); th3.start(); } }
好了,以上就是synchronized块的使用,它的好处自然是明显的,就是simplicity和safety,synchronized是隐式的获取对象的监视器所以是简单的,并可以主动释放所以是安全的。
当然,synchronized(obj) 有不好的地方,它只有一组java.lang.Object实现的监视器方法,wait(),notify(),natifyAll(),而且释放必须在同一个程序块内。
好了,以上的废话是为了引出java api中另外的同步方式:
java.util.concurrent.locks.Condition
java.util.concurrent.locks.Lock
java.util.concurrent.locks.ReentrantLock
ReentrantLock 可重入锁,Condition是定义在锁上“监视器方法”,被称为条件,类似于wait(),notify,notifyAll,但是Condition可以有多个(在一个锁上)。
详细的可以参照java api文档。
相信大家很容易理解这个部分的api,下面给出一个错误的想实现本文target的代码,如果想检测自己concurrent编程能力的朋友可以自己阅读来差错,不愿意读的可以简略跳过,看说明就好
package learn; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.Condition; import java.util.Scanner; class PrintA implements Runnable{ Condition a, b; Lock lock; int count = 10; PrintA(Lock lock, Condition a, Condition b){ this.lock = lock; this.a = a; this.b = b; } public void run(){ while(count > 0){ lock.lock(); try{ a.await(); count--; System.out.println("AAA"); b.signal(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); }finally{ lock.unlock(); } } } } class PrintB implements Runnable{ Condition b, c; Lock lock; int count = 10; PrintB(Lock lock, Condition b, Condition c){ this.b = b; this.c = c; this.lock = lock; } public void run(){ while(count > 0){ lock.lock(); try{ b.await(); count--; System.out.println("BBB"); c.signal(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); }finally{ lock.unlock(); } } } } class PrintC implements Runnable{ Condition c, a; Lock lock; int count = 10; PrintC(Lock lock, Condition c, Condition a){ this.c = c; this.a = a; this.lock = lock; } public void run(){ while(count > 0){ lock.lock(); try{ c.await(); count--; System.out.println("CCC"); a.signal(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); }finally{ lock.unlock(); } } } } class Go{ public static void main(String args[]){ Lock lock = new ReentrantLock(); Condition a = lock.newCondition(); Condition b = lock.newCondition(); Condition c = lock.newCondition(); Thread th1 = new Thread(new PrintA(lock, a, b)); Thread th2 = new Thread(new PrintB(lock, b, c)); Thread th3 = new Thread(new PrintC(lock, c, a)); th1.start(); th2.start(); th3.start(); Scanner s = new Scanner(System.in); int i = s.nextInt(); if(i == 0){ lock.lock(); try{ a.signal(); }finally{ lock.unlock(); } } } }
说明:以上代码运行的结果实际上是不可控的,(或多或少打印部分内容,然后发生死锁,程序不能正常退出),原因在于什么呢?
请看code中存在a.await(),b,await(),c.await(),想象一下,因为jvm对每个线程调度是随机的,那么如果在某次 x.signal()被调用的时候,x.await()线程还没被jvm所调度到,然后在x.await()被jvm调度到的时候,唯一可以唤醒它的x.signal()已经过去了,那么x.await()就醒不来了,一但jvm随机的发生对本例子的线程灾难性的调度顺序,那么就发生了deadlock。
怎么去避开这个问题呢,请记住一般x.await()要放在条件判断语句中,比如
while(somecondition),if(somecondition),因为signal就像一个流星,它对本进程的影响只在于它被调用的一瞬,因为调度问题如果没有被捕捉到,就永远的走了,所以signal线程还必须去修改一个somecondition,把signal曾经活过的信息保留下来,让await线程可以判断somecondition来决定是不是该await,从而避免了死锁,更细粒化的控制了线程的concurrent。
好了以下就是在x.await()的判断,来作为本文的对“AAABBBCCC....”打印的第三种正确实现。
package learn; import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.Condition; class ID{ static int id = 1; } class PrintA implements Runnable{ Lock lock; Condition a,b; int count = 10; PrintA(Lock lock, Condition a, Condition b){ this.lock = lock; this.a = a; this.b = b; } public void run(){ while(count > 0){ try{ lock.lock(); while(ID.id != 1){ try{ a.await(); }catch(InterruptedException e){ throw new RuntimeException(e); } } count--; ID.id = 2; b.signal(); System.out.println("AAA"); }finally{ lock.unlock(); } } } } class PrintB implements Runnable{ Lock lock; Condition b, c; int count = 10; PrintB(Lock lock, Condition b, Condition c){ this.lock = lock; this.b = b; this.c = c; } public void run(){ while(count > 0){ try{ lock.lock(); while(ID.id != 2){ try{ b.await(); }catch(InterruptedException e){ throw new RuntimeException(e); } } count--; ID.id = 3; c.signal(); System.out.println("BBB"); }finally{ lock.unlock(); } } } } class PrintC implements Runnable{ Lock lock; Condition c, a; int count = 10; PrintC(Lock lock, Condition c, Condition a){ this.lock = lock; this.c = c; this.a = a; } public void run(){ while(count > 0){ try{ lock.lock(); while(ID.id != 3){ try{ c.await(); }catch(InterruptedException e){ throw new RuntimeException(e); } } count--; ID.id = 1; a.signal(); System.out.println("CCC"); }finally{ lock.unlock(); } } } } class Go{ public static void main(String args[]){ Lock lock = new ReentrantLock(); Condition a = lock.newCondition(); Condition b = lock.newCondition(); Condition c = lock.newCondition(); Thread th1 = new Thread(new PrintA(lock, a, b)); Thread th2 = new Thread(new PrintB(lock, b, c)); Thread th3 = new Thread(new PrintC(lock, c, a)); th1.start(); th2.start(); th3.start(); } }
以下是第四种方法基于mutex的Semaphore,使用的是 java.util.concurrent.Semaphore API。
package learn; import java.util.concurrent.Semaphore; class PrintSome implements Runnable{ Semaphore mutexNeeded, mutexToRelease; String str; PrintSome(Semaphore mutexNeeded, Semaphore mutexToRelease, String str){ this.mutexNeeded = mutexNeeded; this.mutexToRelease = mutexToRelease; this.str = str; } public void run(){ int count = 10; while(count-- > 0){ try{ mutexNeeded.acquire(); }catch(InterruptedException e){ throw new RuntimeException(e); } System.out.println(str); mutexToRelease.release(); } } } class Go{ public static void main(String args[]){ Semaphore mutexA = new Semaphore(1), mutexB = new Semaphore(0), mutexC = new Semaphore(0); new Thread( new PrintSome(mutexA, mutexB, "AAA")).start(); new Thread( new PrintSome(mutexB, mutexC, "BBB")).start(); new Thread( new PrintSome(mutexC, mutexA, "CCC")).start(); } }
对于Semaphore和线程锁,个人的感觉是别混用,否则很容易出现死锁,原因是sem.acquire()会阻塞线程但是不释放线程锁,使其他sem.release()的线程得不到线程锁,从而发生死锁,个人觉得如果在确定使用Semaphore的java线程要实现“线程锁”的功能可以使用一个Semaphore mutex,mutex的值为0,1。
到此 这个问题应该是要截稿了。。好了 鞠躬 下台。。。
相关推荐
本章的学习目标 了解进程和线程的基本概念和区别 掌握创建线程的两种方法 掌握线程同步的概念和方法 了解线程的优先级 掌握线程间通信的方法 第3页 Java程序设计案例教程-第8章-多线程编程全文共36页,当前为第3页...
浏览器就是一个很好的多线程的例子,在浏览器中你可以在下载JAVA小应用程序或图象的同时滚动页面,在访问新页面时,播放动画和声音,打印文件等。 多线程的好处在于可以提高CPU的利用率——任何一个程序员都不希望...
浏览器就是一个很好的多线程的例子,在浏览器中你可以在下载JAVA小应用程序或图象的同时滚动页面,在访问新页面时,播放动画和声音,打印文件等。 多线程的好处在于可以提高CPU的利用率——任何一个程序员都不希望...
Java局域网通信——飞鸽传书源代码,大家都知道VB版、VC版还有Delphi版的飞鸽传书软件,但是Java版的确实不多,因此这个Java文件传输实例不可错过,Java网络编程技能的提升很有帮助。 Java聊天程序,包括服务端和...
Java局域网通信——飞鸽传书源代码,大家都知道VB版、VC版还有Delphi版的飞鸽传书软件,但是Java版的确实不多,因此这个Java文件传输实例不可错过,Java网络编程技能的提升很有帮助。 Java聊天程序,包括服务端和...
Java局域网通信——飞鸽传书源代码,大家都知道VB版、VC版还有Delphi版的飞鸽传书软件,但是Java版的确实不多,因此这个Java文件传输实例不可错过,Java网络编程技能的提升很有帮助。 Java聊天程序,包括服务端和...
第9章Java多线程机制 9 1Java中的线程 9 2Thread类的子类创建线程 9 3使用Runnable接口 9 4线程的常用方法 9 5GUI线程 9 6线程同步 9 7在同步方法中使用wait notif 和nodf3 All 方法 9 8挂起 恢复和终止线程 9 9计时...
Java局域网通信——飞鸽传书源代码,大家都知道VB版、VC版还有Delphi版的飞鸽传书软件,但是Java版的确实不多,因此这个Java文件传输实例不可错过,Java网络编程技能的提升很有帮助。 Java聊天程序,包括服务端和...
030902_【第9章:多线程】_线程常用操作方法笔记.pdf 030903_〖第9章:多线程〗_线程操作范例笔记.pdf 030904_【第9章:多线程】_同步与死锁笔记.pdf 030905_【第9章:多线程】_线程操作案例——生产者和消费者笔记....
Java局域网通信——飞鸽传书源代码,大家都知道VB版、VC版还有Delphi版的飞鸽传书软件,但是Java版的确实不多,因此这个Java文件传输实例不可错过,Java网络编程技能的提升很有帮助。 Java聊天程序,包括服务端和...
1.9 多线程 1.10 永久性 1.11 Java和因特网 1.11.1 什么是Web? 1.11.2 客户端编程 1.11.3 服务器端编程 1.11.4 一个独立的领域:应用程序 1.12 分析和设计 1.12.1 不要迷失 1.12.2 阶段0:拟出一个计划 1.12.3 阶段...
1.9 多线程 1.10 永久性 1.11 Java和因特网 1.11.1 什么是Web? 1.11.2 客户端编程 1.11.3 服务器端编程 1.11.4 一个独立的领域:应用程序 1.12 分析和设计 1.12.1 不要迷失 1.12.2 阶段0:拟出一个计划 1.12.3 阶段...
1.9 多线程 1.10 永久性 1.11 Java和因特网 1.11.1 什么是Web? 1.11.2 客户端编程 1.11.3 服务器端编程 1.11.4 一个独立的领域:应用程序 1.12 分析和设计 1.12.1 不要迷失 1.12.2 阶段0:拟出一个计划 1.12.3 阶段...
Java局域网通信——飞鸽传书源代码,大家都知道VB版、VC版还有Delphi版的飞鸽传书软件,但是Java版的确实不多,因此这个Java文件传输实例不可错过,Java网络编程技能的提升很有帮助。 Java聊天程序,包括服务端和...
1.9 多线程 1.10 永久性 1.11 Java和因特网 1.11.1 什么是Web? 1.11.2 客户端编程 1.11.3 服务器端编程 1.11.4 一个独立的领域:应用程序 1.12 分析和设计 1.12.1 不要迷失 1.12.2 阶段0:拟出一个计划 1.12.3 阶段...
谈谈java多线程 23.谈谈文件加密技术 24.软件开发生命周期 25.路由协议种类及特点 26.java的awt和swing组件的GUI设计的关键 27.对于java流的认识 28.简单描述一下awt与swing区别。 29.简述java编程中事件处理...
1.9 多线程 1.10 永久性 1.11 Java和因特网 1.11.1 什么是Web? 1.11.2 客户端编程 1.11.3 服务器端编程 1.11.4 一个独立的领域:应用程序 1.12 分析和设计 1.12.1 不要迷失 1.12.2 阶段0:拟出一个计划 1.12.3 阶段...
1.9 多线程 1.10 永久性 1.11 Java和因特网 1.11.1 什么是Web? 1.11.2 客户端编程 1.11.3 服务器端编程 1.11.4 一个独立的领域:应用程序 1.12 分析和设计 1.12.1 不要迷失 1.12.2 阶段0:拟出一个计划 1.12.3 阶段...