【Java】并发之锁与条件

来源:互联网 发布:乍得内战知乎 编辑:程序博客网 时间:2024/06/08 08:07

讨论锁机制,少不了“生产者-消费者”这个经典场景。

多线程中,为什么需要锁?因为多个线程同时访问同一个资源,会出现同时修改的问题。所以,得加一把锁,A线程开始访问该资源时,锁上,此时,B线程就得等待于门外;当A线程访问完毕,解锁,即开门了,B线程才得以进入。

在Java中,可以定义一个Object类型的锁对象,通过synchronized关键词实现线程锁的作用。Java5开始,提供ReentrantLock这个类,锁上通过lock()方法,解锁通过unlock()方法,可取代synchronized用法。代码如下:

class Factory {private int count = 0;//存货数量private ReentrantLock lock = new ReentrantLock();/** * 生产1个产品 */public void produce() {lock.lock();try {System.out.println("生产1个,现有 : " + (++count));} finally {lock.unlock();}}/** * 卖出1个产品 */public void consume() {lock.lock();try {System.out.println("卖出1个,现有 : " + (--count));} finally {lock.unlock();}}}

构造1000个线程执行上例中的produce()方法和,1000个线程执行consume()方法,即模拟现实中生产1000个产品,卖出1000个产品,最终的结果,会剩下0个。因为我们加了同步锁控制。如果没有加锁的话,由于produce()方法和consume()方法同时竞争资源(本例中的“资源”为存货个数count),发生混乱,最终打印的结果很可能不是为0。

通过打印的结果,我们发觉,打印存货个数时,可能为负数,这是与现实不符合的。因为,在consume()方法中可能没有产品可卖了,即现实中的产品供不应求的意思。但程序不解理啊,需要存货个数为0,但程序仍让它卖出,继续减1操作,就成了负数了。

为避免这种现象,就设立一个“有货可卖”的条件,满足这个条件,才允许卖出产品,否则,就得等待,直到生产方有生产出产品为止。

而现实中又有另一需要,工作不敢无限量地生产产品,担心卖不出去,所以,得控制存货的数量,例如:最多存货100个。

Java并发API中,使用了Condition类来实现线程之间的协作控制,我们修改上面的程序如下:

class Factory {private int count = 0; // 存货数量private ReentrantLock lock = new ReentrantLock();private Condition notEmptyCondition = lock.newCondition(); // “有货可卖”条件private Condition notFullCondition = lock.newCondition(); // “允许生产”条件/** * 生产1个产品 */public void produce() {lock.lock();try {while (count == 100) {// 封顶了,暂时不能继续生产,线程我阻塞中,等待卖出产品然后通知我继续生产产品(即继续执行下面的代码)notFullCondition.await();}++count; // 生产1个产品System.out.println("生产1个,共有 : " + count);// 新生产了1个产品,“有货可卖”条件加1,通知受困于notEmptyCondition.await()的线程可以销售1个产品了notEmptyCondition.signal();} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}}/** * 卖出1个产品 */public void consume() {lock.lock();try {while (count == 0) {// 库空了,暂时不能继续销售,线程我阻塞中,等待生产出产品然后通知我继续销售产品(即执行下面的代码)notEmptyCondition.await();}--count; // 卖出1个产品System.out.println("卖出1个,剩下 : " + count);// 新卖出了1个产品,“允许生产”条件加1,通知受困于notFullCondition.await()的线程可以生产1个产品了notFullCondition.signal();} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}}}

这样理解,Condition对象内有一记数器,每调用一次await()方法,就会记录有1个线程阻塞在这里,等待其他的线程调用1次signal()方法,以唤醒这个线程可以继续执行await()后面的代码了。

另外,区别一下signal()和signalAll()方法。signal()只能唤醒1个阻塞在await()处的线程;而signalAll()可唤醒所有阻塞在await()处的线程。

理解Java多线程的锁和条件的用法,对掌握并发集合类(如ConcurrentHashMap, ArrayBlockQueue等)有很大的好处。


@容新华技术博客 - http://blog.csdn.net/rongxinhua - 原创文章,转载请注明出处

0 0
原创粉丝点击