java.util.concurrent
包中提供了3个很实用的工具类帮我们进行复杂的线程同步控制,分别是减少计数器CountDownLatch
、循环栅栏CyclicBarrier
和信号灯Semaphore
。
CountDownLatch
内部维护了一个计数器,调用countDown
方法可以使计数器的值减少1
,子线程可以使用await
方法阻塞等待,直到计数器的值小于0
。我们直接看一个例子。
package com.gacfox.demo;
import java.util.concurrent.CountDownLatch;
public class MyThread implements Runnable {
private CountDownLatch countDownLatch;
public MyThread(CountDownLatch latch) {
this.countDownLatch = latch;
}
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
countDownLatch.countDown();
}
}
package com.gacfox.demo;
import java.util.concurrent.CountDownLatch;
public class Main {
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(3);
new Thread(new MyThread(countDownLatch)).start();
new Thread(new MyThread(countDownLatch)).start();
new Thread(new MyThread(countDownLatch)).start();
countDownLatch.await();
System.out.println("计数器耗尽!");
}
}
代码中,我们的主线程会在CountDownLatch
小于0
前一直阻塞等待,子线程不断将计数器值减小,最终触发主线程await()
返回。
循环栅栏内部维护了一个栅栏值,当多个子线程await()
到循环栅栏上时,如果await()
的线程数达到栅栏值,这些线程的await()
会一起返回。
package com.gacfox.demo;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class MyThread implements Runnable {
private final CyclicBarrier cyclicBarrier;
public MyThread(CyclicBarrier barrier) {
this.cyclicBarrier = barrier;
}
@Override
public void run() {
try {
this.cyclicBarrier.await();
} catch (InterruptedException | BrokenBarrierException e) {
throw new RuntimeException(e);
}
System.out.println("线程" + Thread.currentThread().getId() + "开始执行");
}
}
package com.gacfox.demo;
import java.util.concurrent.CyclicBarrier;
public class Main {
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(3);
new Thread(new MyThread(cyclicBarrier)).start();
new Thread(new MyThread(cyclicBarrier)).start();
new Thread(new MyThread(cyclicBarrier)).start();
}
}
代码中,我们创建了3个线程对循环栅栏进行await()
操作,最终达到栅栏值所有线程返回。
信号灯内部维护了一些令牌,子线程可以从信号灯中“抢”令牌,令牌全部发出去后,未获得到令牌的线程就只能阻塞等待了,有子线程释放令牌后,未获取令牌的子线程才可以继续执行。
package com.gacfox.demo;
import java.util.concurrent.Semaphore;
public class MyThread implements Runnable {
private final Semaphore semaphore;
public MyThread(Semaphore semaphore) {
this.semaphore = semaphore;
}
@Override
public void run() {
try {
this.semaphore.acquire();
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("线程" + Thread.currentThread().getId() + "开始执行");
this.semaphore.release();
}
}
package com.gacfox.demo;
import java.util.concurrent.Semaphore;
public class Main {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(3);
new Thread(new MyThread(semaphore)).start();
new Thread(new MyThread(semaphore)).start();
new Thread(new MyThread(semaphore)).start();
new Thread(new MyThread(semaphore)).start();
new Thread(new MyThread(semaphore)).start();
}
}
上面代码中,我们创建的信号灯中有3个令牌,但有5个线程在获取。执行后,我们会观察到3个线程先执行完,他们释放令牌后,剩余的2个线程才会继续执行。注意这里我们的线程执行完后不要忘记释放令牌,否则后面的线程就永远获取不到令牌了。此外,线程也可以一次获取多个令牌,具体API可以参考JDK文档,这里及不展开介绍了。