在jdk5之后的高级并发包里面Lock接口可以替换原来jvm内置的锁synchronized关键字,同理使用Condition接口的await,signal,signalAll方法分别可以替换原来的协作方式wait,notify,notifyAll。
相比原来的Object提供的协作方式,新的Condition接口则是更加以面相对象的方式展现,它跟Lock接口绑定,新的定义可以在一个锁上声明多个条件量,它的一个模板写法如下:
private Lock lock=new ReentrantLock();
Condition full=lock.newCondition();
Condition empty=lock.newCondition();
相比原来的wait,notify,notifyAll方法,Condition接口还提供了额外的两个功能:
boolean awaitUntil(Date deadline)
void awaitUninterruptibly()
第一个是提供了可以wait到具体某一个时间后,自动唤醒自己。
第二个是提供了wait不可打断的方法。
另外一个与比之前协作强大的地方,就是同一个锁上可以声明多个独立的条件量,举个例子之前的生产者消费者协作模式,生产者和消费者共用一个信号量的问题是,执行notify方法默认是随机唤醒一个线程,如果生产者和消费者共享一个信号量,那么底层相当于维护一个wait队列,那么消费者唤醒生产者线程的时候,有可能还是唤醒的消费者线程,所以这一点相当于是不可控的,粒度太粗,所以Condition接口就是为解决这个问题出现的,允许我们在一个锁对象上声明多个信号量,分别维护不同的等待队列,这样以来,如果 需要唤醒生产者线程,那么直接使用其对应的信号量即可,这样以来就能保证唤醒的一定是生产者线程。
功能点分析:
使用Lock接口实现模拟火车站多个窗口卖票的功能,这里票是共享资源,同一张票只能有一个线程可以卖出,如果票卖完就告诉用户无票。此外为了避免某一个线程卖的太快,把所有的票都垄断,导致有的窗口可能永远也卖不了票,所以这里需要注意合理的控制cpu资源,不能让一个线程一直抢用。
4个线程(窗口)卖10张票的结果输出如下:
窗口1 卖出了票id=10
窗口2 卖出了票id=9
窗口4 卖出了票id=8
窗口3 卖出了票id=7
窗口1 卖出了票id=6
窗口1 卖出了票id=5
窗口2 卖出了票id=4
窗口4 卖出了票id=3
窗口3 卖出了票id=2
窗口1 卖出了票id=1
窗口3 票已经售完,谢谢光临!
窗口2 票已经售完,谢谢光临!
窗口4 票已经售完,谢谢光临!
窗口1 票已经售完,谢谢光临!
功能点分析:
这里面有2个线程,一个生产者一个消费者,这里假设队列大小是10,如果队列满了,生产者就要等待消费者消费,如果队列空了,那么消费者就要等待生产,这里面也可以控制生产和消费的速度:
生产者放入一条数据:87
生产者放入一条数据:51
生产者放入一条数据:86
生产者放入一条数据:94
生产者放入一条数据:71
生产者放入一条数据:8
生产者放入一条数据:10
生产者放入一条数据:57
生产者放入一条数据:72
生产者放入一条数据:61
Thread-0 队列满了,生产者开始阻塞
消费者消费一条数据:87
消费者消费一条数据:51
消费者消费一条数据:86
消费者消费一条数据:94
消费者消费一条数据:71
功能点分析:
这里面需要一个数字累加器,如果+1后是奇数那么奇数就应该打印数据,同时偶数线程阻塞,当奇数打印完毕后,要+1更新奇数器,同时唤醒偶数线程,偶数的逻辑与奇数线程类似,如果是偶数就打印,否则就进入等待状态,同时+1更新计数器,然后唤醒奇数线程。
even偶数线程阻塞
odd线程 打印 1
odd奇数线程阻塞
even线程 打印 2
even偶数线程阻塞
odd线程 打印 3
odd奇数线程阻塞
even线程 打印 4
even偶数线程阻塞
odd线程 打印 5
odd奇数线程阻塞
even线程 打印 6
even偶数线程阻塞
odd线程 打印 7
odd奇数线程阻塞
even线程 打印 8
本文主要了在Lock接口里面的条件量的使用以及它与jdk5之前的线程协作方式相比的优点,同时本文又给出了是三个在线程里面经典的例子运行结果的输出,这里由于篇幅原因代码不再贴出,感兴趣的朋友可以到我的github上下载完整例子代码。
github地址:
https://github.com/qindongliang/Java-Note