写在前面,阅读这篇文章,需要这些知识:
Java并发——Thread类解析、线程初探
Java并发——CAS原子操作
Java并发——AQS框架详解
CountDownLatch
CountDownLatch
类是AQS
框架闭锁机制的一种实现。这是种什么机制呢?假如有以下场景,有三个线程需要并发的进行,但是线程A与B需要在线程C执行完成后才执行,也就是说,线程C完成了后,挡住线程A与B的“锁”才会打开。它的应用场景如下:
public class Test {
public static void main (String args[]) throws InterruptedException {
CountDownLatch count=new CountDownLatch(1);
Thread thread2=new Thread(new Runnable() {
@Override
public void run() {
try {
count.await();
System.out.println("拉姆执行了啦");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread thread1=new Thread(new Runnable() {
@Override
public void run() {
try {
count.await();
System.out.println("蕾姆执行了啦");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread thread3=new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("艾米莉亚执行了");
count.countDown();
}
});
thread2.start();
thread1.start();
thread3.start();
}
}
//输出:
//艾米莉亚执行了
//拉姆执行了啦
//蕾姆执行了啦
从代码中可以看出,即便是thread3
线程最后才开始而且等待了3秒钟,线程thread1
与thread2
也在等在thread3
线程执行完才执行。CountDownLatch
类的内部定义了Sync
同步器继承自AQS
框架,可以自定义资源state
的大小:
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
它的请求获取资源的方法如下:
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
从它的请求资源的方法可以看出,CountDownLatch
类是将AQS框架
中的state
值作为一个计数器,当该值不为0的时候,代表着线程请求资源失败,需要加入AQS
的同步队列中等待state
为0的时候继续获得资源执行。同时state
的大小也代表着可以让多少个线程进入同步队列等待state
为0的时候继续运行。对AQS
框架不熟的同学可以看看这篇文章:Java并发——AQS框架详解。而它的释放的资源的代码如下:
public void countDown() {
//释放1个资源
sync.releaseShared(1);
}
protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
直接将state
的值减一,减到零代表其它因获取资源失败的线程可以重新得到运行,利用这个特性,我们可以用来实现闭锁的机制。
Semaphore
Semaphore
是AQS框架
信号量机制的一种实现。它首先在创建时会定义一定的资源量,每个线程如果能从Semaphore
里得到足够的资源,那么该线程就会得到执行,而如果得不到足够的资源,那么AQS会把线程置于同步队列中,来等待前面获得资源的线程执行完毕释放资源。假如有两两个资源,线程A得到了一个在运行,线程B得到了一个运行,但是线程C因资源不足就会进入AQS
的同步队列陷入等待线程A或者B释放资源,它才能得到资源进行运行,如下代码:
public class Test {
public static void main (String args[]) throws InterruptedException {
CountDownLatch count=new CountDownLatch(1);
Semaphore semaphore=new Semaphore(2);
Lock lock=new ReentrantLock();
Thread thread2=new Thread(new Runnable() {
@Override
public void run() {
try {
semaphore.acquire();
System.out.println("拉姆执行中");
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
System.out.println("拉姆执行完毕啦");
//semaphore.release();
}
}
});
Thread thread1=new Thread(new Runnable() {
@Override
public void run() {
try {
//count.await();
semaphore.acquire();
System.out.println("蕾姆执行中");
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
System.out.println("蕾姆执行完毕啦");
//semaphore.release();
}
}
});
Thread thread3=new Thread(new Runnable() {
@Override
public void run() {
try {
semaphore.acquire();
System.out.println("艾米莉亚开始执行了");
} catch (InterruptedException e) {
e.printStackTrace();
}
//count.countDown();
}
});
thread2.start();
thread1.start();
thread3.start();
}
}
//输出:
//拉姆执行中
//蕾姆执行中
//拉姆执行完毕啦
//蕾姆执行完毕啦
如果注释的地方没有去掉,也就是没有释放资源的话,那么thread3
会一直陷入等待资源的情况,而不会执行下面的代码。同时,Semaphore
类也分公平
与非公平
两种情况,而默认的是非公平的:
public Semaphore(int permits) {
sync = new NonfairSync(permits);
}
以非公平的Semaphore
请求资源源码为示例:
final int nonfairTryAcquireShared(int acquires) {
for (;;) {
int available = getState();
//减去线程请求的资源量
int remaining = available - acquires;
//如果资源量小于0或CAS操作成功了
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
可以看得出来,用循环来重试失败的CAS原子操作
,以此来保证state
的改变一定能成功,也就是说在AQS
中的state
表示的是资源量的大小。当资源量小于线程请求的大小时,线程会进入AQS得到同步队列,来等待资源的释放。公平的请求方法是在这个基础上加了一个判断,如果判断同步队列中有线程在等待,那么直接将新来的线程加入到队尾,保证处于同步队列中的线程先来先执行。而其释放方法如下:
protected final boolean tryReleaseShared(int releases) {
for (;;) {
int current = getState();
int next = current + releases;
if (next < current) // overflow
throw new Error("Maximum permit count exceeded");
if (compareAndSetState(current, next))
return true;
}
}
增加state
的大小,这个大小等于线程释放的资源量。此外Semaphore
还定义了可轮旋的、定时的、可以中断异常的三个请求方法:
public boolean tryAcquire();
public boolean tryAcquire(long timeout, TimeUnit unit);
public void acquireUninterruptibly();
默认的acquire方法调用的是acquireUninterruptibly方法,默认可以中断异常。