标签:
Lock比传统的线程模型中的synchronized方式更加面向对象,因为“锁”本身就是一个对象。
两个线程执行的代码要实现同步互斥的效果,他们必须用同一个Lock对象。
读写锁:(1)读锁:多个读锁不互斥,读锁与写锁互斥,这是由jvm自己控制的,我们只需要代码中用对相应的锁即可。如果只读数据,那么可以很多人(线程)同时读,但是不能同时写,此时就加读锁。如果代码需要修改数据,此时只能一个人(一个线程)写,此时不能同时读,那么就加写锁。
总之,读时,上读锁;写时,上写锁。
Condition 将 Object 监视器方法(wait、notify和notifyAll)分解成截然不同的对象,
以便通过将这些对象与任意 Lock 实现组合使用,为每个对象提供多个等待 set(wait-set)。
其中,Lock 替代了synchronized 方法和语句的使用,Condition 替代了 Object 监视器方
法的使用。
条件(也称为条件队列 或条件变量)为线程提供了一个含义,以便在某个状态条件现在可能
为 true的另一个线程通知它之前,一直挂起该线程(即让其“等待”)。因为访问此共享状态信
息发生在不同的线程中,所以它必须受保护,因此要将某种形式的锁与该条件相关联。等待提供一
个条件的主要属性是:以原子方式 释放相关的锁,并挂起当前线程,就像 Object.wait 做的
那样。
Condition 实例实质上被绑定到一个锁上。要为特定 Lock实例获得 Condition 实例,
使用newCondition()方法。
两个线程(主线程、子线程):子线程循环4次,接着主线程循环5,接着又回到子线程循环4
次,接着再回到主线程又循环5,如此循环3次,请写出程序。
代码如下:使用一个Condition对象完成两线程之间的通信
package com.tgb.thread13; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * Condition实现线程同步通信——简单Demo * @author hanxuemin * */ public class ConditionCommunication { public static void main(String[] args) { final Business business = new Business(); /** * 创建一个子线程 */ new Thread( new Runnable() { @Override public void run() { /** * 循环3次,调用sub()方法 */ for(int i=1;i<=3;i++){ business.sub(i); } } } ).start(); for(int i=1;i<=3;i++){ business.main(i); } } static class Business { Lock lock = new ReentrantLock(); //创建锁对象 Condition condition = lock.newCondition(); //创建condition对象 private boolean bShouldSub = true; /** * 子线程调用的方法 * @param i */ public void sub(int i){ lock.lock(); //加锁 try{ //如果标识变量bShouldSub=false,则阻塞线程 while(!bShouldSub){ try { condition.await(); //阻塞该线程,等待。。。 } catch (Exception e) { e.printStackTrace(); } } /** * 循环4次,打印 */ for(int j=1;j<=4;j++){ System.out.println("sub thread sequence of " + j + ",loop of " + i); } bShouldSub = false; //给标识变量bShouldSub=true condition.signal(); //唤醒其他线程 }finally{ lock.unlock(); //释放锁 } } /** * 主线程方法 * @param i */ public void main(int i){ lock.lock(); //加锁 try{ //如果标识变量bShouldSub=true,则阻塞线程 while(bShouldSub){ try { condition.await(); //阻塞该线程,等待。。。 } catch (Exception e) { e.printStackTrace(); } } /** * 循环5次,打印 */ for(int j=1;j<=5;j++){ System.out.println("main thread sequence of " + j + ",loop of " + i); } bShouldSub = true; condition.signal(); //唤醒其他线程 }finally{ lock.unlock();//释放锁 } } } }
Condition的功能类似在传统线程技术中的Object.wait()和Object.natify()的功能,但传统
线程技术实现的互斥只能一个线程单独执行,不能实现线程间通信(也就是说这个线程执行完了通
知另一个线程来执行);而Condition就是解决这个问题的,实现线程间的通信。比如CPU让小弟做
事,小弟说我先歇着并通知大哥,大哥就开始做事。
Condition 将 Object 监视器方法(wait、notify 和notifyAll)分解成截然不同的对象,以
便通过将这些对象与任意 Lock 实现组合使用,为每个对象提供多个等待 set(wait-set)。其
中,Lock替代了 synchronized 方法和语句的使用,Condition 替代了 Object 监视器方法的使
用。
Condition实例实质上被绑定到一个锁上。要为特定 Lock 实例获得Condition 实例,请使用
其 newCondition() 方法。
可阻塞队列,代码:
package com.tgb.thread13; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * 两个Condition实现的可阻塞队列 * @author hanxuemin * */ public class BoundedBuffer { final Lock lock = new ReentrantLock(); //创建锁 final Condition notFullCondition = lock.newCondition(); //写线程条件 final Condition notEmptyCondition = lock.newCondition(); //读线程条件 //创建大小为100的数组,相当于阻塞队列(缓存队列) final Object[] items = new Object[100]; int putptr; //写索引 int takeptr; //读索引 int count; //队列中存在的数据个数 /** * 往数组items中存放数据的方法 * @param x 存放的数据 * @throws InterruptedException */ public void put(Object x) throws InterruptedException{ lock.lock(); try { while (count == items.length) { //如果队列已满,则阻塞写线程 notFullCondition.await(); //写线程阻塞 } /** * 以下四步为一大步 * 1,存值 * 2,判断写索引,如果是队列最后一个位置,则置为0 * 3,数据个数++ * 4,唤醒读线程 * */ items[putptr] = x; //数组items中存入数据x if(++putptr == items.length) putptr = 0; //如果写索引写到队列的最后一个位置了,那么置为0 ++count; //同时数据个数++ notEmptyCondition.signal(); //唤醒读线程 }finally{ lock.unlock(); //释放锁 } } /** * 从数组items中取数据 * @return items中取出的数据 * @throws InterruptedException */ public Object take() throws InterruptedException{ lock.lock(); try { while (count == 0) { //如果队列为空 notEmptyCondition.await();//阻塞读线程 } /** * 以下四步为一大步 * 1,取值 * 2,判断读索引,如果是队列最后一个位置,则置为0 * 3,数据个数-- * 4,唤醒写线程 * */ Object x = items[takeptr]; //取值 if(++takeptr == items.length) takeptr = 0; //如果读索引读到队列的最后一个位置,则置为0 --count; //同时数据个数-- notFullCondition.signal();//唤醒写线程 return x; }finally{ lock.unlock(); //释放锁 } } }
这是一个处于多线程工作环境下的缓存区,缓存区提供了两个方法,put和take,put是存数
据,take是取数据,内部有个缓存队列,具体变量和方法说明见代码,这个缓存区类实现的功能:
有多个线程往里面存数据和从里面取数据,其缓存队列(先进先出后进后出)能缓存的最大数值
是100,多个线程间是互斥的,当缓存队列中存储的值达到100时,将写线程阻塞,并唤醒读线程,
当缓存队列中存储的值为0时,将读线程阻塞,并唤醒写线程,下面分析一下代码的执行过程:
1.一个写线程执行,调用put方法;
2.判断count是否为100,显然没有100;
3.继续执行,存入值;
4.判断当前写入的索引位置++后,是否和100相等,相等将写入索引值变为0,并将count+1;
5.仅唤醒读线程阻塞队列中的一个;
6.一个读线程执行,调用take方法;
7. ……
8.仅唤醒写线程阻塞队列中的一个。
这就是多个Condition的强大之处,假设缓存队列中已经存满,那么阻塞的肯定是写线程,唤
醒的肯定是读线程,相反,阻塞的肯定是读线程,唤醒的肯定是写线程,那么假设只有一
个Condition会有什么效果呢,缓存队列中已经存满,这个Lock不知道唤醒的是读线程还是写线程
了,如果唤醒的是读线程,皆大欢喜,如果唤醒的是写线程,那么线程刚被唤醒,又被阻塞了,这
时又去唤醒,这样就浪费了很多时间。
子线程二循环10次,子线程三循环5次,接着主线程循环15,接着又回到子线程二循环10次,
子线程三循环5次,接着再回到主线程又循环15,如此循环20次,请写出程序。
——子线程二执行完了,唤醒子线程三;子线程三执行完了,唤醒主线程;主线程执行完了,唤醒
子线程二。并且子线程二只能唤醒子线程三,子线程三只能唤醒主线程,主线程只能唤醒子线二。
package com.tgb.thread13; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * Lock&Condition三个线程彼此同步通信 * * @author hanxuemin * */ public class ThreeConditionCommunication { public static void main(String[] args) { final Business business = new Business(); /** * 出主线程外,再创建两个线程 */ // 创建一个子线程2,循环次,调用sub2()方法 new Thread(new Runnable() { @Override public void run() { /** * 循环20次调用sub2()方法 */ for (int i = 1; i <= 20; i++) { business.sub2(i); // 调用sub2()方法 } } }).start(); // 再创建一个子线程3,循环50次,调用sub3()方法 new Thread(new Runnable() { @Override public void run() { /** * 循环20次调用sub3()方法 */ for (int i = 1; i <= 20; i++) { business.sub3(i); // 调用sub3()方法 } } }).start(); /** * 主线程 循环20次,调用main()方法 */ for (int i = 1; i <= 20; i++) { business.main(i); } } /** * * @author hanxuemin * */ static class Business { Lock lock = new ReentrantLock(); Condition condition1 = lock.newCondition(); // 线程条件1 Condition condition2 = lock.newCondition(); // 线程条件2 Condition condition3 = lock.newCondition(); // 线程条件3 private int shouldSub = 1; // 标识符变量,初始值为1;当值为1时主线程执行,子线程2和子线程3等待; // 当值为2时子线程2执行,其他线程等待;当值为3时子线程3执行,其他线程等待 /** * 子线程2调用的方法 * * @param i */ public void sub2(int i) { lock.lock(); // 加锁 try { while (shouldSub != 2) { // 当标识符变量!=2时,子线程2阻塞,处于等待状态 try { condition2.await(); } catch (Exception e) { e.printStackTrace(); } } /** * 循环10次,打印当前线程信息(调用此方法的为子线程2,即打印子线程2的信息) */ for (int j = 1; j <= 10; j++) { System.out.println("sub2 thread sequence of " + j + ",loop of " + i); } shouldSub = 3; // 给标识符变量赋值为3 condition3.signal(); // 唤醒子线程3 } finally { lock.unlock(); // 释放锁 } } /** * 子线程3调用的方法 * * @param i */ public void sub3(int i) { lock.lock(); // 加锁 try { while (shouldSub != 3) { // 当标识符变量!=3时,子线程3阻塞,处于等待状态 try { condition3.await(); } catch (Exception e) { e.printStackTrace(); } } /** * 循环5次,打印当前线程信息(调用此方法的为子线程3,即打印子线程3的信息) */ for (int j = 1; j <= 5; j++) { System.out.println("sub3 thread sequence of " + j + ",loop of " + i); } shouldSub = 1; // 给标识符变量赋值为1 condition1.signal(); // 唤醒主线程 } finally { lock.unlock(); // 释放锁 } } /** * 主线程调用的方法 * * @param i */ public void main(int i) { lock.lock(); // 加锁 try { while (shouldSub != 1) { // 当标识符变量!=1时,子线程1阻塞,处于等待状态 try { condition1.await(); } catch (Exception e) { e.printStackTrace(); } } /** * 循环15次,打印当前线程信息(调用此方法的为主线程,即打印主线程的信息) */ for (int j = 1; j <= 15; j++) { System.out.println("main thread sequence of " + j + ",loop of " + i); } shouldSub = 2; // 给标识符变量赋值为2 condition2.signal(); // 唤醒子线程2 } finally { lock.unlock(); // 释放锁 } } } }
(1) Condition实例实质上被绑定到一个锁上。要为特定 Lock 实例获得 Condition 实例,请使用其newCondition() 方法。
(2)Condition解决了线程间的通信问题。
(3)一个锁可以有多个Condition,每个条件上可以有多个线程等待,通过调用await()方法,可以让线程在该条件下等待。当调用signal()方法,又可以唤醒该条件下的等待的其他线程。
版权声明:本文为博主原创文章,未经博主允许不得转载。
标签:
原文地址:http://blog.csdn.net/hanxuemin12345/article/details/47171001