`

Java 多线程 打印的控制方法

 
阅读更多
我今天要分析的问题很简单,使用三个线程,要求线程1打印“AAA”,然后线程2打印“BBB”,然后线程3打印“CCC”,重复10次。

使用 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。
到此 这个问题应该是要截稿了。。好了 鞠躬 下台。。。

分享到:
评论

相关推荐

    Java程序设计案例教程-第8章-多线程编程.pptx

    本章的学习目标 了解进程和线程的基本概念和区别 掌握创建线程的两种方法 掌握线程同步的概念和方法 了解线程的优先级 掌握线程间通信的方法 第3页 Java程序设计案例教程-第8章-多线程编程全文共36页,当前为第3页...

    多线程操作实例源码,,

    浏览器就是一个很好的多线程的例子,在浏览器中你可以在下载JAVA小应用程序或图象的同时滚动页面,在访问新页面时,播放动画和声音,打印文件等。  多线程的好处在于可以提高CPU的利用率——任何一个程序员都不希望...

    多线程操作实例源码

    浏览器就是一个很好的多线程的例子,在浏览器中你可以在下载JAVA小应用程序或图象的同时滚动页面,在访问新页面时,播放动画和声音,打印文件等。  多线程的好处在于可以提高CPU的利用率——任何一个程序员都不希望...

    java源码包---java 源码 大量 实例

     Java局域网通信——飞鸽传书源代码,大家都知道VB版、VC版还有Delphi版的飞鸽传书软件,但是Java版的确实不多,因此这个Java文件传输实例不可错过,Java网络编程技能的提升很有帮助。 Java聊天程序,包括服务端和...

    JAVA上百实例源码以及开源项目

     Java局域网通信——飞鸽传书源代码,大家都知道VB版、VC版还有Delphi版的飞鸽传书软件,但是Java版的确实不多,因此这个Java文件传输实例不可错过,Java网络编程技能的提升很有帮助。 Java聊天程序,包括服务端和...

    JAVA上百实例源码以及开源项目源代码

     Java局域网通信——飞鸽传书源代码,大家都知道VB版、VC版还有Delphi版的飞鸽传书软件,但是Java版的确实不多,因此这个Java文件传输实例不可错过,Java网络编程技能的提升很有帮助。 Java聊天程序,包括服务端和...

    Java2实用教程.rar

    第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源码包4

     Java局域网通信——飞鸽传书源代码,大家都知道VB版、VC版还有Delphi版的飞鸽传书软件,但是Java版的确实不多,因此这个Java文件传输实例不可错过,Java网络编程技能的提升很有帮助。 Java聊天程序,包括服务端和...

    Java开发详解.zip

    030902_【第9章:多线程】_线程常用操作方法笔记.pdf 030903_〖第9章:多线程〗_线程操作范例笔记.pdf 030904_【第9章:多线程】_同步与死锁笔记.pdf 030905_【第9章:多线程】_线程操作案例——生产者和消费者笔记....

    java源码包3

     Java局域网通信——飞鸽传书源代码,大家都知道VB版、VC版还有Delphi版的飞鸽传书软件,但是Java版的确实不多,因此这个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初学者入门教学

    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联想(中文)

    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源码包2

     Java局域网通信——飞鸽传书源代码,大家都知道VB版、VC版还有Delphi版的飞鸽传书软件,但是Java版的确实不多,因此这个Java文件传输实例不可错过,Java网络编程技能的提升很有帮助。 Java聊天程序,包括服务端和...

    JAVA_Thinking in 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面试题最全集

    谈谈java多线程 23.谈谈文件加密技术 24.软件开发生命周期 25.路由协议种类及特点 26.java的awt和swing组件的GUI设计的关键 27.对于java流的认识 28.简单描述一下awt与swing区别。 29.简述java编程中事件处理...

    Thinking in 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 阶段...

    Thinking in 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 阶段...

Global site tag (gtag.js) - Google Analytics