JUC 包(java.util.concurrent)提供了并发编程的解决方案,CAS 是 java.util.concurrent.atomic 包的基础,AQS 是 java.util.concurrent.locks 包以及一些常用类比如 Semophore,ReentrantLock 等类的基础。
JUC 包的分类:
包括倒计时闭锁 CountDownLatch、栅栏 CyclicBarrier、信号量 Semaphore、交换器 Exchanger。
倒计时闭锁 CountDownLatch 也称为倒计时计数器,可以让主线程等待一组事件发生后继续执行,事件是指 CountDownLatch 里的 countDown() 方法。
public class RocketLaunch implements Runnable {
static final CountDownLatch latch = new CountDownLatch(10);
static final RocketLaunch rocketLaunch = new RocketLaunch();
@Override
public void run() {
// 模拟检查任务
try {
Thread.sleep(new Random().nextInt(10) * 1000);
System.out.println("check complete");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//计数减一
//放在finally避免任务执行过程出现异常,导致countDown()不能被执行
latch.countDown();
}
}
public static void main(String[] args) throws InterruptedException {
//不建议 LinkedBlockingQueue无界队列
ExecutorService exec = Executors.newFixedThreadPool(10);
//10个任务,10次任务提交
for (int i = 0; i < 10; i++) {
exec.submit(rocketLaunch);
}
// 等待检查
latch.await();// latch.await()方法要求主线程等待所有10个检查任务全部准备好才一起并行执行
// 发射火箭
System.out.println("Fire!");
// 关闭线程池
exec.shutdown();
}
栅栏 CyclicBarrier 可以阻塞当前线程,等待其他线程,所有线程必须同时到达栅栏位置后,才能继续执行;所有线程到达栅栏处,可以触发执行另一个预先设置的线程。
public class CyclicBarrierDemo {
public static void main(String[] args) {
new CyclicBarrierDemo().execute();
}
private void execute() {
CyclicBarrier cyclicBarrier = new CyclicBarrier(3); // 初始化栅栏的参与者为3
new Thread(new Task(cyclicBarrier), "thread1").start();
new Thread(new Task(cyclicBarrier), "thread2").start();
new Thread(new Task(cyclicBarrier), "thread3").start();
}
class Task implements Runnable {
private CyclicBarrier cyclicBarrier;
private Task(CyclicBarrier cyclicBarrier) {
this.cyclicBarrier = cyclicBarrier;
}
@Override
public void run() {
System.out.println("线程" + Thread.currentThread().getName() + "已经到达");
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
System.out.println("线程" + Thread.currentThread().getName() + "开始处理");
}
}
}
运行结果:
线程thread3已经到达
线程thread1已经到达
线程thread2已经到达
线程thread2开始处理
线程thread3开始处理
线程thread1开始处理
信号量 Semaphore 可以控制某个资源可被同时访问的线程个数。
public class SemaphoreDemo {
public static void main(String[] args) {
// 线程池
ExecutorService exec = Executors.newCachedThreadPool();
// 只能5个线程同时访问
final Semaphore semp = new Semaphore(5);
for (int index = 0; index < 20; index++) {
final int no = index;
Runnable runnable = new Runnable() {
@Override
public void run() {
try {
// 获取许可
semp.acquire();
System.out.println("Accessing: " + no);
Thread.sleep((long) (Math.random() * 10000));
// 访问完后, 释放
semp.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
exec.execute(runnable);
}
// 退出线程池
exec.shutdown();
}
}
只有 5 个客户端可以访问,其他客户端只能等待已经获取许可的 5 个客户端调用 release() 方法释放许可,才能进行访问。
交换器 Exchanger 主要用于线程之间数据交换,可以实现两个线程到达同步点后,相互交换数据。先到达同步点的线程会被阻塞,当两个线程都到达同步点后,开始交换数据。
public class ExchangerDemo {
private static Exchanger<String> exchanger = new Exchanger<String>();
public static void main(String[] args) {
ExecutorService exec = Executors.newFixedThreadPool(2);
// 线程一
exec.execute(() -> {
try {
String text = exchanger.exchange("hello1");
System.out.println("thread1: " + text);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
// 线程二
exec.execute(() -> {
try {
TimeUnit.SECONDS.sleep(3);
String text = exchanger.exchange("hello2");
System.out.println("thread2: " + text);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
}
Lock(Java5新特性)提供了比synchronized方法和synchronized代码块更广泛的锁定操作,Lock允许实现更灵活的结构,并且支持多个相关的Condition对象。
Java 中常见的锁有独享锁/共享锁+公平锁/非公平锁+乐观锁/悲观锁。
特点:
性能未必比 synchronized 高,并且也是可重入的。
ReentrantLock 能够实现比 synchronized 更细粒度的控制,如设置公平性 fairness;
基于 AQS 实现,AQS 是 Java 并发用来构建锁或其他同步组件的基础框架,JUC( java.util.concurrent)package 的核心;
ReentrantLock 调用 lock() 之后,必须调用 unlock() 释放锁;
ReentrantLock 是一种排他锁,同一时间只有一个线程在执行 ReentrantLock.lock() 方法后面的任务。
ReentrantLock 实现等待/通知,可以借助于 Condition 对象实现
synchronized 和 ReentrantLock 的区别:
公平性是减少线程饥饿的情况发生的一个办法,线程饥饿指个别线程长期等待锁,但却始终无法获取锁的情况。
Java 默认的调度策略很少会导致饥饿的发生,若要保证公平性,则要引入额外的开销,导致一定的吞吐量下降,所以建议只有当程序确实有公平性需要的时候,才有必要去指定公平锁。
排他锁 ReentrantLock 虽然保证了实例变量的线程安全性,但效率却是十分低下的。所以在 JDK 中提供了一种读写锁 ReentrantReadWriteLock 类,使用它可以加快运行效率,在某些不需要操作实例变量的方法中,完全可以使用读写锁 ReentrantReadWriteLock 来提升该方法的代码运行速度。
读写锁包含两个锁,一个是读操作相关的锁,也称为共享锁;另一个是写操作相关的锁,也就做排他锁。也就是多个读锁之间不互斥,读锁与写锁互斥,写锁与写锁互斥。多个 Thread 可以同时进行读取操作,但是同一时刻只允许一个 Thread 进行写入操作。