第14章 并发
- 操作系统将CPU的时间片分配给每一个进程
- 并发执行的进程数目并不是由CPU数目制约的
- 每一个任务称为一个线程thread
- 每个进程拥有自己的一整套变量,而线程则共享数据
- 推荐书籍《Java并发编程实战》
14.1 什么是线程
-
Thread.sleep(DELAY)
:暂停当前线程的活动
14.1.1 使用线程给其他任务提供机会
- 主线程用来处理UI,子线程处理多个弹跳球的任务
- 如果需要执行一个比较耗时的任务,应当并发地运行任务
- 多线程的两种方法
- 实现Runnable接口(函数式接口,可以使用lambda表达式)
- 继承Thread(不推荐,耦合的太紧)
14.2 中断线程
- 没有可以强制线程终止的方法。然而,interrupt方法可以用来请求终止线程
- 如果线程被阻塞(sleep,wait),就无法检测中断状态
14.3 线程状态
- 线程的6种状态
- New:新创建
- Runnable:可运行
- Blocked:被阻塞
- Waiting:等待
- Timed waiting:计时等待
- Terminated:被终止
- 要确定一个线程的当前状态,可调用
getState
方法
14.3.1 新创建线程
new Thread(r)
14.3.2 可运行线程
- 一旦调用start方法,线程处于Runnable状态
- 一个可运行的线程可能正在运行也可能没有运行,这取决于操作系统给线程提供运行的时间
- 抢占式调度系统给每一个可运行线程一个时间片来执行任务
- 当时间片用完,操作系统剥夺运行权,考虑线程优先级
14.3.3 被阻塞线程和等待线程
- 当线程处于被阻塞或等待状态时,它暂时是不活动的
14.3.4 被终止的线程
- 终止的两个可能原因
- run方法正常退出而自然死亡
- 因为一个没有捕获的异常终止了run方法而意外死亡
14.4 线程属性
14.4.1 线程优先级
- 线程优先级是高度依赖于系统的
14.4.2 守护线程
t.setDaemon(true)
- 守护线程的唯一用途是为其他线程提供服务
14.4.3 未捕获异常处理器
- 线程的run方法不能抛出任何受查异常
14.5 同步
- 两个线程同时访问一个对象,可能会发生错误
- 这种情况称为竞争条件(race condition)
14.5.1 竞争条件的一个例子
- 银行账户开100条线程随机转账
14.5.2 竞争条件详解
- 真正的问题是transfer方法的执行过程中可能会被中断
14.5.3 锁对象
private Lock bankLock = new ReentrantLock();
- ReentrantLock:可重入锁
- Lock:获取这个锁
14.5.4 条件对象
- Condition
14.5.5 synchronized关键字
每一个对象有一个内部锁,并且该锁有一个内部条件。由锁来管理那些试图进入synchronized方法的线程,由条件来管理那些调用wait的线程
14.5.6 同步阻塞
- 任何对象都可以当做锁
14.5.7 监视器概念
- 监视器是只包含私有域的类
14.5.8 Volatile
- 如果申明一个域为volatile,那么编译器和虚拟机就知道该域是可能被另一个线程并发更新的。
14.5.9 final变量
- 建立一次引用的机会
14.5.10 原子性
- AtomicInteger
14.5.11 死锁
- 所有的线程都被阻塞
14.5.12 线程局部变量
- 使用ThreadLocal辅助类为各个线程提供各自的实例
14.5.13 锁测试与超时
- tryLock
14.5.14 读/写锁
- ReentrantReadWriteLock
14.5.15 为什么弃用stop和suspend
- 不安全,容易导致死锁
14.6 阻塞队列
- 对于实际编程来说,应该尽可能远离底层结构
14.7 线程安全的集合
- 可以通过锁来保护共享数据机构,但是选择线程安全的实现作为替代可能更容易些
14.7.1 高效的映射、集和队列
- 这些集合使用复杂的算法,通过允许并发地访问数据结构的不同部分来使竞争极小化
14.7.2 映射条目的原子更新
- LongAdder
网友评论