编程进阶网编程进阶网
  • 基础组成体系
  • 程序编程原理
  • 异常和IO系统
  • 六大设计原则
  • 设计模式导读
  • 创建型设计模式
  • 结构型设计模式
  • 行为型设计模式
  • 设计模式案例
  • 面向对象思想
  • 基础入门
  • 高级进阶
  • JVM虚拟机
  • 数据集合
  • Java面试题
  • C语言入门
  • C综合案例
  • C标准库
  • C语言专栏
  • C++入门
  • C++综合案例
  • C++专栏
  • HTML
  • CSS
  • JavaScript
  • 前端专栏
  • Swift
  • iOS入门
  • 基础入门
  • 开源库解读
  • 性能优化
  • Framework
  • 方案设计
  • 媒体音视频
  • 硬件开发
  • Groovy
  • 常用工具
  • 大厂面试题
  • 综合案例
  • 网络底层
  • Https
  • 网络请求
  • 故障排查
  • 专栏
  • 数组
  • 链表
  • 栈
  • 队列
  • 树
  • 递归
  • 哈希
  • 排序
  • 查找
  • 字符串
  • 其他
  • Bash脚本
  • Linux入门
  • 嵌入式开发
  • 代码规范
  • Markdown
  • 开发理论
  • 开发工具
  • Git管理
  • 百宝箱
  • 开源协议
  • 技术招聘
  • 测试经验
  • 职场提升
  • 技术模版
  • 关于我
  • 目标清单
  • 学习框架
  • 育儿经验
  • 我的专栏
  • 底层能力
  • 读书心得
  • 随笔笔记
  • 职场思考
  • 中华历史
  • 经济学故事
  • 基础组成体系
  • 程序编程原理
  • 异常和IO系统
  • 六大设计原则
  • 设计模式导读
  • 创建型设计模式
  • 结构型设计模式
  • 行为型设计模式
  • 设计模式案例
  • 面向对象思想
  • 基础入门
  • 高级进阶
  • JVM虚拟机
  • 数据集合
  • Java面试题
  • C语言入门
  • C综合案例
  • C标准库
  • C语言专栏
  • C++入门
  • C++综合案例
  • C++专栏
  • HTML
  • CSS
  • JavaScript
  • 前端专栏
  • Swift
  • iOS入门
  • 基础入门
  • 开源库解读
  • 性能优化
  • Framework
  • 方案设计
  • 媒体音视频
  • 硬件开发
  • Groovy
  • 常用工具
  • 大厂面试题
  • 综合案例
  • 网络底层
  • Https
  • 网络请求
  • 故障排查
  • 专栏
  • 数组
  • 链表
  • 栈
  • 队列
  • 树
  • 递归
  • 哈希
  • 排序
  • 查找
  • 字符串
  • 其他
  • Bash脚本
  • Linux入门
  • 嵌入式开发
  • 代码规范
  • Markdown
  • 开发理论
  • 开发工具
  • Git管理
  • 百宝箱
  • 开源协议
  • 技术招聘
  • 测试经验
  • 职场提升
  • 技术模版
  • 关于我
  • 目标清单
  • 学习框架
  • 育儿经验
  • 我的专栏
  • 底层能力
  • 读书心得
  • 随笔笔记
  • 职场思考
  • 中华历史
  • 经济学故事
  • 1.1String深入理解原理
  • 1.2浮点型数据深入研究
  • 1.3数据装箱和拆箱原理
  • 1.4泛型由来和设计思想
  • 1.5加密和解密设计和原理
  • 2.1面向对象设计思想
  • 2.2抽象类和接口设计
  • 2.3封装和继承设计思想
  • 2.4复用和组合设计思想
  • 2.5对象和引用设计思想
  • 3.1IO流设计思想和原理
  • 3.2为何设计序列化数据
  • 3.3各种拷贝数据比较
  • 3.4高效文件读写的原理
  • 4.1反射性能探索和优化
  • 4.2为何要设计注解思想
  • 4.3动态代理的设计思想
  • 4.4SPI机制设计的思想
  • 4.5异常设计和捕获原理
  • 4.6虚拟机如何处理异常
  • 4.7四种引用设计思想
  • 5.1线程的前世今生探索
  • 5.2线程通信的设计思想
  • 5.3线程监控和Debug设计
  • 5.4线程和JVM之间联系
  • 5.5线程池使用技巧介绍
  • 5.6线程池设计核心原理
  • 5.7线程如何最大优化
  • 6.1多线程并发经典案例
  • 6.2并发安全前世今生
  • 6.3线程安全如何保证
  • 6.4变量的线程安全探索
  • 6.5并发上下文切换原理
  • 6.6理解CAS设计和由来
  • 6.7协程设计思想和原理
  • 6.8事物并发模型解读
  • 6.9并发设计模型研究
  • 6.10并发编程数据一致性
  • 6.11锁问题的定位和修复
  • 6.12多线程如何性能调优
  • 7.1类的加载过程和原理
  • 7.2对象布局设计的原理
  • 7.3双亲委派机制设计思想
  • 7.5代码攻击和安全防护
  • 7.6设计动态生成Java类

