回顾
在上一篇 Java 基础之多线程编程, 我们讲解了多线程的实现, 运行起来似乎也没什么问题, 但是我们若加一段代码
- class Windows implements Runnable{// 实现接口
- int ticket=100;
- @Override
- public void run() {
- while (true){
- if(ticket>0){
- try {
- Thread.currentThread().sleep(10);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println(Thread.currentThread().getName()+"售票: 票号为:"+ ticket--);
- }else{
- break;
- }
- }
- }
- }
- Windows w=new Windows();
- Thread t1=new Thread(w);
- Thread t2=new Thread(w);
- Thread t3=new Thread(w);
- t1.setName("窗口 1");
- t2.setName("窗口 2");
- t1.start();
- t2.start();
相比与上一篇我们的代码里多了
Thread.currentThread().sleep(10);
sleep() 方法使得当前线程阻塞 10 毫秒, 我们看代码运行效果
票卖超了, 怎么回事? 按代码逻辑来看, 好像并没什么问题?
这就是存在 线程不安全
问题分析
那么我们分析下如何出现的这个现象? 我们假设有两个卖票线程: 线程 A 和线程 B , 此时余票还有 1 张, 看下图
文字解释下, 当 ticket=1 时, 线程 A 进入 if 判断内, 接着线程 A 进入 sleep 状态, 紧接着线程 B 获得 CPU 执行权开始执行, 此时 ticket=1, 进入 if 判断内 也开始 sleep, 然后线程 A 的 sleep 结束 恢复, 开始执行, 并把 ticket--, 此时 ticket=0, 然后线程 B 恢复, 打印 tickect 为 0,ticket 再次 - 1. 变成了 - 1. 这就时三个窗口同时卖票, 票卖超的原因, 也称线程不安全.
线程安全
上面我们分析了导致线程不安全出现的原因? 那怎么解决呢?
我们希望一个线程操作共享数据结束以后, 其他的线程才有机会参与共享数据的操作.
线程安全是多线程编程时的计算机程序代码中的一个概念. 在拥有共享数据的多条线程并行执行的程序中, 线程安全的代码会通过同步机制保证各个线程都可以正常且正确的执行, 不会出现数据污染等意外情况.
用 java 代码来实现, 主要有两种方法:
线程的同步机制
方法一 : 同步代码块
格式如下 使用 synchronized 关键字
- synchronized(同步监视器){
- // 需要被同步的代码块 (操作共享数据的代码块)
- }
以上同步监视器 又称为 "锁", 锁需要唯一, 代码如下
- class Windows implements Runnable{// 实现接口
- int ticket=100;
- @Override
- public void run() {
- while (true){
- synchronized (this){
- if(ticket>0){
- try {
- Thread.currentThread().sleep(10);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println(Thread.currentThread().getName()+"售票: 票号为:"+ ticket--);
- }
- }
- }
- }
- }
执行后如下, 正常!
方法二 : 同步方法
将操作共享数据的代码 提取到一个方法内 然后用 synchronized 修饰
- synchronized void show(){
- // 操作共享数据的代码
- }
修改卖票程序用同步方法实现如下:
- class Windows implements Runnable{// 实现接口
- int ticket=100;
- public synchronized void show(){
- if(ticket>0){
- try {
- Thread.currentThread().sleep(10);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println(Thread.currentThread().getName()+"售票: 票号为:"+ ticket--);
- }
- }
- @Override
- public void run() {
- while (true){
- this.show();
- }
- }
- }
注意在同步方法实现中 锁默认为 this 也需要唯一, 我们用图解释下线程安全下两个线程如何操作共享数据的:
由上图我们知道, 一旦遇到操作共享数据时, 线程总是同步执行的.
总结
遇到多个线程操作共享数据时就会出现线程不安全问题
同步方法或者同步代码块 都是为了让线程同步执行
同步方法和同步代码块都需要一个对象 作为锁, 这个锁要确保唯一性
来源: https://juejin.im/post/5c3d541bf265da61380f7136