1. Lock
并发包中的锁被用来控制多个线程访问共享资源的方式,于Java SE 5后加入,可以显式获取和释放锁(synchrozined则是隐式的)。
标准使用样例:
Lock lock = new ReentrantLock();
lock.lock();
// Do Somthing
lock.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)` 。
```java
public 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;}
@Override
public 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();