1. Lock
并发包中的锁被用来控制多个线程访问共享资源的方式,于Java SE 5后加入,可以显式获取和释放锁(synchrozined则是隐式的)。
标准使用样例:
Lock lock = new ReentrantLock();lock.lock();// Do Somthinglock.unlock(); // 要注意避免Do Something中发生异常而无法unlock()的死锁发生情况,建议把unlock写在finally里。
于 synchronized 不同的是:
- 非阻塞地获取锁:
tryLock()能获取锁到则返回true否则返回false,不强制进行阻塞。 - 能被中断地获取锁:
lockInterruptibly()在获取锁地过程中可以响应中断请求。 - 超时获取锁:规定时间内未获取到锁会直接放弃并返回false——
tryLock(long time...)。 - 分组唤醒:
newCondition()可以利用等待通知组建来分组唤醒响应地线程。2. 队列同步器(AQS)
队列同步器AQS本质是一个CLH队列,主要用于实现锁。
- 具有三个共享状态访问方法:
getState、setState(int newState)和compareAndSetState(int expect int update)。 可重写的方法: | 方法名称 | 描述 | | —- | —- | | protected boolean tryAquire(int arg) | 独占式获取同步状态 | | protected boolean tryRelease(int arg) | 独占式释放同步状态 | | protected boolean tryAquireShared(int arg) | 共享式获取同步状态 | | protected boolean tryReleaseShared(int arg) | 共享式释放同步状态 | | protected boolean isHeldExclusively() | 判断当前队列同步器是否在独占模式下被线程所占用 |
在交由AQS的实现类代理时,需调用以下模板方法,即
lock()->acquire()->tryAcquire():aquire(int)、aquireShared(int)、aquireInterruptibly(int)、aquireSharedInterruptibly(int)、tryAquireNanos(int, long)、tryAquireSharedNanos(int, long)、release(int)、releaseShared(int)。- 可通过
Collection<Thread> getQueuedThreads()获取等待在同步队列上的线程集合。
在运作时,没有获得同步状态的节点会被加入一个FIFO双向队列的尾部。这一过程需要基于CAS进行设置:
compareAndSetTail(),而设置头节点时则不需要。节点进入同步队列之后,就进入了自旋的过程,自旋过程将会阻塞节点对应的线程。自旋过程将会检定两件事:
- 前驱节点是否为头节点;
- 若为头节点,才尝试获取状态;
其好处为:节省(独占式)获取状态所需的开支、保证FIFO原则不被破坏。
3. 重入锁、读写锁、LockSupport、Condition接口
重入锁 ReentrantLock(boolean fair=false) 支持一个线程对已持有的资源进行重复加锁,并能构建公平锁和非公平锁。重入锁可以避免自己阻塞自己的情况,为显式重入( synchronized 是隐式重入)。
- 公平锁:公平锁就是保障了多线程下各线程获取锁的顺序(FIFO)、绝对时间上一定可以获取到锁。
- 非公平锁:非公平锁有饥饿现象,效率比公平锁高(cpu分时、无上下文切换开销)
读写锁 ReentrantReadWriteLock(boolean fair=false) 同一时刻允许多个读线程访问,但写线程访问并取得锁之后,其它线程均被阻塞。其特性有:可重进入、写锁可以被降为读锁。
- 写锁可以被降为读锁被称为锁降级:保持写锁并获得读锁,最终放开写锁。样例代码如:
```java
readLock.lock(); // 首先获取读锁,保证对条件状态(数据)的获取
if(!条件状态){
readLock.unlock(); // 释放读锁
writeLock.lock(); // 获取写锁,准备开始降级
try{
} finally{if(!条件状态){更新条件状态 = true; // 基于写锁更新条件状态(数据)}readLock.lock(); // 降级到读锁
} } // 至此,降级完成writeLock.unlock(); // 释放写锁
try{ // Do Somthing with 条件状态(数据) } finally{ readLock.unlock(); }
---`LockSupport` 工具类用于阻塞或唤醒一个线程。阻塞: `park()` 、 `parkNanos(long)` 、 `parkUntil(long)` ;唤醒: `unpark(Thread)` 。```javapublic class test { // 样例:多线程无限按序打印1~n的数字private static int n = 10;private static LinkedList<Thread> list = new LinkedList<>();public static void main(String[] args){for(int i=0; i<n; i++){Thread crn = new Thread(new R(i));list.add(crn);crn.start();}LockSupport.unpark(list.get(0));}static class R implements Runnable{private int id;R(int id){this.id = id;}@Overridepublic void run(){while (true){LockSupport.park();System.out.println(this.id);LockSupport.unpark(list.get((this.id+ 1) % n)); // 唤醒下一个线程}}}}
Condition 和 wait()/notify() 的区别:
| 对比项目 | Monitor | Condition |
|---|---|---|
| 等待队列个数 | 一个 | 多个 |
| 等待锁的同时响应中断 | N | Yes |
| 当前线程释放锁并等待状态到未来某个时间 | N | Yes |
| 是否公平 | ? | Yes |
使用示例:
ReentrantLock lock = new ReentrantLock();
