前言
相信JUC和AQS都是大家如雷贯耳、耳熟能详的词儿了。
JUC是大名鼎鼎的java.util.concurrent包的缩写,一看名字就很牛,java提供的并发工具集。而AQS(Abstract Queued Synchronizer)则是JUC包中许多同步工具实现的基础。
我们先来看看JUC包中有哪些东西:
- atomic原子类:著名的基于CAS的原子操作类。
- 并发容器:包括ConcurrentMap、ConcurrentSkipListSet、CopyOnWriteArraySet、ConcurrentLinkedQueue、Dequeue等等。而创建线程池的一个重要参数:workQueue,一般就出自这里。
- 线程池:
- 异步机制常用的Future、Callable
- 线程池ThreadPoolExecutor以及四种预定义的线程池SingleThreadExecutor、CachedThreaadPool、FixedThreadPool、SecheduledThreadPool
- 同步工具
- AQS
- Lock:ReentrantLock、ReadWriteLock、LockSupport
- 其他工具:CountDownLatch、CyclicBarrier、Phaser、Semaphore、Exchanger
几乎囊括了所有和多线程相关的内容。
本文我们先来总结一下JUC中的同步工具。
LockSupport
LockSupport中提供的方法不多,且都是以静态方法提供:
1 | public static void unpark(Thread thread); |
说白了就是将Unsafe类中的 park()
和 unpark()
方法包装了一下,提供了挂起和唤醒线程的方法。
为啥要包装呢?
因为Unsafe类并不是普通应用代码可以直接调用的,看下Unsafe的源码:
1 | private Unsafe() {} |
可以看到:
- Unsafe是单例的,外界无法通过构造方法直接构造Unsafe对象,需要调用
getUnsafe()
方法获取。 getUnsafe()
方法中是有条件的,调用者必须是 Bootstrap类加载器 加载的类(比如AQS),否则会报Unsafe异常,显然我们写的普通代码是无法调用的。
因此,一般情况下,我们写的代码是无法直接调用Unsafe类中的方法的。
当然,也不是完全没有办法,比如我们可以通过反射,获取 theUnsafe
方法:
1 | final Field field = Unsafe.class.getDeclaredField("theUnsafe"); |
但这毕竟不是官方推荐的方法,因此,LockSupport便将 park()
和 unpark()
方法包装了一下,提供给普通应用程序使用。
Lock
ReentrantLock
通过类名就能看出来,这是一把可重入锁。
常用方法
1 | /* 构造方法 */ |
ReentrantReadWriteLock
其他同步工具
CountDownLatch
从类名来看,这是个向下计数的门栓。初始化时,传入计数初始值,调用await()的线程挂起等待,其他线程每调用一次countDown(),计数值减一,当计数值减到0,调用await()的方法被唤醒继续执行。
常用方法
1 | /* 构造方法 */ |
举例
1 | public class T06_TestCountDownLatch { |
输出:
1 | Waiting latch... |
CyclicBarrier
CyclicBarrier是个计数拦截器,创建CyclicBarrier时,需要传入计数初始值。每个线程调用wait()方法,将线程挂起。直到调用wait()的线程数达到计数值,所有线程一起唤醒。
常用方法
1 | /* 构造方法 */ |
举例
1 | public class TestCyclicBarrier { |
输出:
1 | Thread-0 waiting... |
Phaser
常用方法
1 | /* 构造方法 */ |
Semaphore
信号量,和锁的区别是,锁只能允许一个线程进入同步代码段,但是信号量可以允许多个线程进入同步代码段。
常用方法
1 | /* 构造方法 */ |
举例
1 | public class TestSemaphore { |
输出:
1 | T1 running... |
Exchanger
Exchanger就如其类名,用于线程之间交换数据。
常用方法
1 | /* 构造方法 */ |
举例
1 | public class TestExchanger { |
输出:
1 | t1 ready to exchange... |