JAVA 并发编程-线程同步通信技术(Lock和Condition)(十)


    在之前的博客中已经介绍过线程同步通信技术《JAVA 并发编程-传统线程同步通信技术(四)》,上篇是使用的synchronized,waitnotify来实现,今天我们使用的是LockCondition,下面我们结合两者对比来学习。

 

简单的Lock锁应用:


/**
 * 简单Lock的应用
 * @author hejingyuan
 *
 */
public class LockTest {

	public static void main(String[] args) {
		new LockTest().init();
	}
	
	private void init(){
		final Outputer outputer = new Outputer();
		new Thread(new Runnable(){
			@Override
			public void run() {
				while(true){
					try {
						Thread.sleep(10);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					outputer.output("zhangxiaoxiang");
				}
				
			}
		}).start();
		
		new Thread(new Runnable(){
			@Override
			public void run() {
				while(true){
					try {
						Thread.sleep(10);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					outputer.output("lihuoming");
				}
				
			}
		}).start();
		
	}

	static class Outputer{
		Lock lock = new ReentrantLock();
		public void output(String name){
			int len = name.length();
			lock.lock();
			try{
				for(int i=0;i<len;i++){
					System.out.print(name.charAt(i));
				}
				System.out.println();
			}finally{
				lock.unlock();
			}
		}
	}
}


Lock比传统线程模型中的Synchronied方式更加面向对象,与生活中的锁类似,锁本身也应该是一个对象.两个线程执行的代码段要实现同步互斥的效果,它们必须用同一个Lock对象,锁是在代表要操作的资源的类的内部方法中,而不是线程代码中.

 

注意:

 

和synchronized不同的是,在线程执行完以后,要关闭锁unlock(),如果不关闭,其他在等待的线程就永远被锁在外面了。因为synchronized是在JVM层面实现的,因此系统可以监控锁的释放与否,而ReentrantLock使用代码实现的,系统无法自动释放锁,需要在代码中finally子句中显式释放锁lock.unlock();

 

 

结合前篇博客进行对比(同步通信):

 

实现效果:子线程循环10次,接着主线程循环100次,又回到子线程循环10次,接着再回到主线程又循环100次,如此循环50次


public class ConditionCommunication {

	public static void main(String[] args) {
		
		final Business business = new Business();
		//创建了一个线程,并启动
		new Thread(
				new Runnable() {
					
					@Override
					public void run() {					
						for(int i=1;i<=50;i++){
							business.sub(i);
						}						
					}
				}
		).start();
		//因为mian方法本身就占用一个线程,所以主线程不需要再new Thread
		for(int i=1;i<=50;i++){
			business.main(i);
		}
		
	}

	static class Business {
		
		  Lock lock = new ReentrantLock();
		  Condition condition = lock.newCondition();
		  //决定是main执行还是sub执行
		  private boolean bShouldSub = true;
		  public  void sub(int i){
			  lock.lock();// 锁住了别的线程就不能进来了,包括下面的main()因为他们用的是同一把锁
			  try{
				  //bShouldSub==false时等待
				  while(!bShouldSub){
					  try {
						condition.await();
					} catch (Exception e) {
						e.printStackTrace();
					}
				  }
					for(int j=1;j<=10;j++){
						System.out.println("sub thread sequence of " + j + ",loop of " + i);
					}
				  bShouldSub = false;
				  condition.signal();
			  }finally{
				  lock.unlock();
			  }
		  }
		  
		  public  void main(int i){
			  lock.lock();
			  try{
				 //bShouldSub==true时等待
				 while(bShouldSub){
				  		try {
							condition.await();
						} catch (Exception e) {
							e.printStackTrace();
						}
				  	}
					for(int j=1;j<=100;j++){
						System.out.println("main thread sequence of " + j + ",loop of " + i);
					}
					bShouldSub = true;
					condition.signal();
		  }finally{
			//如果中途抛出异常,那么这把锁就没有被解锁,别人就进不来了
			//所以写在finally里面
			  lock.unlock();
		  }
	  }
	
	}
}

在Condition中,用await()替换wait(),用signal()替换notify(),用signalAll()替换notifyAll(),传统线程的通信方式,Condition都可以实现。

Condition实例实质上被绑定到一个锁上。要为特定Lock实例获得Condition 实例,请使用其newCondition() 方法。

 

注意:在等待Condition时,可能会发生"虚假唤醒"。

 

虚假唤醒(spuriouswakeup)在采用条件等待时,我们使用的是

 

while(条件不满足){ 

   condition_wait(cond, mutex); 

而不是: 

If( 条件不满足 ){ 

   Condition_wait(cond,mutex); 

}  

 

这是因为可能会存在虚假唤醒”spuriouswakeup”的情况。

也就是说,即使没有线程调用condition_signal,原先调用condition_wait的函数也可能会返回。此时线程被唤醒了,但是条件并不满足,这个时候如果不对条件进行检查而往下执行,就可能会导致后续的处理出现错误。

 

在系统设计时应该可以避免虚假唤醒,但是这会影响条件变量的执行效率,而既然通过while循环就能避免虚假唤醒造成的错误,因此程序的逻辑就变成了while循环的情况。

 

Condition可以控制多个线程之间的运行顺序 


Condition和传统的线程通信没什么区别,Condition的强大之处在于它可以为多个线程间建立不同的Condition,这样可以控制多个线程之间的运行顺序。

 

例如:

 

应当先对线程1、线程2,建 condition对象 1: c_th1,对象 2: c_th2;

c_th1.await()   // 阻塞写线程1

c_th2.signal()   // 唤醒读线程2

-------

否则,多线程时,函数没有参数时,如何指定阻塞哪个,唤醒哪个呢,线程可能多于2个,总要有方法指定。

 

实现demo(有三个线程,想让线程1运行完以后运行线程2,线程2运行完以后运行线程3,线程3运行完以后又运行线程1):


public class ThreeConditionCommunication {

	public static void main(String[] args) {
		
		final Business business = new Business();
		new Thread(
				new Runnable() {
					
					@Override
					public void run() {
					
						for(int i=1;i<=50;i++){
							business.sub2(i);
						}
						
					}
				}
		).start();
		
		new Thread(
				new Runnable() {
					
					@Override
					public void run() {
					
						for(int i=1;i<=50;i++){
							business.sub3(i);
						}
						
					}
				}
		).start();		
		
		for(int i=1;i<=50;i++){
			business.main(i);
		}
		
	}

	static class Business {
		  Lock lock = new ReentrantLock();
		  Condition condition1 = lock.newCondition();
		  Condition condition2 = lock.newCondition();
		  Condition condition3 = lock.newCondition();
		  private int shouldSub = 1;
		  public  void sub2(int i){
			  lock.lock();
			  try{
				  while(shouldSub != 2){
					  try {
						condition2.await();
					} catch (Exception e) {
						e.printStackTrace();
					}
				  }
					for(int j=1;j<=10;j++){
						System.out.println("sub2 thread sequence of " + j + ",loop of " + i);
					}
				  shouldSub = 3;
				  condition3.signal();
			  }finally{
				  lock.unlock();
			  }
		  }

		  public  void sub3(int i){
			  lock.lock();
			  try{
				  while(shouldSub != 3){
					  try {
						condition3.await();
					} catch (Exception e) {
						e.printStackTrace();
					}
				  }
					for(int j=1;j<=20;j++){
						System.out.println("sub3 thread sequence of " + j + ",loop of " + i);
					}
				  shouldSub = 1;
				  condition1.signal();
			  }finally{
				  lock.unlock();
			  }
		  }		  
		  
		  public  void main(int i){
			  lock.lock();
			  try{
				 while(shouldSub != 1){
				  		try {
							condition1.await();
						} catch (Exception e) {
							e.printStackTrace();
						}
				  	}
					for(int j=1;j<=100;j++){
						System.out.println("main thread sequence of " + j + ",loop of " + i);
					}
					shouldSub = 2;
					condition2.signal();
		  }finally{
			  lock.unlock();
		  }
	  }
	
	}
}

总结:

 

对于以上的两种实现线程同步通信的方式,Lock替代了 synchronized 方法和语句的使用,Condition 替代了 Object 监视器方法的使用。而对比来说Lock(本身为一个对象)更加面向对象,Condition可以为多个线程间建立不同的Condition,这样可以控制多个线程之间的运行顺序。




评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值