Lock接口
在Lock接口出现之前,java只靠synchronized来实现锁功能。
Lock接口虽然没有了synchronized隐式获得锁的便捷,但是却拥有了锁获取和释放的可操作性,可中断地获取锁以及超时获取锁的synch不具备的同步特性
相比之下,sychronized关键字则在以下几方面存在局限性:
- 隐式锁获取和释放:synchronized块和方法隐式地获取和释放锁,程序员无法显式地控制锁的生命周期。
- 不可中断的锁获取:线程在等待synchronized锁时无法响应中断。
- 无限期等待:如果一个线程尝试进入synchronized块或方法,而锁被其他线程持有,则会一直等待直到锁被释放。
Lock相比syn的主要优点
- 尝试非阻塞地获取锁:尝试非阻塞地获取锁意味着线程会尝试立即获取锁,如果锁不可用,线程不会阻塞或等待,而是立即返回一个失败的状态。tryLock()方法被用来尝试获取锁。如果锁可用,它将返回true并获取锁;否则,它将返回false,而不会阻塞线程。
- 能中断地获取锁:在获取锁的同步队列中可以中断,但线程执行过程中中断不会自动释放锁(finally中释放)。
- 超时获取锁:超时返回失败
Lock的API
lock()
当一个线程调用Lock接口的lock()方法时,如果锁当前不可用,线程会进入阻塞(blocking)状态,并被加入同步队列。直到锁被其他线程释放并且轮到该线程获取锁时,它才会从等待状态中恢复并继续执行。
tryLock()
尝试获取锁,如果锁可用,则获取锁并立即返回true,如果锁不可用,则立即返回false。
unlock()
lockInterruptibly()
获取锁,但可以响应中断。如果当前线程在获取锁的过程中被中断,则会抛出InterruptedException。
newCondition()
队列同步器:AbstractQueuedSynchronizer(AQS)
队列同步器是是用来构建锁或者其他同步组件的基础框架。
同步器的核心成员变量:
abstract class AQS{
private transient volatile Node head;
private transient volatile Node tail;
private volatile int state;
abstract static class Node {
volatile Node prev; // initially attached via casTail
volatile Node next; // visibly nonnull when signallable
Thread waiter; // visibly nonnull when enqueued
volatile int status;
}
}
一个volatile的状态量state
操作这个volatile的state变量有三种方法:
- getState()
- setState(int newState)
- compareAndSetState(int expect ,int update)
一个Node构成的双向list队列(同步队列)
这个list队列使用 CAS(Compare-And-Swap)原子地更新队头队尾元素,而不是阻塞队列。
同步队列的实现:
如果一个线程获取同步失败后,会将当前线程以及等待状态等信息构成一个节点并且将其加入同步队列,同时会阻塞当前线程,只有当它成为首节点并且被唤醒后才能获取同步状态。
线程节点状态信息(即Node中的status):
- cancelled,被取消,即在同步队列中等待的线程超时或者被中断,需要取消。
- signal,如果当前节点释放锁后会通知其Node的next节点使其获得锁。
- condition,此时节点不在同步队列中,而是处于conditionObject内部类的等待队列中,当被唤醒后,会放入同步队列中。
AQS使用模板方法模式,将具体的实现延迟到继承子类中实现
子类需要利用操作volatile的state的方法和操作同步队列的方法来实现线程管理和同步。
子类需要按需实现的模板方法如下:
具体分为:
- 独占式地获取和释放同步状态
- 共享式地获取和释放同步状态
- 查询同步队列中的线程状态
//以下是需要我们重写的方法
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
protected boolean tryRelease(int arg) {
throw new UnsupportedOperationException();
}
protected int tryAcquireShared(int arg) {
throw new UnsupportedOperationException();
}
protected boolean tryReleaseShared(int arg) {
throw new UnsupportedOperationException();
}
protected boolean isHeldExclusively() {
throw new UnsupportedOperationException();
}
//以下是我们使用自定义的同步器实现子类会直接使用的方法
public final void acquire(int arg) {
if (!tryAcquire(arg))
acquire(null, arg, false, false, false, 0L);
}
public final void acquireInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted() ||
(!tryAcquire(arg) && acquire(null, arg, false, true, false, 0L) < 0))
throw new InterruptedException();
}
public final boolean tryAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (!Thread.interrupted()) {
if (tryAcquire(arg))
return true;
if (nanosTimeout <= 0L)
return false;
int stat = acquire(null, arg, false, true, true,
System.nanoTime() + nanosTimeout);
if (stat > 0)
return true;
if (stat == 0)
return false;
}
throw new InterruptedException();
}
实现一个两个资源的共享锁
public class TwinsLock implements Lock {
private final Sync sync=new Sync();
private static class Sync extends AbstractQueuedSynchronizer{
public Sync(int count){
if(count<=0){
throw new IllegalArgumentException("count must large than zero");
}
setState(count);
}
@Override
protected int tryAcquireShared(int arg) {
while(true){
int current=getState();
int newState=current-arg;
if(newState<0||compareAndSetState(current,newState))
return newState;
}
}
@Override
protected boolean tryReleaseShared(int arg) {
while(true){
int current=getState();
int newState=current+arg;
if(compareAndSetState(newState,current))
return true;
}
}
}
@Override
public void lock() {
sync.tryAcquireShared(1);
}
@Override
public void unlock() {
sync.tryReleaseShared(1);
}
}
公平锁和非公平锁
公平锁:加入同步队列时,会判断一下是否是头节点,如果不是则无法加入同步队列。
非公平锁:在加入同步队列前先抢
相比于公平锁,非公平锁开销更小,更少的上下文切换,保证了更大的吞吐。
非公平锁可能造成饥饿,因为如果一直有加入,那么其他同步队列中的线程就无法获取锁。
读写锁
将volatile变量state做了高低位的拆分,高位写锁,地位读锁。
写锁获取必须没有线程持有读锁。
如何实现阻塞或者唤醒一个线程:LockSupport工具(阻塞,等待状态和就绪互相转)
当aqs阻塞和唤醒时,需要使用LockSupport工具来阻塞和唤醒线程。
在 Java 的 AbstractQueuedSynchronizer (AQS) 中,线程进入阻塞状态和从阻塞状态进入就绪状态的机制主要依赖于 LockSupport 类提供的 park 和 unpark 方法。这些方法通过与底层操作系统的线程调度机制交互,控制线程的阻塞和唤醒。
同步队列和阻塞队列具体的实现都是通过 LockSupport 类的 park 和 unpark 方法来挂起和唤醒线程,但在使用上有一些不同,主要体现在使用场景、触发条件和操作对象上。
Condition接口
顶级父类Object上定义了监视器方法,包含wait()和notify()等。这些方法和synch配合可以实现等待/通知模式。
Condition接口也实现了类似Object 的等待/通知模式。
Condition 依赖于 Lock 接口。
- 监视器需要获取监视器锁,而condition需要获取Lock锁,然后调用Lock.newCondition()获取Condition对象。
- object.wait(),condition.await()
- object只有一个等待队列,condition可以有多个
- 都支持释放锁并且进入等待状态,超时等待状态
- object上有一个同步队列和一个等待队列,而一个aqs可以有多个等待队列(即有多个condition)