Java并发编程原理与实战十五:手动实现一个可重入锁
public class Sequence { private MyLock lock = new MyLock(); private int value; public int getNext() { lock.lock(); value++; lock.unlock(); return value; } public static void main(String[] args) { Sequence s = new Sequence(); new Thread(new Runnable() { @Override public void run() { while(true) System.out.println(s.getNext()); } }).start(); new Thread(new Runnable() { @Override public void run() { while(true) System.out.println(s.getNext()); } }).start(); new Thread(new Runnable() { @Override public void run() { while(true) System.out.println(s.getNext()); } }).start(); new Thread(new Runnable() { @Override public void run() { while(true) System.out.println(s.getNext()); } }).start(); new Thread(new Runnable() { @Override public void run() { while(true) System.out.println(s.getNext()); } }).start(); } }
package com.roocon.thread.ta1; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; public class MyLock implements Lock { private boolean isLocked = false; @Override public synchronized void lock() { while (isLocked) {//如果不是第一个进来的线程,就需要等待 try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } isLocked = true;//第一个进来的线程获得锁,不需要等待 } @Override public synchronized void unlock() { isLocked = false; notify();//wait notify 必须和synchronized一起使用 } @Override public void lockInterruptibly() throws InterruptedException { } @Override public boolean tryLock() { return false; } @Override public boolean tryLock(long time, TimeUnit unit) throws InterruptedException { return false; } @Override public Condition newCondition() { return null; } }
运行结果:
1 2 3 4 5 6 7 ...
现在来模拟下,基于以上代码,锁是否可重入:
package com.roocon.thread.ta1; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class Demo { MyLock lock = new MyLock(); public void a() { lock.lock(); System.out.println("a"); b(); lock.unlock(); } public void b() { lock.lock(); System.out.println("b"); lock.unlock(); } public static void main(String[] args) { Demo d = new Demo(); new Thread(new Runnable() { @Override public void run() { d.a(); //输出a,并且一直处于等待状态,程序并未运行结束 } }).start(); } }
分析以上运行结果:
线程1调用a方法,第一次进入lock方法,去获取锁。此时,isLocked为false,于是将标志锁改为true。然后,输出a。再去执行b方法。此时,再次去调用lock方法。lock方法是使用
synchronized修饰的,是可重入的,于是继续执行b方法中的代码。判断isLocked,由于之前进入拿到了锁,因此isLocked为true,于是,会一直等待等待。这就是为什么输出a一直等待的原因。
为了实现可重入锁的效果,改进代码如下:
package com.roocon.thread.ta1; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; public class MyLock implements Lock { private boolean isLocked = false; private Thread lockBy = null; private int lockCount = 0; @Override public synchronized void lock() { if (isLocked && lockBy != Thread.currentThread()) { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } isLocked = true; lockBy = Thread.currentThread(); lockCount++; } @Override public synchronized void unlock() { if (lockBy == Thread.currentThread()) { lockCount--; if (lockCount == 0) { isLocked = false; notify();//wait notify 必须和synchronized一起使用 } } } @Override public void lockInterruptibly() throws InterruptedException { } @Override public boolean tryLock() { return false; } @Override public boolean tryLock(long time, TimeUnit unit) throws InterruptedException { return false; } @Override public Condition newCondition() { return null; } }
运行结果:
a b
解释以上运行结果:
线程1调用a方法,执行lock方法。线程1第一次进入,获得锁,于是,将isLocked设置为true,且lockBy为当前线程Thread1,同时,lockCount=1。
输出a后,再次执行代码调用b方法,synchronized可重入,再次调用b方法中的lock。此时,isLocked为true,但是,只有lockBy和当前线程相等,不满足wait操作条件,因此,
它会再次执行后面的代码,于是,lockCount=2。然后,在输出b之后,它会执行b方法中的unlock,解锁,但是,要明确,只有当线程1将它所有的锁都释放完毕后,才会去通知那些wait等待的线程。因此,需要加入对lockCount的判断,只有lockCount为0时,才将isLock的标志位改为false,同时通知其他线程可以去获取锁了。
参考资料:
《java并发编程与实战》龙果学院
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 因为Apifox不支持离线,我果断选择了Apipost!
· 通过 API 将Deepseek响应流式内容输出到前端