1 多线程
1.1 实现多线程方式
继承多线程和实现Runnable接口。
1.2 启动线程
start(),run()只是调用方法;start()重复调用将会抛出异常
1.3 停止线程
1.3.1 正确停止线程
停止线程 interrupt()
获取停止线程信号
- interrupted()
currentThread().isInterrupted(true);
重置 - isInterrupted()
isInterrupted(false)
不重置
传递中断:处理中断的最好方法是将中断异常抛出,传递到顶层,让run函数处理
恢复中断:Thread.currentThread().interrupt()
1.3.2 错误的停止方法
-
stop() 会导致程序处于一种无法预估的状态,这与编程的思想相违背
-
suspend() 和锁一起挂起,容易导致死锁
-
resume() suspend()的逆操作
-
voletile和flag方式,其实和中断类似,但是遇到sleep等阻塞操作时,程序变锁死了,而Interrupt却可以响应所有存在阻塞的操作。
1.3.3 如何分析native方法
- 进github或者openJDK网站
1.3.4
对于不能响应的InterruptException的阻塞,没有通用解决方案,有时需要用到具体类的方法响应。
1.4 线程的生命周期
2 Thread与Object重要方法
2.1 wait/notify/notifyAll
- 必须先拥有monitor
- 只能唤醒一个线程
- 属于Object类
2.2 JRE JDK JVM
JRE : 包含JVM,和其他类库,运行时环境,用于运行环境
JDK : 包含JRE,开发工具包
JVM :虚拟机
2.3 sleep
不占用CPU资源
不释放锁synchronized,lock
如果休眠期间被中断,则抛出InterruptedException
2.4 join
- 等待其他线程执行完成,再出发;主线程等待主线程
otherThread.join()
- 源码中包含
wait()
,线程执行完毕后会有notifyAll
2.5 yield
- 释放CPU时间片、不释放锁、不进入阻塞状态
- JVM不保证遵循
- 与sleep区别在于,可以被再次调度
2.6 线程的各个属性
- 线程Id 从1自增,JVM首先会创建很多线程
- 线程名字
- 用户线程与守护线程 守护线程用于给用户线程提供服务;由JVM启动;不影响JVM退出
- 线程优先级,1-10;默认5;程序不应该依赖优先级
2.7 UncaughtExceptionHandler
- 主线程可以轻松发现异常,子线程却不行
- 无法在主线程中用
try...catch
捕获子线程异常
解决方案: - 在每个
run
方法里加入try...catch
捕获语句,缺点是太繁杂了 - 利用
UncaughtExceptionHandler
- 程序统一设置
Thread.setDefaultUncaughtExceptionHandler()
- 单个线程设置
- 线程池设置
- 程序统一设置
3 线程安全
3.1 对象发布和初始化时候的安全问题
- 方法返回一个只读private对象 使用复本
- 未初始化完成就提供给外界
- 构造函数中还未初始化完成就用this赋值
- 隐式逸出-注册监听事件
- 构造函数中使用多线程
4 死锁
当两个或多个线程(进程)相互持有对方所需要的资源,又不主动释放。导致程序都陷入阻塞
4.1 四个必要条件
- 互斥
- 请求与保持
- 不可剥夺
- 循环等待
4.2 检查死锁
- jstack
- jps java查看进程pid
- jstack pid 查看是否有死锁
ThreadMXBean.findDeadlockedThreads()
4.3 修复死锁策略
- 避免策略 :通过调整获取锁的顺序来破除循环等待条件,当条件相等时引入额外的锁。
- 哲学家就餐问题:
- 服务员检查:提前问服务员是否可以就餐(避免策略)
- 改变拿筷子的顺序(避免策略)
- 餐票(避免策略)
- 领导调节(检测与恢复)
4.4 避免死锁
- 设置超时时间
lock.trylock()
- 多使用并发类而不是自己设计锁
- 降低锁的粒度
- 使用同步代码块而不是同步方法:自己指定锁对象
- 给线程起有意义的名字
- 避免锁的嵌套
- 分配资源前先计算能不能回收:银行家算法
- 尽量不要几个功能使用同一把锁:专锁专用
4.5 其他活跃性故障
4.5.1 活锁
线程没有阻塞,也始终在运行,但是得不到进展,做重复的事情。
引入随机性解决
4.5.2 饥饿
当线程需要某些资源,但永远得不到