前置思想
首先明确一点ReentrantLock与synchronized最大的不同点在于synchronized是锁住了对象的头部,而ReentrantLock是类似于我们买一把锁,锁住我们不想因为多线程下导致资源的原子性 ,可见性的问题,在明确这一点时我们的锁其实与生活中的所没太大区别,生活中的锁无非就是大门挂一把锁,这样我们就告诉别人此处以上锁,唯一不同的是程序锁必须由上锁的人来解锁,而生活中我们可以持有别人的锁进行开锁。
主要使用到的类
- AtomicInteger:主要保证此锁的原子性与可见性,以及涉及到后面的可重入锁的状态,注:volatile只能保证资源的可见性,并不能保证原子性
- Thread:当前那个线程持有这把锁【PS:也就是谁持有了锁,此处到后面可重入锁,以及解锁所需要的】
- LinkedBlockingQueue:没抢到锁对象的容器
- LockSupport:切换线程状态
交代完了开怼!!!!
/**
* 线程状态定义
*/
AtomicInteger state = new AtomicInteger();
/**
* 那个线程持有锁
*/
Thread ownerThread = null;
/**
* 存放线程的容器
*/
LinkedBlockingQueue<Thread> waiters = new LinkedBlockingQueue<>();
这步没啥可说的继续!!
public boolean tryLock() {
//不用每次都进行CAS操作,如果当期状态为0我直接返回抢锁成功
if (state.get() == 0) {
//CAS抢锁
if (state.compareAndSet(0, 1)) {
//将抢到锁的线程设定为当前锁的持有者
ownerThread = Thread.currentThread();
return true;
}
} else if (ownerThread == Thread.currentThread()) {
System.out.println("重入锁成功!!!!");
state.set(state.get() + 1);
return true;
}
return false;
}
此处主要用到了CAS机制,如果修改成功证明已抢到锁,失败则加入队列,以及重入锁如果是当前线程那么获取锁成功,并且在状态+1【PS:此处状态+1主要是为了可重入锁,应为重入锁的本质需要你加锁几次就要释放几次】
public void lock() {
//真公平锁 如果没有!waiters.isEmpty(),在锁的上一个持有者他也会抢
if (!waiters.isEmpty() || !tryLock()) {
//没抢到锁进入等待队列
waiters.add(Thread.currentThread());
for (; ; ) {
if (tryLock()) {
//移除并且返回队列头部
waiters.poll();
return;
} else {
//没抢到 进入阻塞(WAITING)
LockSupport.park();
}
}
}
}
如果当前没有线程持有锁则直接返回,有则将他加入队列并且阻塞【PS:当然一些猿认为使用LinkedBlockingQueue不就已经是公平锁了么(先入先出),其实不然比如:有三个线程,1抢锁成功,在释放后其实1也参与抢锁了,也就是插队!!此处的插队就导致看似公平的锁变的不公平了】
public void unlock() {
//如果不是当前线程那么不允许释放锁
if (Thread.currentThread() != ownerThread) {
throw new RuntimeException("不是你的别乱动,你不是锁的持有者");
}
//释放锁的过程,每次过来减一
if (state.decrementAndGet() == 0) {
ownerThread = null;
//获取第一个等待的元素并且通知他开始抢锁了,此时还不能移除
//在真正抢到锁后才能移除队列
Thread waiter = waiters.peek();
if (waiter != null) {
LockSupport.unpark(waiter);
}
}
}
此处我们释放锁的时候必须是当前持有锁的线程,如果其他线程来释放锁呢就乱套了
PS:如有不对的地方请大手不吝啬指出