什么是可重入锁,什么是不可重入锁,它们是如何实现的?
定义
可重入锁:当线程获取某个锁后,还可以继续获取它,可以递归调用,而不会发生死锁;
不可重入锁:与可重入相反,获取锁后不能重复获取,否则会死锁(自己锁自己)。
不可重入锁 用代码说话。
基于 wait/notify 实现不可重入锁 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 import java.util.concurrent.locks.ReentrantLock;public class ReentrantForbiddenLock { private Thread owner; public synchronized void lock () throws InterruptedException { Thread thread = Thread.currentThread(); while (owner != null ) { System.out.println(String.format("%s 等待 %s 释放锁" , thread.getName(), owner.getName())); wait(); } System.out.println(thread.getName() + " 获得了锁" ); owner = thread; } public synchronized void unlock () { if (Thread.currentThread() != owner) { throw new IllegalMonitorStateException(); } System.out.println(owner.getName() + " 释放了持有的锁" ); owner = null ; notify(); } public static void main (String[] args) throws InterruptedException { ReentrantForbiddenLock lock = new ReentrantForbiddenLock(); lock.lock(); lock.lock(); } }
第二次调用lock后线程就阻塞了,线程开始等待持有锁的线程放手,然而是它是它就是它。
基于自旋锁实现不可重入锁 自旋锁,即获取锁的线程在锁被占用时,不是阻塞,而是不断循环去尝试,直到获取锁。
好处:线程保持活跃,减少了线程切换的开销
缺点:很消耗CPU,特别是等待时间很长时
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 import java.util.concurrent.atomic.AtomicReference;import java.util.concurrent.locks.ReentrantLock;public class ReentrantForbiddenLock { private AtomicReference<Thread> owner = new AtomicReference<>(); public void lock () { Thread thread = Thread.currentThread(); while (!owner.compareAndSet(null , thread)) { System.out.println(String.format("%s 等待 %s 释放锁" , thread.getName(), owner.get().getName())); } System.out.println(thread.getName() + " 获得了锁" ); } public void unlock () { Thread thread = Thread.currentThread(); if (owner.compareAndSet(thread, null )) { System.out.println(thread.getName() + " 释放了锁" ); return ; } throw new IllegalMonitorStateException(); } public static void main (String[] args) throws InterruptedException { ReentrantForbiddenLock lock = new ReentrantForbiddenLock(); lock.lock(); lock.lock(); } }
如果不想磁盘爆掉,不要在自旋过程中随便打印日志😈
可重入锁 不可重入锁扩展一下,增加一个计数器,同一个线程每次获取锁计数器加1,释放锁减1,为0时释放锁。
基于自旋锁实现可重入锁 直接用上个例子的代码改一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 import java.util.concurrent.atomic.AtomicReference;import java.util.concurrent.locks.ReentrantLock;public class ReentrantForbiddenLock { private AtomicReference<Thread> owner = new AtomicReference<>(); private int count; public void lock () { Thread thread = Thread.currentThread(); if (thread == owner.get()) { count++; System.out.println(thread.getName() + " 再次获得了锁, count = " + count); return ; } while (!owner.compareAndSet(null , thread)) { System.out.println(String.format("%s 等待 %s 释放锁" , thread.getName(), owner.get().getName())); } count = 1 ; System.out.println(thread.getName() + " 获得了锁" ); } public void unlock () { Thread thread = Thread.currentThread(); if (thread == owner.get()) { count--; System.out.println(thread.getName() + " 释放了锁,count = " + count); if (count == 0 ) { owner.set(null ); System.out.println(thread.getName() + " 彻底释放了锁" ); } return ; } throw new IllegalMonitorStateException(); } public static void main (String[] args) throws InterruptedException { ReentrantForbiddenLock lock = new ReentrantForbiddenLock(); lock.lock(); lock.lock(); lock.unlock(); lock.unlock(); } }
可重入锁 synchronized 没错,用于声明同步方法/代码块的synchronized关键字提供的也是一个可重入锁。
同步方法递归测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 public class Main { public static void main (String[] args) throws Exception { new Thread(() -> { lock(5 ); }).start(); Thread.sleep(1000 ); System.out.println("我是主线程,我也要来" ); lock(2 ); } private static synchronized void lock (int count) { if (count == 0 ) { return ; } try { Thread.sleep(1000 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " " + count); lock(count - 1 ); } }
可重入锁 ReentrantLock ReentrantLock是Java中很常见的工具类, 从名字就可以看出,它是个可重入锁,用法也很简单:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 import java.util.concurrent.locks.ReentrantLock;public class Main { public static void main (String[] args) throws Exception { ReentrantLock lock = new ReentrantLock(false ); new Thread(() -> { lock.lock(); System.out.println("A 获取了锁" ); try { Thread.sleep(10000 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("A 释放了锁" ); lock.unlock(); }).start(); new Thread(() -> { System.out.println("B 等待锁" ); lock.lock(); System.out.println("B 获取了锁" ); lock.unlock(); System.out.println("B 释放了锁" ); }).start(); } }