1 LockSupport
1.1 LockSupport定义
- LockSupport使用Unsafe类实现的,在rt.jar包内提供的,主要作用是挂起和唤醒线程
- 可以在线程内任意位置让线程阻塞
- 与Thread.suspend()相比,弥补了resume()方法导致线程无法继续执行的情况
- 与Object.wait()相比,不需要先获得某个对象的锁,也不会抛出InterruptedException异常
- 不用担心unpark()方法在park()方法之前执行而导致park()方法永久挂起问题
1.2 LockSupport原理
- LockSupport使用类似信号量机制,为每个线程准备了一个许可,如果许可可用,park()方法会立即返回,并且消费这个许可(许可变为不可用),如果许可不可用就会阻塞,unpark()方法会使得许可可用(和信号量不通的是许可不能累加,不能拥有超过一个许可,永远只有一个),使得unpark()方法操作发生在park()方法之前
1.3 LockSupport主要方法
- LockSupport.park()
- 如果调用park方法的线程已经拿到了与LockSupport关联的许可证,则调用LockSupport.park()时会马上返回,否则调用线程会被禁止参与线程的调度,即阻塞
- 其他线程调用unpark(Thread thread)方法并将当前线程作为参数时,之前调用park方法而被阻塞的线程会返回
- 如果其他线程调用了阻塞线程的interrupt()方法,设置了中断标志或者线程被虚假唤醒,则阻塞线程也会返回,所以调用park方法最好也使用循环条件判断方式
- 因调用park方法而被阻塞的线程被其他线程中断而返回时并不会抛出InterruptedException异常
- LockSupport.unpark(Thread.currentThread());
- 当一个线程调用unpark时,如果参数thread线程没有持有thread与LockSupport类关联的许可证,则让thread持有.
- 如果thread之前因调用park()而被挂起,则调用unpark后,该线程会被唤醒
- 如果thread之前没有调用park,则调用unpark方法后,再调用park方法,其会立刻返回
- LockSupport.parkNanos(long nanos)
- 与park方法类似,不同之处是如果没有拿到许可证,则调用线程会被挂起nano时间后修改为自动返回
2 StampedLock
2.1 StampedLock定义
- 是JDK8新增的锁,提供了三种读写控制,当调用获取锁系列的方法时,会返回long类的变量(stamp),代表了锁的状态. try系列获取锁的方法,当获取锁失败后会返回为0的stamp值. 当调用释放锁和转换锁的方法时需要传入获取锁时stamp值
- StampedLock还支持三种锁(写锁,悲观读锁,乐观读锁)在一定条件下进行互相转换
2.2 写锁WriteLock
- 是一个排他锁/独占锁,且不可重入
- 请求该锁成功后会返回一个stamp变量用来表示该锁的版本,释放该锁时调用unlockWrite方法并传递获取锁时stamp参数.并提供tryWriteLock方法
2.3 悲观读锁ReadLock
- 是一个共享锁,在没有现成获取独占锁的情况下,多个线程可以同时获取该锁.
- 如果有线程持有写锁,则其他线程请求获取该读锁会被阻塞,写锁也不可重入
- 请求该锁成功后会返回一个stamp变量用来表示该锁的版本,当释放该锁时需要调用unlockRead方法并传递stamp参数,并且提供了非阻塞tryReadLock方法
- 适用于读少写多的场景
2.4 乐观读锁tryOptimisticRead
- 是相对于悲观锁readLock来说的,指操作数据前没有通过cas设置锁的状态,仅仅通过位运算测试
- 当前没有线程持有写锁,则简单地返回一个非0的stamp版本,获取stamp后具体操作数据前还需要调用validate方法验证该stamp是否已经不可用(既看当调用tryOptimisticRead返回stamp后看是否有其他线程持有了写锁,如果是返回0,不是则可以使用该stamp进行操作)
- tryOptimisticRead没有使用CAS设置锁状态,所以不需要显示的释放锁
- 适用于读多写少的场景
- 获取读锁只是使用位操作进行检验,不涉及cas操作,所以效率高
- 同时由于没有真正的锁,保证数据一致性上需要复制一份要操作的变量到方法栈,并且在操作数据时可能其他的线程已经修改了数据,而我们操作的方法栈里的数据只是一个快照,所以最多返回的不是最新的数据
3 Condition
3.1 Condition定义
- 与wait()和notify()方法的作用大致相同,wait/notify方法是与synchronized关键字联合使用的,Condition是与ReentrantLock联合使用的
- 在使用Condition.await()方法时,要求线程持有相关重入锁,在Condition.await()方法调用后,这个线程就会释放这把锁
- 在使用Condition.singal()方法时,也要求线程持有相关重入锁,singal()方法调用后,系统会从当前Condition对象的等待队列中唤醒一个线程,一旦线程被唤醒,将会重新尝试获取重入锁,获取成功可正常执行
3.2 Condition主要方法
- await() 使当前线程等待,同时释放当前锁,当其他线程中使用signal()方法或者signalAll()方法时,线程会重新获得锁并继续执行.线程被中断时,也能挑出等待.与object.wait()方法类似
- awaitUninterruptibly()方法与await()基本相同,但不会在等待过程中响应中断
- singal()方法用于唤醒一个在等待中的线程,singalAll()方法会唤醒所有在等待中的线程,与object.notify()方法类似
ReentrantLock lock =new ReentrantLock();
Condition condition = lock.newCondition();
lock.lock();
condition.await();
4 ReentrantLock
4.1 ReentrantLock定义
- ReentrantLock可以完全代替synchronized,JDK5之前重入锁性能远优于synchronized,JDK6开始,两者性能差距不大
- 如果同一个线程多次获得锁,则在释放锁时也必须释放相同次数,释放的多了会有ILLegalMonitorStateException异常,释放的少了线程还会持有这个锁
- 特性有 响应中断,等待限时,公平锁
- 公平锁
- 不会产生饥饿的现象,new ReentrantLock(boolean fair),fair为true时代表公平锁
- 公平锁需要维护一个有序队列,因此实现成本比较高,性能低下
- 默认情况下,可以都使用非公平锁
4.2 ReentrantLock主要方法
- lock() 获得锁,如果占用则等待
- lockInterruptibly()获得锁,可以优先响应中断
- tryLock() 尝试获得锁,如果成功返回true,失败返回false,方法不等待,立即返回
- lock.tryLock(5,TimeUnit.SECONDS);在给定时间内尝试获取锁
- unlock()释放锁
4.3 ReentrantLock中AQS过程
- 假如线程Thread1,Thread2,Thread3同时尝试获取独占锁ReentrantLock,假设Thread1获取到了锁,Thread2/Thread3就会被转换为Node节点并被放入ReentrantLock对应的AQS阻塞队列,而后被阻塞挂起
- 假设Thread1获取锁后调用了对应的锁创建条件变量1,则Thread1就会释放获取到的锁,然后当前线程就会被转换为Node节点插入条件变量1的条件队列.
- 由于Thread1释放了锁,所以阻塞到AQS队列里面的Thread2/Thread3就有机会获取到该锁,假如使用的是公平策略, 那么这时候Thread2会获取到该锁,随后从AQS队列里面移除Thread2对应的Node节点
4.4 ReentrantLock实现原理
- 状态 原子态使用CAS操作来存储当前锁的状态,判断锁是否已经被别的线程持有了
- 等待队列 所有没有请求到锁的线程,都会进入等待队列进行等待.待有线程释放锁后,系统就能从等待队列中唤醒一个线程,继续工作
- 阻塞 使用park和unpark来挂起和恢复线程,没有得到锁的线程会被挂起
private final ReentrantLock takeLock = new ReentrantLock();
private final Condition notEmpty = takeLock.newCondition();
private final ReentrantLock putLock = new ReentrantLock();
private final Condition notFull = putLock.newCondition();
public void put(E e) throws InterruptedException {
if (e == null) throw new NullPointerException();
int c = -1;
Node<E> node = new Node<E>(e);
final ReentrantLock putLock = this.putLock;
final AtomicInteger count = this.count;
putLock.lockInterruptibly();
try {
while (count.get() == capacity) {
notFull.await();
}
enqueue(node);
c = count.getAndIncrement();
if (c + 1 < capacity)
notFull.signal();
} finally {
putLock.unlock();
}
if (c == 0)
signalNotEmpty();
}
public E take() throws InterruptedException {
E x;
int c = -1;
final AtomicInteger count = this.count;
final ReentrantLock takeLock = this.takeLock;
takeLock.lockInterruptibly();
try {
while (count.get() == 0) {
notEmpty.await();
}
x = dequeue();
c = count.getAndDecrement();
if (c > 1)
notEmpty.signal();
} finally {
takeLock.unlock();
}
if (c == capacity)
signalNotFull();
return x;
}
5 ReenReadWriteLock
5.1 ReenReadWriteLock定义
- 是JDK5中提供的读写分离锁,读读=非阻塞,其他=阻塞
- 如果系统中读的次数远远大于写的次数,读写锁就可以发挥最大功效,提升性能
- 读写锁的内部维护了一个ReadLock和WriteLock,依赖Sync实现具体功能,Sync继承自AQS,并且提供公平与非公平的实现
- ReentrantReadWriteLock中state的高16位表示读的次数,低16位表示写的次数
ReentrantReadWirteLock readWriteLock= new ReentrantReadWriteLock();
Lock readLock= readWriteLock.readLock();
Lock writeLock = readWriteLock.writeLock();
5.2 ReenReadWriteLock主要方法
- 如果其他线程已获取了写锁, 则当前线程获取读锁失败,则进入等待状态
- 如果当前线程获取了写锁或写锁以未被获取,则当前线程(线程安全,CAS保证),增加读状态,成功获取读锁
- 读锁的每次释放(线程安全,可能多个线程同时释放读锁)均减少读状态,减少的值为1<<16
- 所以读锁才能实现读读的过程共享,而读写,写读,写写互斥
void lock()
void lockInterruptibly()
boolean tryLock()
boolean tryLock(long timeout,TimeUnit unit)
void unlock()
protected final int tryAcquireShared(int unused) {
Thread current = Thread.currentThread();
int c = getState();
if (exclusiveCount(c) != 0 && getExclusiveOwnerThread() != current)
return -1;
int r = sharedCount(c);
if (!readerShouldBlock() && r < MAX_COUNT &&
compareAndSetState(c, c + SHARED_UNIT)) {
if (r == 0) {
firstReader = current;
firstReaderHoldCount = 1;
} else if (firstReader == current) {
firstReaderHoldCount++;
} else {
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current))
cachedHoldCounter = rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh);
rh.count++;
}
return 1;
}
return fullTryAcquireShared(current);
}