假如你现在还在为自己的技术担忧,假如你现在想提升自己的工资,假如你想在职场上获得更多的话语权,假如你想顺利的度过35岁这个魔咒,假如你想体验BAT的工作环境,那么现在请我们一起开启提升技术之旅吧,详情请点击http://106.12.206.16:8080/qingruihappy/index.html
Condition的作用是对锁进行更精确的控制。Condition中的await()方法相当于Object的wait()方法,Condition中的signal()方法相当于Object的notify()方法,Condition中的signalAll()相当于Object的notifyAll()方法。不同的是,Object中的wait(),notify(),notifyAll()方法是和"同步锁"(synchronized关键字)捆绑使用的;而Condition是需要与"互斥锁"/"共享锁"捆绑使用的。
Condition除了支持上面的功能之外,它更强大的地方在于:能够更加精细的控制多线程的休眠与唤醒。对于同一个锁,我们可以创建多个Condition,在不同的情况下使用不同的Condition。
例如,假如多线程读/写同一个缓冲区:当向缓冲区中写入数据之后,唤醒"读线程";当从缓冲区读出数据之后,唤醒"写线程";并且当缓冲区满的时候,"写线程"需要等待;当缓冲区为空时,"读线程"需要等待。 如果采用Object类中的wait(), notify(), notifyAll()实现该缓冲区,当向缓冲区写入数据之后需要唤醒"读线程"时,不可能通过notify()或notifyAll()明确的指定唤醒"读线程",而只能通过notifyAll唤醒所有线程(但是notifyAll无法区分唤醒的线程是读线程,还是写线程)。 但是,通过Condition,就能明确的指定唤醒读线程。
1 import java.util.concurrent.locks.Condition; 2 import java.util.concurrent.locks.Lock; 3 import java.util.concurrent.locks.ReentrantLock; 4 5 class BoundedBuffer { 6 final Lock lock = new ReentrantLock(); 7 final Condition notFull = lock.newCondition(); 8 final Condition notEmpty = lock.newCondition(); 9 10 final Object[] items = new Object[5]; 11 int putptr, takeptr, count; 12 13 public void put(Object x) throws InterruptedException { 14 lock.lock(); //获取锁 15 try { 16 // 如果“缓冲已满”,则等待;直到“缓冲”不是满的,才将x添加到缓冲中。 17 while (count == items.length) 18 notFull.await(); 19 // 将x添加到缓冲中 20 items[putptr] = x; 21 // System.out.println(items); 22 // 将“put统计数putptr+1”;如果“缓冲已满”,则设putptr为0。 23 if (++putptr == items.length) putptr = 0; 24 // 将“缓冲”数量+1 25 ++count; 26 // 唤醒take线程,因为take线程通过notEmpty.await()等待 27 notEmpty.signal(); 28 29 // 打印写入的数据 30 System.out.println(Thread.currentThread().getName() + " put "+ (Integer)x); 31 } finally { 32 lock.unlock(); // 释放锁 33 } 34 } 35 36 public Object take() throws InterruptedException { 37 lock.lock(); //获取锁 38 try { 39 // 如果“缓冲为空”,则等待;直到“缓冲”不为空,才将x从缓冲中取出。 40 while (count == 0) 41 notEmpty.await(); 42 // 将x从缓冲中取出 43 Object x = items[takeptr]; 44 // 将“take统计数takeptr+1”;如果“缓冲为空”,则设takeptr为0。 45 if (++takeptr == items.length) takeptr = 0; 46 // 将“缓冲”数量-1 47 --count; 48 // 唤醒put线程,因为put线程通过notFull.await()等待 49 notFull.signal(); 50 51 // 打印取出的数据 52 System.out.println(Thread.currentThread().getName() + " take "+ (Integer)x); 53 return x; 54 } finally { 55 lock.unlock(); // 释放锁 56 } 57 } 58 }
1 public class ConditionTest2 { 2 private static BoundedBuffer bb = new BoundedBuffer(); 3 4 public static void main(String[] args) throws InterruptedException { 5 // 启动10个“写线程”,向BoundedBuffer中不断的写数据(写入0-9); 6 // 启动10个“读线程”,从BoundedBuffer中不断的读数据。 7 for (int i=0; i<10; i++) { 8 new PutThread("p"+i, i).start(); 9 Thread.sleep(100); 10 new TakeThread("t"+i).start(); 11 } 12 } 13 14 static class PutThread extends Thread { 15 private int num; 16 public PutThread(String name, int num) { 17 super(name); 18 this.num = num; 19 } 20 public void run() { 21 try { 22 Thread.sleep(1); // 线程休眠1ms 23 bb.put(num); // 向BoundedBuffer中写入数据 24 } catch (InterruptedException e) { 25 } 26 } 27 } 28 29 static class TakeThread extends Thread { 30 public TakeThread(String name) { 31 super(name); 32 } 33 public void run() { 34 try { 35 Thread.sleep(10); 36 Integer num = (Integer)bb.take(); // 从BoundedBuffer中取出数据 37 } catch (InterruptedException e) { 38 } 39 } 40 } 41 }
1 p1 put 1 2 p4 put 4 3 p5 put 5 4 p0 put 0 5 p2 put 2 6 t0 take 1 7 p3 put 3 8 t1 take 4 9 p6 put 6 10 t2 take 5 11 p7 put 7 12 t3 take 0 13 p8 put 8 14 t4 take 2 15 p9 put 9 16 t5 take 3 17 t6 take 6 18 t7 take 7 19 t8 take 8 20 t9 take 9
结果说明:
(01) BoundedBuffer 是容量为5的缓冲,缓冲中存储的是Object对象,支持多线程的读/写缓冲。多个线程操作“一个BoundedBuffer对象”时,它们通过互斥锁lock对缓冲区items进行互斥访问;而且同一个BoundedBuffer对象下的全部线程共用“notFull”和“notEmpty”这两个Condition。
notFull用于控制写缓冲,notEmpty用于控制读缓冲。当缓冲已满的时候,调用put的线程会执行notFull.await()进行等待;当缓冲区不是满的状态时,就将对象添加到缓冲区并将缓冲区的容量count+1,最后,调用notEmpty.signal()缓冲notEmpty上的等待线程(调用notEmpty.await的线程)。 简言之,notFull控制“缓冲区的写入”,当往缓冲区写入数据之后会唤醒notEmpty上的等待线程。
同理,notEmpty控制“缓冲区的读取”,当读取了缓冲区数据之后会唤醒notFull上的等待线程。
(02) 在ConditionTest2的main函数中,启动10个“写线程”,向BoundedBuffer中不断的写数据(写入0-9);同时,也启动10个“读线程”,从BoundedBuffer中不断的读数据。
(03) 简单分析一下运行结果。
1 1, p1线程向缓冲中写入1。 此时,缓冲区数据: | 1 | | | | | 2 2, p4线程向缓冲中写入4。 此时,缓冲区数据: | 1 | 4 | | | | 3 3, p5线程向缓冲中写入5。 此时,缓冲区数据: | 1 | 4 | 5 | | | 4 4, p0线程向缓冲中写入0。 此时,缓冲区数据: | 1 | 4 | 5 | 0 | | 5 5, p2线程向缓冲中写入2。 此时,缓冲区数据: | 1 | 4 | 5 | 0 | 2 | 6 此时,缓冲区容量为5;缓冲区已满!如果此时,还有“写线程”想往缓冲中写入数据,会调用put中的notFull.await()等待,直接缓冲区非满状态,才能继续运行。 7 6, t0线程从缓冲中取出数据1。此时,缓冲区数据: | | 4 | 5 | 0 | 2 | 8 7, p3线程向缓冲中写入3。 此时,缓冲区数据: | 3 | 4 | 5 | 0 | 2 | 9 8, t1线程从缓冲中取出数据4。此时,缓冲区数据: | 3 | | 5 | 0 | 2 | 10 9, p6线程向缓冲中写入6。 此时,缓冲区数据: | 3 | 6 | 5 | 0 | 2 | 11 9, p6线程向缓冲中写入6。 此时,缓冲区数据: | 3 | 6 | | 0 | 2 | 12 9, p6线程向缓冲中写入6。 此时,缓冲区数据: | 3 | 6 | 7 | 0 | 2 | 13 9, p6线程向缓冲中写入6。 此时,缓冲区数据: | 3 | 6 | 7 | | 2 | 14 9, p6线程向缓冲中写入6。 此时,缓冲区数据: | 3 | 6 | 7 | 8 | 2 | 15 9, p6线程向缓冲中写入6。 此时,缓冲区数据: | 3 | 6 | 7 | 8 | | 16 9, p6线程向缓冲中写入6。 此时,缓冲区数据: | | 6 | 7 | 8 | 9 | 17 9, p6线程向缓冲中写入6。 此时,缓冲区数据: | | | 7 | 8 | 9 | 18 9, p6线程向缓冲中写入6。 此时,缓冲区数据: | | | | 8 | 9 | 19 9, p6线程向缓冲中写入6。 此时,缓冲区数据: | | | | | 9 | 20 9, p6线程向缓冲中写入6。 此时,缓冲区数据: | | | | | |
假如你现在还在为自己的技术担忧,假如你现在想提升自己的工资,假如你想在职场上获得更多的话语权,假如你想顺利的度过35岁这个魔咒,假如你想体验BAT的工作环境,那么现在请我们一起开启提升技术之旅吧,详情请点击http://106.12.206.16:8080/qingruihappy/index.html