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