java 一步一步教你手写ReentrantLock

前言

我们都知道,ReentrantLock 是我们java中经常使用到的锁,他的内部使用cas机制来进行实现,相比于synchronized 同步关键字,在早期版本有很大的性能提升,首先我们看一下synchronized的实现原理并且进行一下对比

synchronized实现原理

实现原理: JVM 是通过进入、退出对象监视器( Monitor )来实现对方法、同步块的同步的。

具体实现是在编译之后在同步方法调用前加入一个 monitor.enter 指令,在退出方法和异常处插入 monitor.exit 的指令。

其本质就是对一个对象监视器( Monitor )进行获取,而这个获取过程具有排他性从而达到了同一时刻只能一个线程访问的目的。

而对于没有获取到锁的线程将会阻塞到方法入口处,直到获取锁的线程 monitor.exit 之后才能尝试继续获取锁。

使用 javap -c Synchronize 可以查看编译之后的具体信息。


public class com.crossoverjie.synchronize.Synchronize {
public com.crossoverjie.synchronize.Synchronize();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return

public static void main(java.lang.String[]);
Code:
0: ldc #2 // class com/crossoverjie/synchronize/Synchronize
2: dup
3: astore_1
**4: monitorenter**
5: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
8: ldc #4 // String Synchronize
10: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V

13: aload_1
**14: monitorexit**
15: goto 23
18: astore_2
19: aload_1
20: monitorexit
21: aload_2
22: athrow
23: return
Exception table:
from to target type
5 15 18 any
18 21 18 any
}
/<init>

可以看到在同步块的入口和出口分别有 monitorenter,monitorexit 指令。

锁优化

synchronized 很多都称之为重量锁,JDK1.6 中对 synchronized 进行了各种优化,为了能减少获取和释放锁带来的消耗引入了偏向锁和轻量锁。

而我们今天要实现的就是一种轻量级锁的机制,那么我们直奔主题

首先我们实现锁,就要implements Lock接口

@Override
public void lock() {

}
@Override
public void unlock() {
}

既然要实现锁,那么我们必须要知道当前锁的状态,所以,我们使用一个原子引用对当前持有锁的线程进行控制,若为nullptr 则认为当前锁没有线程持有

AtomicReference<thread> owner = new AtomicReference<>(null);
/<thread>

初始化为null

当我们要对线程加锁的时候,我们将其设置为要加锁的线程

当我们要对线程释放锁的时候。我们将其设置为nullper

@Override
public void lock() {
owner.set(Thread.currentThread());
}
@Override
public void unlock() {
owner.set(null);
}

目前变成这样了,但是现在出现了一个问题,加锁时候不能判断owner是否有线程持有,如果有线程持有就不能加锁,只能等待,同时,释放锁也有这样的问题,如果当前线程不是锁的持有者,那么就是给其他线程的锁给释放了,所以我们要事先判断

@Override
public void lock() {
if (owner.get() == null) {

owner.set(Thread.currentThread());
}
}
@Override
public void unlock() {
if (owner.get() == Thread.currentThread()) {
owner.set(null);
}
}

这样的话,我们就可以在对其加锁前进行判断是否能够加锁,但是这样的代码却出现了线程安全的问题,因为判断和赋值并不是原子操作,所以,我们需要利用atomic类特性进行进一步改造

@Override
public void lock() {
if (owner.compareAndSet(null,Thread.currentThread()))
{
//获取锁成功
}
else
{
//获取锁失败
}
}
@Override
public void unlock() {
if (owner.compareAndSet(Thread.currentThread(),null))
{
//释放琐成功
}
else
{
//释放锁失败
}
}

这样通过compareandset机制可以保证一个操作的原子性,但是如果获取锁失败,我们需要进行一个等待,等待上一个线程释放锁之后进行一个尝试,那么我们就需要一个队列拉进行线程的保存

else {
waitqueue.offer(Thread.currentThread());
LockSupport.park();
//获取锁失败
}j

将线程存入队列,使用park阻塞,等待唤醒

if (owner.compareAndSet(null, Thread.currentThread())) {
//获取锁成功
} else {
waitqueue.offer(Thread.currentThread());
LockSupport.park();
lock();
//获取锁失败
}
@Override
public void unlock() {
if (owner.compareAndSet(Thread.currentThread(), null)) {
LockSupport.unpark(waitqueue.poll());
} else {
//释放锁失败,只有一种可能,当前线程不持有锁
}
}

这样的话我们的线程就可以正确的唤醒了,很简单是吧


分享到:


相關文章: