在之前的博客中已经介绍过线程同步通信技术《JAVA 并发-多线程传统线程同步通信技术(4)》,上篇是使用的synchronized,wait,notify来实现,今天我们使用的是Lock和Condition,下面我们结合两者对比来学习。
简单的Lock锁应用:
-
-
-
-
-
- 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();
-
- for(int i=1;i<=50;i++){
- business.main(i);
- }
-
- }
-
- static class Business {
-
- Lock lock = new ReentrantLock();
- Condition condition = lock.newCondition();
-
- private boolean bShouldSub = true;
- public void sub(int i){
- lock.lock();
- try{
-
- 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{
-
- 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{
-
-
- 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):
总结:
对于以上的两种实现线程同步通信的方式,Lock替代了 synchronized 方法和语句的使用,Condition 替代了 Object 监视器方法的使用。而对比来说Lock(本身为一个对象)更加面向对象,Condition可以为多个线程间建立不同的Condition,这样可以控制多个线程之间的运行顺序。