6.1多线程并发经典案例

目录介绍

  • 01.先来看一个案例
    • 1.1 卖电影票案例1
    • 1.2 卖电影票案例2
    • 1.3 卖电影票案例3
  • 02.线程安全问题探讨
    • 2.1 思考一下电影票案例
    • 2.2 为何出现脏数据
    • 2.3 线程模型数据私有化
    • 2.4 解决多线程安全思路
    • 2.5 如何解决多线程安全
  • 03.线程安全解决实践
    • 3.1 synchronized解决同步问题
    • 3.2 ReentrantLock解决同步问题
    • 3.3 CAS乐观锁解决同步问题
    • 3.4 volatile关键字来保证可见性
  • 04.处理多线程并发使用
    • 4.1 synchronized使用场景
    • 4.2 ReentrantLock使用场景
    • 4.3 CAS锁使用场景
    • 4.4 volatile关键字使用场景

01.先来看一个案例

1.1 卖电影票案例1

  • 继承Thread类的方式卖电影票案例,代码如下所示
    public class ThreadDemo {
    	public static void main(String[] args) {
    		/**
    		 * 需求:某电影院目前正在上映贺岁大片,共有100张票,而它有3个售票窗口售票,请设计一个程序模拟该电影院售票。
    		 */
    		// 创建3个线程对象
    		SellTicktes t1 = new SellTicktes() ;
    		SellTicktes t2 = new SellTicktes() ;
    		SellTicktes t3 = new SellTicktes() ;
    		// 设置名称
    		t1.setName("窗口1") ;
    		t2.setName("窗口2") ;
    		t3.setName("窗口3") ;
    		// 启动线程
    		t1.start() ;
    		t2.start() ;
    		t3.start() ;
    	}
    }
    
    public class SellTicktes extends Thread {
    	private static int num = 100 ;
    	@Override
    	public void run() {
    		/**
    		 * 定义总票数
    		 * 
    		 * 如果我们把票数定义成了局部变量,那么表示的意思是每一个窗口出售了各自的100张票; 而我们的需求是: 总共有100张票
    		 * 而这100张票要被3个窗口出售; 因此我们就不能把票数定义成局部变量,只能定义成成员变量
    		 */		
    		// 模拟售票
    		while(num != 0) {
    			if( num > 0 ) {
    				System.out.println(Thread.currentThread().getName() + " 正在出售 " + (num--) + " 张票");
    			}
    		}
    	}
    }

1.2 卖电影票案例2

  • 实现Runnable接口的方式卖电影票,代码如下所示,假设有三个窗口
    public class SellTicektesDemo {
        public static void main(String[] args) {
            // 创建SellTicektes对象
            SellTicektes st = new SellTicektes() ;
            // 创建Thread对象
            Thread t1 = new Thread(st , "窗口1") ;
            Thread t2 = new Thread(st , "窗口2") ;
            Thread t3 = new Thread(st , "窗口3") ;
            // 启动线程
            t1.start() ;
            t2.start() ;
            t3.start() ;
        }
    }
    
    public class SellTicektes implements Runnable {
        private static int num = 100 ;
        @Override
        public void run() {
            while(true) {
                if(num > 0) {
                    System.out.println(Thread.currentThread().getName() + "正在出售第" + (num--) + "张票");
                }
            }
        }
    }

