标签:
在之前的博客中已经介绍过线程同步通信技术《JAVA 并发编程-传统线程同步通信技术(四)》,上篇是使用的synchronized,wait,notify来实现,今天我们使用的是Lock和Condition,下面我们结合两者对比来学习。
/** * 简单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() 方法。
虚假唤醒(spuriouswakeup)在采用条件等待时,我们使用的是
while(条件不满足){
condition_wait(cond, mutex);
}
而不是:
If( 条件不满足 ){
Condition_wait(cond,mutex);
}
这是因为可能会存在虚假唤醒”spuriouswakeup”的情况。
也就是说,即使没有线程调用condition_signal,原先调用condition_wait的函数也可能会返回。此时线程被唤醒了,但是条件并不满足,这个时候如果不对条件进行检查而往下执行,就可能会导致后续的处理出现错误。
在系统设计时应该可以避免虚假唤醒,但是这会影响条件变量的执行效率,而既然通过while循环就能避免虚假唤醒造成的错误,因此程序的逻辑就变成了while循环的情况。
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,这样可以控制多个线程之间的运行顺序。
版权声明:本文为博主原创文章,未经博主允许不得转载。
JAVA 并发编程-线程同步通信技术(Lock和Condition)(十)
标签:
原文地址:http://blog.csdn.net/hejingyuan6/article/details/47068305