虽然【线程同步】中的案例2实现了线程同步,但是我们发现不能人为干涉,即我们不能让两个线程按照我们想要的规则来运行,案例2每次运行结果都可能是不一样的。
<br/>
如果需要人为控制线程的运行规则,就需要考虑到线程之间的通信问题了。
<br/>
如果我们想控制或改变CPU切换线程的随机性,可以通过Java的等待唤醒机制,让多个线程依靠同一个同步锁,借助`wait`和`notify`方法来实现线程之间的协调通信。
<br/>
<mark>多个线程必须使用同一个锁,才能互相通信</mark>。
```java
public class Object {
public final void wait() // 让当前的线程释放锁
public final native void wait(long timeout) // 让当前的线程在timeout毫米后释放锁
public final native void notify() // 唤醒持有同一个锁的某个线程
public final native void notifyAll() // 唤醒持有统一锁的所有线程
...
}
```
注意:`wait`和`notify`都是当前线程已经持有锁的时候,才可以调用,否则抛出异常。
<br/>
**案例3:农夫摘水果问题**
一个农夫摘水果放到果篮中,一个小孩从果篮中拿水果吃。
<br/>
要求实现:如果果篮装满了,农夫停;如果果篮是空的,小孩停;如果果篮既没有满也没有空,则农夫和小孩不得休息。
<br/>
假设:农夫是一个线程、小孩是一个线程、果篮就是这两个线程的锁。
```java
public class TestSyn {
//果篮就是一个锁
public static List<String> baseket = new ArrayList<>();
/**
* 农夫线程
*/
class FarmerSyn implements Runnable {
@Override
public void run() {
for (int i = 0; i < 30; i++) {
//同步块
synchronized (baseket) {
//果篮已满
if (baseket.size() >= 5) {
try {
System.out.println("果篮满了,农夫休息.");
baseket.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//果篮未满
baseket.add("apple");
System.out.println("现在果篮中有" + baseket.size() + "个苹果.");
baseket.notify();
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
/**
* 小孩线程
*/
class ChildrenSyn implements Runnable {
@Override
public void run() {
for (int i = 0; i < 30; i++) {
synchronized (baseket) {
//果篮空了
if (baseket.size() == 0) {
try {
System.out.println("没有水果了,小孩停下.");
baseket.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//果篮未空
baseket.remove("apple");
System.out.println("小孩吃了一个,还剩" + baseket.size() + "个苹果.");
baseket.notify();
}
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
TestSyn testSyn = new TestSyn();
Thread farmerThread = new Thread(testSyn.new FarmerSyn());
Thread childrenThread = new Thread(testSyn.new ChildrenSyn());
farmerThread.start();
childrenThread.start();
}
}
```
某一次运行的结果如下:
```
现在果篮中有1个苹果.
小孩吃了一个,还剩0个苹果.
现在果篮中有1个苹果.
现在果篮中有2个苹果.
小孩吃了一个,还剩1个苹果.
现在果篮中有2个苹果.
现在果篮中有3个苹果.
小孩吃了一个,还剩2个苹果.
......
```
通过调整两个人吃或摘的速度,就能看到不同效果。
当你都将两个线程的`Thread.sleep(200)`调整成一样的时间,这两个线程则是轮着执行的,如下:
```
现在果篮中有1个苹果.
小孩吃了一个,还剩0个苹果.
现在果篮中有1个苹果.
小孩吃了一个,还剩0个苹果.
现在果篮中有1个苹果.
小孩吃了一个,还剩0个苹果.
现在果篮中有1个苹果.
......
```
说明我们可以做到让这两个线程按照我们想要的来运行了。
- 网络通信
- 网络协议
- 端口和套接字
- 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使用步骤