0%

Java中Lock接口的原理

Lock接口

锁是用来控制多个线程访问共享资源的方式,一般来说,一个锁能够防止多个线程同时访问共享资源(但是有些锁可以允许多个线程并发的访问共享资源,比如读写锁)。

在Lock接口出现之前,Java程序是靠synchronized关键字实现锁功能的,而Java 5之后,并发包中新增了Lock接口(以及相关实现类)用来实现锁功能,它提供了与synchronized关键字类似的同步功能,只是在使用时需要显式地获取和释放锁。虽然它缺少了(通过synchronized块或者方法所提供的)隐式获取释放锁的便捷性,但是却拥有了锁获取与释放的可操作性、可中断的获取锁以及超时获取锁等多种synchronized关键字所不具备的同步特性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class X{
//定义锁对象
private final ReentrantLock lock=new ReentrantLock();
//定义需要保证线程安全的方法
public void m(){
//加锁
lock.lock();
try{
//...method body
}
//使用finally块来保证释放锁
finally{
lock.unlock();
}
}
}

使用Reentrantlock可以进行尝试锁定tryLock(),这样无法锁定,或者在指定时间内无法锁定,返回false;

使用ReentrantLock还可以调用lockInterruptibly()方法,可以对线程interrupt()方法做出响应,在一个线程等待锁的过程中,可以被打断,打断后会抛异常。

自己实现一个锁

自旋实现锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class SpinLock {
//原子引用线程
AtomicReference<Thread> atomicReference = new AtomicReference<>();

public void mylock() {
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName() + "\t come in");
// 自旋获取锁
while (!atomicReference.compareAndSet(null, thread)) {

}
}

public void myUnlock() {
Thread thread = Thread.currentThread();
// CAS解锁
atomicReference.compareAndSet(thread, null);
System.out.println(Thread.currentThread().getName()+"\t invoked myunlock()");
}
}

缺点:耗费CPU资源,没有竞争到锁的线程会一直占用CPU资源进行CAS操作。

park+自旋实现锁

Java提供了一个较为底层的并发工具类:LockSupport,可以让线程停止下来(阻塞),还可以唤醒线程。

1
2
3
4
// 阻塞线程
LockSupport.park(Object blocker)
// 唤醒线程
LockSupport.unpark(Thread thread)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class SpinLock {
// 原子引用线程
AtomicReference<Thread> atomicReference = new AtomicReference<>();
// 阻塞线程队列
Queue<Thread> parkQueue = new LinkedBlockingQueue<>();

public void mylock() {
System.out.println(Thread.currentThread().getName() + "\t come in");
// 自旋获取锁
while (!atomicReference.compareAndSet(null, thread)) {
park();
}
}

public void myUnlock() {
Thread thread = Thread.currentThread();
// CAS解锁
atomicReference.compareAndSet(thread, null);
System.out.println(Thread.currentThread().getName()+"\t invoked myunlock()");
lock_notify();
}

public void park() {
parkQueue.add(Thread.currentThread());
LockSupport.park(Thread.currentThread());
}

public void unpark() {
Thread t = parkQueue.poll();
LockSupport.unpark(t);
}
}

队列同步器AQS

队列同步器AbstractQueuedSynchronizer(AQS)是用来构建锁或者其他同步组件的基础框架,它使用了一个int成员变量表示同步状态,通过内置的FIFO队列来完成资源获取线程的排队工作,并发包的作者(Doug Lea)期望它能够成为实现大部分同步需求的基础。

AQS的实现

FIFO队列

同步器依赖内部的同步队列(一个FIFO双向队列)来完成同步状态的管理,当前线程获取同步状态失败时,同步器会将当前线程以及等待状态等信息构造成为一个节点(Node)并将其加入同步队列,同时会阻塞当前线程,当同步状态释放时,会把首节点中的线程唤醒,使其再次尝试获取同步状态。

AQS中的节点Node:

1
2
3
4
5
6
7
8
9
10
static final class Node {
// 等待状态,若值为-1,表示后继节点处于等待状态
volatile int waitStatus;
// 前一个节点
volatile Node prev;
// 下一个节点
volatile Node next;
// 节点绑定线程
volatile Thread thread;
}

AQS的属性:

1
2
3
4
5
6
7
8
public abstract class AbstractQueuedSynchronizer {
// 等待队列头结点
private transient volatile Node head;
// 等待队列尾结点
private transient volatile Node tail;
// 状态
private volatile int state;
}

​ 未完待续


本文整理自

Java中的锁及AQS实现原理

仅做个人学习总结所用,遵循CC 4.0 BY-SA版权协议,如有侵权请联系删除!