JDK中可以使用ReentrantLock类来实现可重入锁,其功能更加丰富,使用起来更为灵活,也更适合复杂的并发场景。下面来自己实现以下可重入锁。原理一样。
2. ReentrantLock
- 不可重入锁
不可重入锁,锁不可延续使用。
public class LockTest1 {
Lock lock=new Lock();
public void a() throws InterruptedException {
lock.lock();
doSomething();
lock.unlock();
}
//不可重入锁
public void doSomething() throws InterruptedException {
lock.lock();
//
lock.unlock();
}
public static void main(String[] args) throws InterruptedException {
LockTest1 test1=new LockTest1();
test1.a();
test1.doSomething();
}
}
class Lock{
//是否占用
private boolean isLock=false;
//使用锁
public synchronized void lock() throws InterruptedException {
while (isLock){
wait();
}
isLock=true;
}
//释放锁
public synchronized void unlock(){
isLock=false;
notify();
}
}
如此代码中,方法a()首先得到得到锁,然后调用方法doSomething(),方法doSomething()中有lock.lock(),也需要上锁,然后就等待a()方法执行完,方法a()必须执行完doSomething()方法才会运行下一条语句unlock()方法释放锁,同时方法doSomething()等方法a()运行完释放锁,就形成了死循环。永远不会停止,永远等待。上述就是不可重入锁。
- 可重入锁
锁可以延续使用
public class LockTest2 {
ReLock lock=new ReLock();
public void a() throws InterruptedException {
System.out.println(Thread.currentThread().getName());
lock.lock();
System.out.println(lock.getHoldCount());
doSomething();
lock.unlock();
System.out.println(lock.getHoldCount());
}
//不可重入锁
public void doSomething() throws InterruptedException {
System.out.println(Thread.currentThread().getName());
lock.lock();
System.out.println(lock.getHoldCount());
//
lock.unlock();
System.out.println(lock.getHoldCount());
}
public static void main(String[] args) throws InterruptedException {
LockTest2 test1=new LockTest2();
test1.a();
Thread.sleep(1000);
System.out.println(test1.lock.getHoldCount());
}
}
class ReLock{
//是否占用
private boolean isLock=false;
Thread lockedBy=null;//储存线程//如果自身就不等了
private int holdCount=0;//计数器
public int getHoldCount() {
return holdCount;
}
//使用锁
public synchronized void lock() throws InterruptedException {
Thread thread = Thread.currentThread();//加锁时识别当前线程
System.out.println(Thread.currentThread().getName());
while (isLock &&lockedBy!=thread){//不是自己的线程就等待
wait();
}
isLock=true;
lockedBy=thread;
holdCount++;
}
//释放锁
public synchronized void unlock(){
System.out.println(Thread.currentThread().getName());
if(Thread.currentThread()==lockedBy){//当前线程等于自身的时候释放
holdCount--;//计数器减一
if(holdCount==0) {
isLock = false;
notify();
lockedBy=null;
}
}
}
}
运行结果:
可重入锁中引入了计数器,上锁就会加一,解除锁减一,直到计数器为零,完全释放锁。
- 上锁的时候先识别当前的线程,一开始isLock为false,则不进行while循环,下一步isLock改为true,计数器加一,此时就完成了第一次上锁;等第二次上锁的时候,isLock已经是true了,此时判断线程是不是当前线程,是当前线程则继续上锁,计数器加一,不用等待。后面的同理。在此例子中通过输出当前线程名,都是main线程,线程相同。所以可以完成加锁减锁。
- 释放锁的时候同理判断是否是当前锁,是则计数器减一,释放锁,并通过notify()方法唤醒被wait()等待的线程,将lockBy置为空。
它和不可重入锁的设计不同之处:
- 不可重入锁:只判断这个锁有没有被锁上,只要被锁上申请锁的线程都会被要求等待。实现简单
- 可重入锁:不仅判断锁有没有被锁上,还会判断锁是谁锁上的,当就是自己锁上的时候,那么他依旧可以再次访问临界资源,并把加锁次数加一。
- 设计了加锁次数,以在解锁的时候,可以确保所有加锁的过程都解锁了,其他线程才能访问。不然没有加锁的参考值,也就不知道什么时候解锁?解锁多少次?才能保证本线程已经访问完临界资源了可以唤醒其他线程访问了。实现相对复杂。