Java中的锁

Condition接口

synchronized关键字配合Object类提供的wait(), wait(long timeout),notify(), notifyAll()等方法,可以实现等待/通知模式。Condition接口也提供了类似的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public interface Condition {
//进入等待状态,直到其他线程调用signal()或者signalAll()方法进行唤醒。或者其他线程中断当前线程。如果当前线程返回,则已经重新获得锁。
void await() throws InterruptedException;

//不可中断
void awaitUninterruptibly();

//超时后返回,单位为毫秒
long awaitNanos(long nanosTimeout) throws InterruptedException;

//超时后返回,单位为 TimeUnit中的枚举
boolean await(long time, TimeUnit unit) throws InterruptedException;

//超时到将来的具体时间
boolean awaitUntil(Date deadline) throws InterruptedException;

//唤醒一个线程
void signal();

//唤醒所有线程
void signalAll();
}

Lock接口

synchronized进行加锁时,都是隐式加锁,不容易因为释放锁导致出错,Lock接口需要显式加锁,更加灵活。Lock接口具备synchronized关键字所不具备的灵活性:

  • 超时加锁,在指定时间内如果尚未获取到锁,返回false,如果在超时等待的时间内被中断,则抛出异常,获取到锁则返回true。
  • 可以响应中断,在线程获取锁的过程中,可以响应中断,避免死锁发生。
  • 非阻塞的获取锁,使用synchronized加锁,如果没有获得锁,则会进入阻塞状态,Lock加锁则是进入等待状态。

AbstractQueuedSychronizer

AQS的设计是基于模版方法mo模式的,使用者需要继承AQS,重写指定的方法,然后组合到同步组件当中使用。同步组件对外提供的调用方法,可以委托给AQS子类具体执行。

AQS的使用

同步器提供了三个方法,可以线程安全的访问同步的状态。

1
2
3
4
5
6
7
8
9
10
11
12
13
private volatile int state;

protected final int getState() {
return state;
}

protected final void setState(int newState) {
state = newState;
}

protected final boolean compareAndSetState(int expect, int update) {
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}

具体同步组件只需视情况实现以下方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//独占式获取同步状态,在具体实现中,需要原子判断当前state是否符合预期(为旧值,其他线程未修改),如果符合,将状态设置为新值。
protected boolean tryAcquire(long arg) {
throw new UnsupportedOperationException();
}

//释放同步状态
protected boolean tryRelease(long arg) {
throw new UnsupportedOperationException();
}

//共享式获取
protected long tryAcquireShared(long arg) {
throw new UnsupportedOperationException();
}

//共享式释放
protected boolean tryReleaseShared(long arg) {
throw new UnsupportedOperationException();
}

//判断当前状态是否被独占
protected boolean isHeldExclusively() {
throw new UnsupportedOperationException();
}

同步组件对外提供了一些模版方法,供外部查询和操作同步状态,这些方法可以支持超时和中断的独占式获取和共享式获取同步状态。值得注意的是,这些方法都已经被final修饰,不可重写。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}

public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}

AQS的实现

同步队列

同步器依赖内部的同步队列来完成同步状态的管理,当前线程获取同步状态失败时,会将当前线程以及等待状态信息构成一个Node加入到队尾,同时会阻塞当前线程,当同步状态被释放时,会从队列首部唤醒节点中的线程,使其尝试获取同步状态。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
NOde的部分字段
static final class Node {
volatile int waitStatus;

volatile Node prev;

volatile Node next;

volatile Thread thread;

Node nextWaiter;


}

AQS使用同步队列维护获取同步状态失败而阻塞的的线程,head指向头节点,每次获取状态失败的线程构成节点以后加入队列尾部。首节点是获取到同步状态的线程,当其释放同步状态时,会将首节点设置为其后继节点。

tail.png

1
2
3
4
5
6
7
8
9
10
private final boolean compareAndSetHead(Node update) {
return unsafe.compareAndSwapObject(this, headOffset, null, update);
}

/**
* CAS tail field. Used only by enq.
*/
private final boolean compareAndSetTail(Node expect, Node update) {
return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
}
文章作者: hohnor
文章链接: http://www.zhulk3.cn/2022/02/10/Java中的锁/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 甜茶不贵