[toc]
# 1. Condition类
ReentrantLock 与 Condition 组合实现线程同步机制,防止线程访问共享资源发生冲突的一个重要原因就是:<mark>当线程已经进入临界区(共享资源区)时才发现,居然还需要满足一定的条件才能够运行</mark>,那么这种情况该如何解决资源分配问题。
<br/>
**1. 组合如下**
```
private Lock myLock = new ReentrantLock();
private Conditoin myCondition = myLock.newCondition();
...
myLock.lock(); // 加锁
...
try {
while(运行条件) {
myCondition.await(); // 如果条件不满足则阻塞线程并解锁
}
...
myCondition.signalAll(); // 解除线程阻塞,通知线程去检测条件是否满足
} finally {
myLock.unlock(); // 解锁
}
```
资源分配规则为:假设有线程A、B、C。
<br/>
当线程A进入临界区发现不满足运行条件,则调用`await`方法使线程A进入等待集中,并释放锁。
<br/>
当线程B进入临界区发现也不满足运行条件,则调用`await`方法使线程B进入等待集中,并释放锁。
<br/>
当线程C进入临界区发现满足运行条件,则不调用`await`方法,而去调用`signalAll`方法,解除线程A和线程B的阻塞状态,等待线程C从临界区出去以后,线程A和线程B通过竞争获取对临界区的访问机会。
<br/>
**2. 意外情况**
假设线程A调用`await`方法后,线程A是无法自动唤醒的,只能是被其他线程调用`signalAll`方法唤醒,否则线程A永远无法被唤醒。
<br/>
该组合可能还会出现一种情况:当最后一个线程在唤醒其他线程前自身也进入了等待集,则整个程序就会挂起了,导致了死锁现象。就是泥菩萨过河,自身难保,谁也救不了了。解决的经验就是:应该在有利于解除其他被阻塞状态的地方调用`signalAll`方法。
<br/>
**3. 案例演示**
下面的程序实现100个线程共同打印一个变量`sum`,打印的条件是`sum`必须是2的倍数,否则进入等待状态。该程序最终还是会出现阻塞。
```
public class LockAndConditionTest {
int sum = 0;
private Lock myLock = new ReentrantLock(); // 创建锁对象
private Condition myCondition = myLock.newCondition(); // 创建条件对象
public LockAndConditionTest() {
for (int i = 1; i <= 100; i++) { // 使用for创建100个线程
Runnable r = () -> {
try {
while (true) {
myLock.lock(); // 加锁
try {
++sum;
while (sum % 2 != 0) {
System.out.println("{线程名: " + Thread.currentThread().getName()
+ ", 状态: 进入等待集, 原因: " + sum + "不是2的倍数}");
myCondition.await(); // 如果不满足运行条件则进入等待状态并释放锁
}
System.out.println("{线程名: " + Thread.currentThread().getName()
+ ", 状态: 通过, 原因: " + sum + "是2的倍数}");
Thread.sleep(100);
myCondition.signalAll(); // 解除所有被阻塞线程的阻塞状态
} finally {
myLock.unlock(); // 解锁
}
}
} catch (InterruptedException e) {
}
};
Thread t = new Thread(r);
t.setName("线程" + i);
t.start(); // 启动线程
}
}
public static void main(String[] args) {
new LockAndConditionTest();
}
}
```
某一次运行的结果如下:
```java
{线程名: 线程97, 状态: 通过, 原因: 192是2的倍数}
{线程名: 线程97, 状态: 进入等待集, 原因: 193不是2的倍数}
{线程名: 线程98, 状态: 通过, 原因: 194是2的倍数}
{线程名: 线程98, 状态: 进入等待集, 原因: 195不是2的倍数}
{线程名: 线程99, 状态: 通过, 原因: 196是2的倍数}
{线程名: 线程99, 状态: 进入等待集, 原因: 197不是2的倍数}
{线程名: 线程100, 状态: 通过, 原因: 198是2的倍数}
{线程名: 线程100, 状态: 进入等待集, 原因: 199不是2的倍数}
{线程名: 线程1, 状态: 进入等待集, 原因: 199不是2的倍数}
{线程名: 线程4, 状态: 进入等待集, 原因: 199不是2的倍数}
{线程名: 线程2, 状态: 进入等待集, 原因: 199不是2的倍数}
{线程名: 线程6, 状态: 进入等待集, 原因: 199不是2的倍数}
----跑到 199 程序就被阻塞了 -----
```
<br/>
# 2. 锁测试与超时
线程调用`lock`方法获取其它线程的锁的时候可能会发生阻塞,为了避免这种情况可以调用Lock类的`tryLock`方法试图申请一个锁,如果申请到锁着返回true,否则返回false,并离开去做其它事。
```
private Lock myLock = new ReentrantLock();
private Condition myCondition = myLock.getCondition();
...
if (myLock.tryLock()) { // 或者myLock.tryLock(100, TimeUnit.MILLISECONDS)
...
try {
...
} finally {
myLock.unlock();
}
} else {
}
```
* java.util.concurrent.locks.Lock 5.0
* Condition newCondition()
返回一个与锁相关条件对象Condition
* java.util.concurrent.locks.Condition 5.0
* void await()
将线程放入到条件等待集中。
* void signalAll();
解除所有在条件等待集中的线程的阻塞状态。
* void signal()
从条件等待集中随机解除一个线程的阻塞状态。相比于`signalAll`方法解除阻塞线程的速度更快,但危险大,因为如果被随机选择的线程依然不能够被解除阻塞,而再无其他线程调用`signal`方法了,那么系统就会死锁了,这就是绝望啊!。
- 网络通信
- 网络协议
- 端口和套接字
- TCP网络程序
- UDP网络程序
- 多线程聊天室
- 多线程
- 线程相关概念
- 线程实现方式
- 中断线程
- 线程生命周期
- 线程优先级
- 优先级规则
- 案例演示
- 线程同步机制
- 线程同步机制
- synchronized关键字
- ReentrantLock类
- Condition类
- 监视器概念
- volatile关键字
- final变量
- 死锁
- 线程局部变量
- 读/写锁
- 原子类
- 阻塞队列
- 工作规则
- 案例演示
- 常用阻塞队列
- 线程安全集合
- 高效的映射/集/队列
- 并发集视图
- 写数组的拷贝
- Arrays类的并行数组算法
- 同步包装器
- Callable与Future
- 执行器
- 线程池
- 预定执行
- 控制任务组
- Fork-Join框架
- 同步器
- 同步器
- 信号量
- CountDownLatch类
- CyclicBarrier类
- Exchanger类
- SynchronousQueue类
- 线程与Swing
- Swing与线程问题
- 两个原则
- Swing工作线程
- 单一线程规则
- 文件IO
- File类
- 文件输入输出
- ZIP压缩文件
- 集合
- 集合框架
- 集合接口
- 集合实现类
- 线程安全集合
- 集合算法
- 迭代器
- 集合排序
- JDBC
- JDBC是什么
- JDBC-ODBC桥
- JDBC驱动程序类型
- JDBC常用类与接口
- 数据库操作
- 连接数据库
- 增/删/改/查/预处理
- 事务
- 批处理
- commons-dbutils工具
- 安全问题
- Jedis
- 使用Jedis操作Redis数据库
- JSON转换
- 使用连接池
- 案例
- 单例破坏
- 单例定义
- 单例实现方式
- 懒汉式实现单例
- 饿汉式实现单例
- 单例破坏
- 类的单例破坏
- 枚举的单例破坏
- 克隆
- 克隆是什么
- 浅克隆
- 深克隆
- 注解
- 注解是什么
- 三大注解
- 内置注解
- 元注解
- 自定义注解
- NIO
- 相关概念
- BIO/NIO/AIO
- 多线程编程
- 线程同步
- 线程通信
- NIO
- NIO三大核心组件
- NIO网络编程
- NIO文件读写
- AIO
- Java8新特性
- Lambda表达式
- 方法引用
- 函数式接口
- 默认方法
- 什么是默认方法
- 默认方法语法格式
- 多个同名的默认方法问题
- 静态默认方法
- 默认方法实例
- Stream
- Stream是什么
- Stream示例
- Optional容器
- 新的日期时间API
- Base64
- SPI
- SPI是什么
- SPI与API的区别
- 常见场景
- 使用SPI需遵循的约定
- SPI使用步骤