1.3 卖电影票案例3

  • 讲解过电影院售票程序,从表面上看不出什么问题,但是在真实生活中,售票时网络是不能实时传输的,总是存在延迟的情况。
    • 所以,在出售一张票以后,需要一点时间的延迟。改实现接口方式的卖票程序,每次卖票延迟100毫秒
    public class ThreadDemo {
        public static void main(String[] args) {
            // 创建3个线程对象
            SellTicktes t1 = new SellTicktes() ;
            SellTicktes t2 = new SellTicktes() ;
            SellTicktes t3 = new SellTicktes() ;
            // 设置名称
            t1.setName("窗口1") ;
            t2.setName("窗口2") ;
            t3.setName("窗口3") ;
            // 启动线程
            t1.start() ;
            t2.start() ;
            t3.start() ;
        }
    }
    
    public class SellTicktes extends Thread {
        private static int num = 100 ;
        @Override
        public void run() {
            // 模拟售票
            while(num != 0) {
                if( num > 0 ) {
                    try {
                        Thread.sleep(100) ;
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "正在出售" + (num--) + "张票");
                }
            }
        }
    }

02.线程安全问题探讨

2.1 思考一下电影票案例

2.2 为何出现脏数据

  • 首先想为什么出现问题?
    • 是否是多线程环境,是否有共享数据,是否有多条语句操作共享数据

2.3 线程模型数据私有化

2.4 解决多线程安全思路

  • 如何解决多线程安全问题呢?
    • 基本思想:让程序没有安全问题的环境。怎么实现呢?把多个语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可。

2.5 如何解决多线程安全

  • 保证原子性
    • 锁和同步:lock锁 或者 synchronized同步
    • CAS
  • 保证可见性
    • volatile:比如单利对象用volatile修饰
  • 保证有序性
    • synchronized和锁保证顺序性

03.线程安全解决实践

3.1 synchronized解决同步问题

  • 同步可以解决安全问题的根本原因就在那个对象上。该对象如同锁的功能
    • 可以保证同一时间只有一个线程能拿到锁,也就保证了同一时间只有一个线程能执行申请锁和释放锁之间的代码。
    synchronized(对象){
        需要同步的代码;
    }
  • 同步代码块优势和劣势
    • 同步的好处: 同步的出现解决了多线程的安全问题。
    • 同步的弊端: 当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。

3.2 ReentrantLock解决同步问题

  • ReentrantLock,一个可重入的互斥锁
    • 它具有与使用synchronized方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大。
  • 针对上述方法,具体的解决方式如下:
    public class SellTicktes implements Runnable {
        // 当前拥有的票数
        private int num = 100;
        ReentrantLock lock = new ReentrantLock();
    
        public void run() {
            while (true) {
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                }
                lock.lock();
                // 输出卖票信息
                if (num > 0) {
                    System.out.println(Thread.currentThread().getName() + ".....sale...." + num--);
                }
                lock.unlock();
            }
        }
    }

3.3 CAS乐观锁解决同步问题

  • 基础类型变量自增(i++)是一种常被误以为是原子操作而实际不是的操作。
    • Java中提供了对应的原子操作类来实现该操作,并保证原子性,其本质是利用了CPU级别的CAS指令。由于是CPU级别的指令,其开销比需要操作系统参与的锁的开销小。
    public static class SellTicktes extends Thread {
        private static AtomicInteger integer = new AtomicInteger(100);
        @Override
        public void run() {
            // 模拟售票
            while (integer.get() != 0) {
                if (integer.get() > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //CAS乐观锁解决同步问题
                    System.out.println(Thread.currentThread().getName() + "正在出售" + integer.getAndDecrement() + "张票");
                }
            }
        }
    }

3.4 volatile关键字来保证可见性

  • Java提供了volatile关键字来保证可见性。
    • 当使用volatile修饰某个变量时,它会保证对该变量的修改会立即被更新到内存中,并且将其它缓存中对该变量的缓存设置成无效,因此其它线程需要读取该值时必须从主内存中读取,从而得到最新的值。
    public static class SellTicktes4 extends Thread {
        private static volatile int num = 100;
    
        @Override
        public void run() {
            // 模拟售票
            while (num != 0) {
                if (num > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + " 正在出售 " + (num--) + " 张票");
                }
            }
        }
    }
贡献者: yangchong211
上一篇
5.7线程如何最大优化
下一篇
6.2并发安全前世今生