生活
今天周五,明天加班。
如痴如醉,如梦如醒。
—
今天开门见山,直接来引入今天的内容。昨天(上一篇)讲到了并发编程开发中非常重要的锁,ReentrantLock,即可重入锁,在这个锁中【其实是在其父接口Lock下】,有个newCondition方法用来创建一个与其锁相关联的条件变量,由于这玩意也是非常之重要,所以单独搞一篇写写,加深记忆。
JAVA中的条件变量都实现了java.util.concurrent.locks.Condition接口,条件变量的实例化是通过Lock对象上调用一个newCondition来获取的,这样一个条件变量就和一个锁关联起来了。因此,java中的条件变量只能和锁搭配使用,来控制并发程序对共享资源的竞争。
源码
先来看下Condition接口里有些什么?
//等待,直到被中断或者被唤醒
void await() throws InterruptedException;
//等待,直到被唤醒,不响应中断,在进入同步队列后自我中断
void awaitUninterruptibly();
//在指定时间内等待直到被唤醒
long awaitNanos(long nanosTimeout) throws InterruptedException;
boolean await(long time, TimeUnit unit) throws InterruptedException;
boolean awaitUntil(Date deadline) throws InterruptedException;
//唤醒下一个
void signal();
//唤醒全部
void signalAll();
再来看看Condition的实现,AQS中的ConditionObject做了什么?
/** First node of condition queue. */
private transient Node firstWaiter;
/** Last node of condition queue. */
private transient Node lastWaiter;
可以看到ConditionObject维护了一个等待队列
ConditionObject内部维护了一个等待队列,其实是复用了AQS Node的一个FIFO队列,队列中的每一个Node都是等待在Condition对象上的线程的引用,调用Condition的await方法后,线程释放锁,构造成相应的Node对象进入等待队列,等待被唤醒进入AQS的同步队列参与资源的竞争。
以上的代码细节不深入,可以自己去看,其实都是AQS里的东西,仔细研究过AQS,应该都不会看不明白。
下面直接来看下具体的使用。
案例
Condition常用于生产者消费者模式和阻塞队列的实现。
下面来看下具体的案例。
package test;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class ConditionTest {
public static void main(String[] args) {
Account account = new Account();
CountDownLatch latch = new CountDownLatch(1);
new Thread(new Action("a", 1000, account, true, latch)).start();
new Thread(new Action("b", 2000, account, false, latch)).start();
new Thread(new Action("c", 3000, account, true, latch)).start();
new Thread(new Action("d", 2500, account, false, latch)).start();
new Thread(new Action("e", 300, account, true, latch)).start();
new Thread(new Action("f", 100, account, false, latch)).start();
new Thread(new Action("g", 500, account, true, latch)).start();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
latch.countDown();
}
static class Action implements Runnable{
private String name;
private int m;
private Account account;
private boolean save;
private CountDownLatch latch;
@Override
public void run() {
try {
latch.await();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if(save) {
account.save(name,m);
}else {
account.draw(name,m);
}
}
public Action(String name, int m, Account account, boolean save, CountDownLatch latch) {
super();
this.name = name;
this.m = m;
this.account = account;
this.save = save;
this.latch = latch;
}
}
static class Account{
private ReentrantLock lock = new ReentrantLock();
//存款条件
private Condition save = lock.newCondition();
//取款条件
private Condition draw = lock.newCondition();
//金额 银行卡金额上限4000
private volatile int money;
public void save(String name,int m) {
try {
lock.lock();
if(m>0) {
while(money+m>4000) {
save.await();
System.out.println(String.format("name:%s,存钱阻塞中", name));
}
money+=m;
System.out.println(String.format("name:%s,存款:%s,余额:%s", name,m,money));
}
draw.signalAll();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void draw(String name,int m) {
try {
lock.lock();
if(m>0) {
while(m>money) {
draw.await();
System.out.println(String.format("name:%s,取钱阻塞中", name));
}
money-=m;
System.out.println(String.format("name:%s,取款:%s,余额:%s", name,m,money));
save.signalAll();
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
}
结果:
name:a,存款:1000,余额:1000
name:c,存款:3000,余额:4000
name:f,取款:100,余额:3900
name:b,取款:2000,余额:1900
name:d,取钱阻塞中
name:e,存钱阻塞中
name:e,存款:300,余额:2200
name:g,存钱阻塞中
name:g,存款:500,余额:2700
name:d,取钱阻塞中
name:d,取款:2500,余额:200
总结
Condition的作用是对锁进行更精确的控制。Condition中的await()方法相当于Object的wait()方法,Condition中的signal()方法相当于Object的notify()方法,Condition中的signalAll()相当于Object的notifyAll()方法。
不同的是,Object中的wait(),notify(),notifyAll()方法是和"同步锁"(synchronized关键字)捆绑使用的;而Condition是需要与"互斥锁"/"共享锁"捆绑使用的。
睡觉明天还要加班。。。。
明天看读写锁哦