Java 基础(四)并发类
1、并发类
有以下几种重要的并发:ConcurrentHashMap,ReentrantLock(可重入锁)、ReentrantReadAndWriteLock(可重入读写锁)
1.1 ConcurrentHashMap
jdk1.7 中 ConcurrentHashMap 实现原理
采用了段锁 + 二次哈希的方式提供并发访问,但是由于其还是以数组+链表的形式,查询效率低,并发度也不高,这种实现机制在 JDK1.8 中被替换了
JDK 1.8 中 ConcurrentHashMap 实现原理
使用了 volatile + synchronized + CAS 来进行实现
扩容使用了多线程扩容机制
扩容数量和 HashMap 保持一致,每次扩容为原来的两倍
ConcurrentHashMap#computeIfAbsent 方法存在死循环的 bug 风险
参考:
https://www.cnblogs.com/huangjuncong/p/9478505.html
1.2 ReentrantLock(可重入锁)
ReentrantLock 是 Lock 接口的子类。
关于 Lock
接口
Lock 是整个 JDK 锁相关的父接口,定义了锁的操作原理和注意事项。
Lock.lockInterruptibly() 说明:
当两个线程 A、B 同时使用 lock.lockInterruptibly() 方法去获取锁的时候,如果 A 获得锁,则 B 进入等待停止状态,此时其他线程调用 B 线程的 interrupt 方法可以使线程 B 中断,从而运行终止。(如果是 B 获取了锁,则调用 B.interrupt 无效)
示例代码如下:
查看代码
package com.test.root.base.thread; import java.io.IOException; import java.util.HashMap; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class ReentrantLockTest { public static Lock lock = new ReentrantLock(); public static void main(String[] args) { Thread a = new Thread(() -> { try { lock.lockInterruptibly(); for (;;); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } }, "Thread-A"); a.start(); try { Thread.sleep(100); } catch (InterruptedException e) { throw new RuntimeException(e); } Thread b = new Thread(() -> { try { try { lock.lockInterruptibly(); for (;;); } catch (InterruptedException e) { e.printStackTrace(); } } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } }, "Thread-B"); b.start(); HashMap<String, Thread> map = new HashMap<>(); map.put("A", a); map.put("B", b); try { while (true) { byte[] bytes = new byte[1024]; int l = System.in.read(bytes); printThreadState(map.get("B")); printThreadState(map.get("A")); map.get("B").interrupt(); } } catch (IOException e) { throw new RuntimeException(e); } } public static void printThreadState(Thread thread) { System.out.println(thread.getName() + ": " + thread.getState()); } }
在命令行随意输入两次 1,可以看到运行结果如下,第一次:当 B 处于了等待状态,然后主线程调用了 B.interrupt() 方法,使得 B线程从等待状态变成了终止状态,同时抛出了 InterruptedException 异常。
1 Thread-B: WAITING Thread-A: RUNNABLE java.lang.InterruptedException at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:898) at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1222) at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335) at com.test.root.base.thread.ReentrantLockTest$1.run(ReentrantLockTest.java:36) at java.lang.Thread.run(Thread.java:748) Exception in thread "Thread-B" java.lang.IllegalMonitorStateException at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:151) at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1261) at java.util.concurrent.locks.ReentrantLock.unlock(ReentrantLock.java:457) at com.test.root.base.thread.ReentrantLockTest$1.run(ReentrantLockTest.java:44) at java.lang.Thread.run(Thread.java:748) 1 Thread-B: TERMINATED Thread-A: RUNNABLE
Lock 接口各个方法说明:
- void lock():获取锁,如果没有获取到锁,则该线程会进入 停止等待状态(WAITING)。此处于使用 synchronized 关键词不同,使用 synchronized 未获取到锁时候线程进入阻塞(BLOCK)状态
- void lockInterruptibly() throws InterruptedException:获取锁,如果没有获取到锁,则该线程会进入 停止等待状态(WAITING),此时其他线程可以使用 interrupt 方法终止
获取锁
同时在获取锁方法处抛出异常,这个异常需要自己去捕获处理,不然的话会造成线程终止。 - void tryLock():尝试获取锁,如果成功获取的话,则返回 true,如果没有获取到锁返回 false,不会阻塞线程
- boolean tryLock(long time, TimeUnit unit) throws InterruptedException:尝试获取锁,并且设置最大等待时间。此方法可以被中断(即其他线程调用该线程的 interrupt 方法)。
- void unlock():解锁。如果锁不存在则会抛出 IllegalMonitorStateException 异常。
- Condition newCondition():适用于线程间的协同,可以主动释放锁,等待后续重新获取锁之后继续执行,因此,获取锁的线程可以执行所有代码逻辑或者在某一处按条件停止等待。
Condition 代码示例:
查看代码
package com.test.root.base.thread; import cn.hutool.core.date.DateUtil; import java.io.IOException; import java.util.Date; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; public class ConditionTest { public static ReentrantLock lock = new ReentrantLock(); public static void main(String[] args) { Thread a = new Thread(() -> { print("尝试获取锁"); if (lock.tryLock()) { print("成功获取锁"); try { sleep(5); Condition condition = lock.newCondition(); try { print("主动进入等待,等待 5 秒"); // 等待的含义: // 如果重新获取到线程,耗时超过设置值返回 false,没有超过返回 true,如果没有重新获取锁,会一直停止等待 // 上述是 JDK 的文档解释,不过此处我没有复现,返回值一直是 false boolean flag = condition.await(5, TimeUnit.SECONDS); if (!flag) { print("重新获取锁超过时间了"); } print("重新获取到了锁,继续执行"); while (true) ; } catch (InterruptedException e) { throw new RuntimeException(e); } } finally { lock.unlock(); } } else { print("获取锁失败"); } }, "Thread-A"); a.start(); sleep(1000); Thread b = new Thread(() -> { print("尝试获取锁"); lock.lock(); print("成功获取锁"); try { sleep(10); // sleep(3000); } finally { print("释放锁"); lock.unlock(); } }, "Thread-B"); b.start(); while (true) { try { System.in.read(new byte[1024]); b.interrupt(); printThreadState(a); printThreadState(b); } catch (IOException e) { throw new RuntimeException(e); } } } public static void printThreadState(Thread thread) { System.out.println(thread.getName() + ": " + thread.getState()); } public static void print(String msg) { String time = DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss:SSS"); System.out.println(time + " " + Thread.currentThread().getName() + ": " + msg); } public static void sleep(long l) { try { Thread.sleep(l); } catch (InterruptedException e) { throw new RuntimeException(e); } } }
代码中用到的 Jar 包依赖
<dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.2.0</version> </dependency>
ReentrantLock 类图:
内部(分为公平锁和非公平锁)加锁:主要还是使用 CAS + 同步队列(ABS)来实现的,大致流程如下(没有细化到源码级别,只要是用于理解):
公平锁和非公平锁的区别在于:公平锁先检查同步队列中是否有等待线程,非公平锁会先去尝试竞争锁,如果竞争失败,则走公平锁相同逻辑。
提示:ReentrantLock 不是自旋锁
参考:
AQS与ReentrantLock详解:https://juejin.cn/post/7031527716159488007
公平锁和非公平锁:https://cloud.tencent.com/developer/article/2165891
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)
2015-11-19 Java 调用 C++ (Java 调用 dll)