1、join
我们需要解析一个Excel里多个sheet的数据,此时可以考虑使用多 线程,每个线程解析一个sheet里的数据,等到所有的sheet都解析完之后,程序需要提示解析完 成。在这个需求中,要实现主线程等待所有线程完成sheet的解析操作,最简单的做法是使用 join()方法
public class JoinCountDownLatchTest {
public static void main(String[] args) throws InterruptedException {
Thread parser1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("parser1 finish");
}
});
Thread parser2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("parser2 finish");
}
});
parser1.start();
parser2.start();
parser1.join();
parser2.join();
System.out.println("all parser finish");
}
}
join用于让当前执行线程等待join线程执行结束。其实现原理是不停检查join线程是否存 活,如果join线程存活则让当前线程永远等待。其中,wait(0)表示永远等待下去
public final void join() throws InterruptedException {
join(0);
}
public final synchronized void join(long millis) throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
2、CountDownLatch
JDK 1.5之后的并发包中提供的CountDownLatch代替join的功能
public class JoinCountDownLatchTest {
static CountDownLatch c = new CountDownLatch(2);
public static void main(String[] args) throws InterruptedException {
Thread parser1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("parser1 finish");
//N--
c.countDown();
}
});
Thread parser2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("parser2 finish");
//N--
c.countDown();
}
});
parser1.start();
parser2.start();
//阻塞当前线程,直到N变成零
c.await();
System.out.println("all parser finish");
}
}
3、CountDownLatch原理:
创建CountDownLatch时需要传入一个计数N。
当我们调用CountDownLatch的countDown方法时,N就会减1,CountDownLatch的await方法会阻塞当前线程,直到N变成零。
由于countDown方法可以用在任何地方,所以这里说的N个点,可以是N个线程,也可以是1个线程里的N个执行步骤
如果有某个解析sheet的线程处理得比较慢,我们不可能让主线程一直等待,所以可以使
用另外一个带指定时间的await方法——await(long time,TimeUnit unit),这个方法等待特定时
间后,就会不再阻塞当前线程。join也有类似的方法。
注意:计数器必须大于等于0,否则await方法时不会阻塞当前线程。
而且CountDownLatch不能重新初始化或者修改CountDownLatch对象的内部计数器的值。这也是他比同步屏障CyclicBarrier弱的地方
CountDownLatch的内部类Sync,继承自AQS
public class CountDownLatch {
private static final class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 4982264981922014374L;
Sync(int count) {
setState(count);
}
int getCount() {
return getState();
}
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
protected boolean tryReleaseShared(int releases) {
//循环进行CAS,直到当前线程成功完成CAS使计数值减一并更新到state
for (;;) {
int c = getState();
//如果当前状态值为0则直接返回
if (c == 0)
return false;
int nextc = c-1;
//CAS设置计数值减一
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
}
private final Sync sync;
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);
}
public boolean await(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}
public void countDown() {
sync.releaseShared(1);
}
public long getCount() {
return sync.getCount();
}
public String toString() {
return super.toString() + "[Count = " + sync.getCount() + "]";
}
}