今天讲reentrantlock源码,讲解的思路是先自己手写最简单lock,然后一步一步增加功能,然后再去看源码
目录
手写版本1,最基础的lock,抢不到锁就自旋
先写基础部分,获取unsafe类和stateOffset,这个主要是也用于CAS操作
private final static Unsafe unsafe = UnsafeUtils.getUnsafe();//1. 获取unsafe类
private final static long stateOffset; //2. 定义偏移量
static {
try {
stateOffset = unsafe.objectFieldOffset
(MyLock2.class.getDeclaredField("state"));
} catch (NoSuchFieldException e) {
throw new Error(e);
}
}
接着写lock方法和unlock方法,并且定义一个int类型的变量state当做锁
private int state = 0;
public void lock() {
//抢锁
if (!unsafe.compareAndSwapInt(this, stateOffset, 0, 1)) {
//抢锁失败自旋,这里睡眠100ms是为了让出cpu,不然一直抢占着cpu是不行的
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void unlock() {
//放锁
state = 0;
}
这样,一个最最简单的lock就完成了,让我们测试一下
public static void main(String[] args) throws InterruptedException {
RenhaiLock lock = new RenhaiLock();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 100000; i++) {
// lock.lock();
k++;
// lock.unlock();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 100000; i++) {
// lock.lock();
k++;
// lock.unlock();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(k);
}
输出结果
Connected to the target VM, address: '127.0.0.1:65421', transport: 'socket'
112277
Disconnected from the target VM, address: '127.0.0.1:65421', transport: 'socket'
Process finished with exit code 0
然后再看加上lock的测试结果
public static void main(String[] args) throws InterruptedException {
RenhaiLock lock = new RenhaiLock();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 100000; i++) {
lock.lock();
k++;
lock.unlock();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 100000; i++) {
lock.lock();
k++;
lock.unlock();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(k);
}
测试结果
Connected to the target VM, address: '127.0.0.1:59131', transport: 'socket'
200000
Disconnected from the target VM, address: '127.0.0.1:59131', transport: 'socket'
Process finished with exit code 0
测试结果证明我们的lock生效了,但是这个版本的lock存在一个问题,就是当抢不到锁的时候会进入自旋,如果抢到锁的任务耗时太长,那么就会浪费很多cpu资源,所以我们要增加一个队列用于存放抢不到锁的线程,并且用LockSupport.park()方法阻塞它,然后解锁的时候去唤醒队列中的线程,这样在获取锁线程执行时间再长也不会浪费资源,因为抢不到锁的线程是在队列中阻塞着的.
手写版本2,抢不到锁就把自己放入队列并且阻塞自己
lock方法的改动
unlock方法的改动
这么一改,当任务执行时间过长也不怕,等待队列中的线程是被阻塞的,不会浪费cpu资源
版本三,增加可重入功能
整体思路:增加一个变量存放持有锁的线程,当持有锁的线程获取锁时直接把state+1,持有锁的线程要释放锁的时候就把state-1
private int state = 0;
private LinkedBlockingDeque<Thread> waiters = new LinkedBlockingDeque<>();
//持有锁的线程
private Thread holdThread;
public void lock() {
if (!tryLock()) { // 先抢锁,抢锁成功直接退出,抢锁失败加入则等待队列
waiters.add(Thread.currentThread()); // 抢锁失败,加入等待队列中
while (true) { // 自旋 进入:抢锁 -> 抢锁失败 -> 阻塞自己 -> 抢锁 的循环
if (tryLock()) { // 抢锁
waiters.poll(); // 抢锁成功,把自己从等待队列中移除
return; // 退出循环
}
LockSupport.park(); // 抢锁失败,阻塞自己,等待被唤醒
}
}
}
public boolean tryLock() {
Thread current = Thread.currentThread();
if (holdThread != null && holdThread == current) {
state++; // 如果获取锁的线程尝试再次获取锁,则state+1
return true;
}
if (unsafe.compareAndSwapInt(this, stateOffset, 0, 1)) {
holdThread = current; // 获取到锁就把holdThread=当前线程
return true;
}
return false;
}
public void unlock() {
state--;// 释放锁,直接把state-1
if (state == 0) {
//唤醒队列中第一个线程
Thread peek = waiters.peek();
LockSupport.unpark(peek);
}
}
测试代码
public static void main(String[] args) throws InterruptedException {
RenhaiLockV2Reentrant lock = new RenhaiLockV2Reentrant();
Thread t1 = new Thread(() -> {
lock.lock();
logger.info("抢到锁");
lock.lock();
logger.info("抢到锁");
lock.unlock();
logger.info("解锁");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
lock.unlock();
logger.info("解锁");
});
Thread t2 = new Thread(() -> {
lock.lock();
logger.info("抢到锁");
lock.unlock();
});
t1.start();
t2.start();
t1.join();
t2.join();
}
测试结果,线程1是等线程0释放两次锁才抢到锁的,释放第二次锁之前线程阻塞1秒钟特意给线程1留出时间,然而那时候线程0还没释放完毕,还持有一把锁,所以即使阻塞一秒钟也没办法抢到锁
Connected to the target VM, address: '127.0.0.1:51910', transport: 'socket'
12:37:48.362 [Thread-0] INFO com.lz.demo.concurrent.renhai.RenhaiLockV2Reentrant - 抢到锁
12:37:48.366 [Thread-0] INFO com.lz.demo.concurrent.renhai.RenhaiLockV2Reentrant - 抢到锁
12:37:48.366 [Thread-0] INFO com.lz.demo.concurrent.renhai.RenhaiLockV2Reentrant - 解锁
12:37:49.369 [Thread-0] INFO com.lz.demo.concurrent.renhai.RenhaiLockV2Reentrant - 解锁
12:37:49.369 [Thread-1] INFO com.lz.demo.concurrent.renhai.RenhaiLockV2Reentrant - 抢到锁
0
Disconnected from the target VM, address: '127.0.0.1:51910', transport: 'socket'
Process finished with exit code 0