MOOC 个人学习笔记
# 1. 概念
- 一个程序可以包括多个子任务,可串 / 并行
- 每个子任务可以称为一个线程
- 如果一个子任务阻塞,程序可以将 CPU 调度另外一个子任务进行工作。这样 CPU 还是保留在本程序中,而不是被调度到别的程序 (进程) 去。这样,提高本程序所获得 CPU 时间和利用率
# 2. 多线程实现
# 多线程创建
- java.lang.Thread
- 线程继承 Thread 类,实现 run 方法
public class Thread1 extends Thread {
public void run() {
System.out.println("run");
}
}
- java.lang.Runnable 接口
- 线程实现 Runnable 接口,实现 run 方法
public class Thread2 implements Runnable {
public void run() {
System.out.println("run");
}
}
# 多线程启动
- start 方法,会自动以新进程调用 run 方法
// 继承 Thread 类 | |
new Thread1().start(); | |
// 实现 Runnable 接口,必须包装在 Thread 类中才能启动 | |
new Thread(new Thread2()).start(); |
- 直接调用 run 方法,将变成串行执行
- 同一个线程,多次 start 会报错,只执行第一次 start 方法
- 多个线程启动,其启动的先后顺序是随机的
- 线程无需关闭,只要其 run 方法执行结束后,自动关闭
- main 函数 (线程) 可能早于新线程结束,整个程序并不终止
- 整个程序终止是等所有的线程都终止 (包括 main 函数线程)
# 实现对比
- Thread vs Runnable
- Thread 占据了父类的名额,不如 Runnable 方便
- Thread 类实现 Runnable
- Runnable 启动时需要 Thread 类的支持
- Runnable 更容易实现多线程中资源共享
- 结论:建议实现 Runnable 接口来完成多线程
# 3. 多线程信息共享
- 通过共享变量在多个线程中共享消息
- 如果继承了 Thread 类,只能使用 static 变量
- 如果实现了 Runnable 接口,可以使用同一个成员变量
# 信息共享问题
- 工作缓存副本
- 解决方法
- 采用 volatile 关键字修饰变量
private volatile boolean flag = false;
- 保证不同线程对共享变量操作时的可见性
- 解决方法
- 关键步骤缺乏加锁限制
- 解决方法:关键步骤加锁限制
- 互斥:某一个线程运行一个代码段 (关键区),其他线程不能同时运行这个代码段
- 同步:多个线程的运行,必须按照某一种规定的先后顺序来运行
- 互斥是同步的一种特例
- 互斥的关键字是 synchronized
- synchronized 代码块 / 函数,只能一个线程进入
- synchronized 加大性能负担,但是使用简便
public synchronized void sale() {
//TODO
}
// 等同于
public void sale() {
synchronized(this) {
//TODO
}
}
- 解决方法:关键步骤加锁限制
# 4. 多线程管理
# 线程状态
- NEW 刚创建 (new)
- RUNNABLE 就绪态 (start)
- RUNNING 运行中 (run)
- BLOCK 阻塞 (sleep)
- TERMINATED 结束
- Thread 的部分 API 已经废弃
- 暂停和恢复 suspend/resume
- 消亡 stop/destroy
- 线程阻塞 / 和唤醒
- sleep,时间一到,自己会醒来
- wait/notify/notifyAll,等待,需要别人来唤醒
- join,等待另外一个线程结束
- interrupt,向另外一个线程发送中断信号,该线程收到信号,会触发 InterruptedException (可解除阻塞),并进行下一步处理
- 线程被动地暂停和终止
- 依靠别的线程来拯救自己
- 没有及时释放资源
- 线程主动暂停和终止
- 定期监测共享变量
- 如果需要暂停或者终止,先释放资源,再主动动作
- 暂停:Thread.sleep (),休眠
- 终止:run 方法结束,线程终止
- 多线程死锁
- 每个线程互相持有别人需要的锁
- 预防死锁,对资源进行等级排序
- 守护 (后台) 线程
- 普通线程的结束,是 run 方法运行结束
- 守护线程的结束,是 run 方法运行结束,或 main 函数结束
- 守护线程永远不要访问资源,如文件或数据库等
Thread1 t = new Thread1();
t.setDaemon(true);
t.start();
- 线程查看工具 jvisualvm
# 线程组管理
- 线程组 ThreadGroup
- 线程的集合
- 树形结构,大线程组可以包括小线程组
- 可以通过 enumerate 方法遍历组内的线程,执行操作
- 能够有效管理多个线程,但是管理效率低
- 任务分配和执行过程高度耦合
- 重复创建线程、关闭线程操作,无法重用线程
// 创建线程组 | |
ThreadGroup threadGroup = new ThreadGroup("Searcher"); | |
// 创建线程 | |
Searcher searchTask=new Searcher(); | |
Thread thread=new Thread(threadGroup, searchTask); | |
//public class Searcher implements Runnable{} | |
// 返回线程组中还处于 active 的线程数(估计数) | |
System.out.println(threadGroup.activeCount()); | |
// 将线程组中 active 的线程拷贝到数组中 | |
Thread[] threads=new Thread[threadGroup.activeCount()]; | |
threadGroup.enumerate(threads); | |
for (int i=0; i<threadGroup.activeCount(); i++) { | |
System.out.printf("Thread %s: %s\n",threads[i].getName(),threads[i].getState()); | |
} | |
// 打印线程组中所有的线程信息 | |
threadGroup.list(); | |
// 对线程组中的所有线程发出 interrupt 信号 | |
threadGroup.interrupt(); |