-
概述
- 简单聊聊这个 关键字
-
背景
- 最近整理到了 jvm 的线程, 然后就开始整理到了 jvm 并发相关的内容
- 老实说, 这块之前确实没有学好, 挖了一个大坑
- 碰见坑就填吧, 慢慢填, 一点一点填好
- 最近整理到了 jvm 的线程, 然后就开始整理到了 jvm 并发相关的内容
1. 单线程的问题, 与多线程的引入
-
概述
- 单线程的问题
- 多线程的引入
-
单线程的问题
-
单线程
- 一个线程执行程序
- 所有的执行都是 序列化
- 有章可循, 逻辑清晰
- 所有的执行都是 序列化
- 一个线程执行程序
-
问题
-
当硬件上来时, 单线程无法全部发挥硬件的效果
- CPU 有能力处理多个线程时, 只处理 单线程, 会浪费算力
- 但是从另一个角度来说, 也会限制程序的 处理能力
- 内存 扩大时, JVM 可以容纳更多的 栈
- I/O 规模扩大时, 单线程需要 wait 或者 sleep, 也没有好好发挥资源
- 这个后面再说
- 简单的说, 就是 IO 的时候, 这个线程通常只能等着, 啥也干不了
- 其实可以异步, 但这个暂时不提
- CPU 有能力处理多个线程时, 只处理 单线程, 会浪费算力
-
单线程对 用户 来说, 不够友好
- 一个线程如果要处理数据, 则 UI 的响应, 肯定会很慢
-
容易崩溃
- 单线程 的程序来说, 一个线程崩溃, 整个程序就挂掉了
- 多线程还可以想办法抢救一下...
- 单线程 的程序来说, 一个线程崩溃, 整个程序就挂掉了
-
-
-
问题的解决
- 当然是 引入多线程 啦
- 其实从 Thread 类来看, jdk1.0 就已经引入了
- 当然是 引入多线程 啦
2. 多线程的问题
-
概述
- 多线程的问题
-
多线程的问题
-
多线程
- 一个进程内, 有多个线程
- 他们会 同时 执行 相同 或者 不同 的任务
- 表面上的同时
- 他们会 同时 执行 相同 或者 不同 的任务
- 一个进程内, 有多个线程
-
问题
-
硬件消耗增加, 但是如果控制得当, 则未必是坏事
-
CPU 消耗增加
- 提高了 CPU 的利用率
- 也能提高 程序处理数据的能力
-
内存占用大
- 高了内存的占用率
-
I/O 占用大
- 高了 I/O 效率
-
线程的 创建 和 切换, 会消耗额外的资源
- 没关系, 相比线程带来的好处, 这些都是毛毛雨
-
-
线程调度, 这是一个新的问题
-
如何确定什么时候, 该执行什么线程
- jvm 的线程调度, 这个以后有机会再说
-
竞态条件
-
竞态条件(race condition) - 这翻译我总觉得怪怪的...
- 多个线程争夺相同资源的使用
- 但是 线程调度 的不可预测
- 于是, 会导致下列情况
-
操作被打断
- 例如: a += 1
- 这个操作不是原子性的, 需要 取出; 加1; 赋值 三个步骤
- 如果在这中途中断了, 就会导致后续程序出现问题
- 而且通常情况下, 你无法确定
- 例如: a += 1
-
流程被打断
- 常见的例子, 就是那个 取钱 的例子
- 例子都举烂了, 我就不说了
-
出现了死锁, 又怎么办?
- 场景
- 两个线程, 需要同样的资源
- 需要同时持有 资源A 和 资源B 才能办事
- 资源A 和 资源B 只有一份
- 线程持有资源后, 不会主动释放
- 于是可能会出现一个场景: 线程1 持有A, 线程2 持有B, 然后卡死不动
- 场景
-
-
-
-
-
解决
-
硬件问题
- 不是大问题
-
调度问题
-
jvm 的调度顺序, 通常来说不可预知
- 同样的程序, 通常每次执行的顺序, 都会不一样
- 试着把没有顺序的东西, 变得有顺序
- 依靠 同步, 等待, 唤醒
- 以及其他一些工具类
- 通过为线程的执行, 添加各种 前置状态, 来间接限定线程的执行顺序
-
非原子操作
- 这个通过 volatile 来解决
- 作用有限
- 线程调度导致的 流程中断, 是没办法通过这个来解决的
- 这个通过 volatile 来解决
-
流程中断
- 这个通过 synchronized 关键字解决
-
死锁的通常解决方案
- 确定线程的获取顺序
- 如果持有资源但长时间无法工作, 则放弃资源重新等待获取
- 这个后面会有机会会说...
-
-
3. sychronized
-
概述
- synchronized
-
数据库事务的四个特性
-
概述
- 怎么想起了这玩意
-
ACID
- 原子性
- 一致性
- 隔离性
- 持久性
-
synchronized 解决的问题
- 主要还是 流程中断 的问题
- 隔离性
- 线程在 synchronized 段, 不会被别的线程打断
- 因为不会被打断, 避免了执行中因为 线程调度, 而导致 数据出现类似 脏读, 不可重复读, 幻读 之类的隔离性问题
- 隔离性
- 主要还是 流程中断 的问题
-
-
用法
-
synchronized 方法
-
使用
-
通过 synchronized 关键字, 来修饰方法
public static synchronized void method() {} public synchronized void method() {}
-
-
分类
-
修饰 静态方法
- 由 Class对象 的锁, 来保证 原子性 与 隔离性
- 同一时间, 只有抢到 监视器锁 的线程, 可以执行这段代码
- 没有抢到的, 需要等待锁释放
- 由 Class对象 的锁, 来保证 原子性 与 隔离性
-
修饰 实例方法
- 由 实例对象 的锁, 来保证 原子性 与 隔离性
- 同 修饰静态方法
- 由 实例对象 的锁, 来保证 原子性 与 隔离性
-
-
问题
-
无法手工制定 锁
- 锁 是跟随 对象的
- 每个对象会有一个 监视器锁
- synchronized 方法的锁, 是指定了无法修改
- 锁 是跟随 对象的
-
粒度太粗
- 部分 synchronized 方法里, 只有一小段代码需要
- 如果 限定整个方法, 就会引起阻塞
- 部分 synchronized 方法里, 只有一小段代码需要
-
-
-
synchronized 代码块
-
使用
-
使用 synchronized 修饰代码块
synchronized(obj) {}
-
-
obj
- 这个是一个特定的对象
- 线程争抢的锁, 就是 这个 obj 的 监视器锁
- 这个是一个特定的对象
-
-
-
其他
- 类中没有被 synchronized 关键字修饰的代码, 不会受到影响
- 可以允许多个 线程 同时, 不同步的执行
4. 等等, 好像又有一个问题
-
概述
- 好像又有一个问题
-
场景
-
角色
- 资源 S
- 对象 O1, O2
- 线程 T1, T2, T3
-
场景
-
对象 O1 持有 S
- O1 在 synchronized 代码块里, 操作 S
- T1 和 T2 轮流访问 O1 的 synchronized 代码块
- 看上去好像很正常的样子
-
对象 O2, 也持有 S
- T3 通过 O2 来访问 S
- O1 的同步代码块, 好像就管不着这个玩意了...
-
-
我的感觉
- 这样的场景下, 依然会出现很多 匪夷所思, 无法预料到结果...
-
ps
-
ref
- Java 语言规范(Java SE 8)
- Java 编程思想(第四版)
- Java 核心技术(第十版)
-
后续
- 线程调度
- 监视器锁 与 等待队列
- 线程状态
- 如何处理 死锁
- volatile
- Java 内存模型
- 这玩意竟然是 描述多线程情况下, 线程可以对 主存 所作的事
- Java 语言规范