一直在用ReentrantLock跟synchronized加锁,但是没认真去研究过里面具体实现的原理。抽时间看了书籍跟视频,把自己学习的心得记录下来。
首先我们创建一个maven项目,然后新建一个Mylock.java文件,定义一些必要的变量:
/**
* 加锁状态
*/
private volatile int state=0;
/**
* 锁的持有者
*/
private Thread lockHolder =null;
/**
* 获取锁等待队列
*/
private ConcurrentLinkedQueue<Thread> waiters = new ConcurrentLinkedQueue<>();
/**
* unsafe魔法类
*/
private static final Unsafe UNSAFE = UnSafeTool.getUnSafe();
/**
* 锁状态偏移量
*/
private static long stateOffset;
Unsafe这个类是一个特别的类,提供了丰富的原子操作方法,有兴趣的请自行去查询相关资料了解,这里不再作过多介绍.
它是不能直接实例化的,所以需要反射出来,下面是Unsafe实例获取方法
public static Unsafe getUnSafe(){
try{
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
return (Unsafe) f.get(null);
}
catch (Exception e){
return null;
}
}
锁状态偏移量其实就是锁状态state的内存地址.
既然是同步器,肯定是先获取锁.下面是加锁方法定义
/**
* 加锁
*/
public void lock(){
if (acquire()){
System.out.println(Thread.currentThread().getName() + "加锁成功");
return;
}
//加锁失败,则进入等待队列,等待被唤醒,
waiters.offer(Thread.currentThread());
//自旋去获取锁
for (;;){
if (acquire()){
//加锁成功,出队列
System.out.println(Thread.currentThread().getName() + " 加锁成功");
waiters.remove(Thread.currentThread());
return;
}
//阻塞并释放cpu使用权
LockSupport.park();
}
}
没有获取到锁的线程会进行自旋,并使用 LockSupport.park()方法释放cpu使用权.
下面我们再看acquire尝试加锁方法:
/**
* 尝试加锁
* @return
*/
private boolean acquire(){
//先判断锁是否被持有
if(getState() == 0)
{
if(!shouldPark() &&compareAndSwapInt(getState(),1))
{
setLockHolder(Thread.currentThread());
return true;
}
}
//支持可重入
if (Thread.currentThread().equals(getLockHolder())){
int update=getState();
setState(update++);
return true;
}
return false;
}
这里使用Unsafe的CAS更新锁状态来达到原子性修改锁状态,state锁状态我使用了volatile保证了线程之间的可见性.
/**
* CAS更新锁状态
* @param expect 预期值
* @param update 更新后的值
* @return
*/
private boolean compareAndSwapInt(int expect, int update){
return UNSAFE.compareAndSwapInt(this, stateOffset, expect, update);
}
这里就是加锁的所有过程
下面是释放锁的方法定义
/**
* 释放锁
*/
public void unLock()
{
//检验是否是持锁线程
if (Thread.currentThread() != getLockHolder()){
throw new RuntimeException(MessageFormat.format("LockHolder is not current thread, currentThead is {0}," +
"LockHolder thread is {1}!",Thread.currentThread().getName(),getLockHolder().getName()));
}
//清空持锁线程
setLockHolder(null);
//恢复锁状态,必须先清空持锁线程再回复锁状态,否则可能发生:新线程设置持锁线程后,在这儿这里清空了
setState(0);
System.out.println(Thread.currentThread().getName() + " 释放锁");
//唤醒等待队列中第一个线程
Thread first = waiters.peek();
if (first != null){
LockSupport.unpark(first);
System.out.println(Thread.currentThread().getName() + " 唤醒 " + first.getName());
}
}
释放锁比较简单,就是持有锁线程把锁状态设置回0(最原始的状态),清空持有锁,再从唤醒等待队列获取第一个线程对象,并使用LockSupport.unpark唤醒,让它去尝试加锁
下面是这个同步器的用法,跟java 给我们并发工具类ReentrantLock用法差不多,ReentrantLock公平锁跟我们写的这个锁原理差不多。
mylock.lock();
//do something
mylock.unLock();
源码