**案例1:没有实现线程同步的火车站购票系统**
```java
public class SaleWindow implements Runnable {
//假定总票数为10张
private int num = 10;
@Override
public void run() {
for (int i = 0; i < 10; i++) {
if (num > 0) {
System.out.println(Thread.currentThread().getName() + "买处出了第" + num + "张票.");
num--;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
SaleWindow window = new SaleWindow();
//创建两个线程,表示正有两个窗口在售票
Thread w1 = new Thread(window);
Thread w2 = new Thread(window);
w1.setName("window1");
w2.setName("window2");
w1.start();
w2.start();
}
}
```
运行结果如下:
```
window1买处出了第10张票.
window2买处出了第10张票.
window2买处出了第8张票.
window1买处出了第8张票.
window1买处出了第6张票.
window2买处出了第5张票.
window2买处出了第4张票.
window1买处出了第4张票.
window2买处出了第2张票.
window1买处出了第2张票.
```
可以看到这是不合理的,怎么能让两个窗口卖出的是同一张票。
<br/>
这是因为两条线程彼此独立,但使用相同的资源,不能做到同步更新票数。一条线程操作到一半,cpu被另一条线程抢占,就会出现这个问题,导致线程不安全。
<br/>
如何解决?Java提供了<mark>同步机制(锁)</mark>来解决资源共享问题。让操作共享数据的那段代码,同时只能被一个线程执行(锁住),执行过程中,其它线程不能参与进来。
<br/>
Java为每个对象都自动内置了一个锁,某一个线程执行到某个代码片段时就会自动得到这个对象的锁,所以sync(同步)的实现其实就是声明使用这个对象的锁。
<br/>
实现线程同步的两种基本方式:`synchronized`同步代码块、`synchronized`同步方法。
<br/>
**案例2:实现了线程同步的火车站购票系统**
1. 使用`synchronized`同步代码块实现线程同步。
```java
public class SaleWindowSyncBlok implements Runnable {
//假定总票数为10张
private int num = 10;
/**
* 使用synchronized同步代码块实现线程同步
*/
@Override
public void run() {
for (int i = 0; i < 10; i++) {
//对共享的资源添加同步代码块
synchronized (this) {
if (num > 0) {
System.out.println(Thread.currentThread().getName() + "买处出了第" + num + "张票.");
num--;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
SaleWindowSyncBlok window = new SaleWindowSyncBlok();
//创建两个线程,表示正有两个窗口在售票
Thread w1 = new Thread(window);
Thread w2 = new Thread(window);
w1.setName("window1");
w2.setName("window2");
w1.start();
w2.start();
}
}
```
2. 或者使用`synchronized`同步方法实现线程同步
```java
public class SaleWindowSyncMethod implements Runnable {
//假定总票数为10张
private int num = 10;
@Override
public void run() {
for (int i = 0; i < 10; i++) {
salePacket();
}
}
/**
* 或者使用synchronized同步方法实现线程同步
*/
public synchronized void salePacket() {
if (num > 0) {
System.out.println(Thread.currentThread().getName() + "买处出了第" + num + "张票.");
}
num--;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
SaleWindowSyncMethod window = new SaleWindowSyncMethod();
//创建两个线程,表示正有两个窗口在售票
Thread w1 = new Thread(window);
Thread w2 = new Thread(window);
w1.setName("window1");
w2.setName("window2");
w1.start();
w2.start();
}
}
```
以上两种的结果都如下一样:
```
window1买处出了第10张票.
window1买处出了第9张票.
window1买处出了第8张票.
window2买处出了第7张票.
window2买处出了第6张票.
window2买处出了第5张票.
window2买处出了第4张票.
window2买处出了第3张票.
window2买处出了第2张票.
window2买处出了第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使用步骤