1 概念
1.1 进程
(1)程序由指令和数据组成,但这些指令要运行,数据要读写,就必须将指令加载至CPU,数据加载至内存。在指令运行过程中还需要用到磁盘、网络等设备。进程就是用来加载指令、管理内存、管理IO的。
(2)进程可以看作是一个程序的实例。
1.2 线程
(1)线程是一个指令流,将指令流中的一条条指令以一定的顺序交给CPU执行。
(2)线程作为最小调度单位,进程作为资源分配的最小单位。
1.3 对比
- 进程基本上是独立的,而线程存在于进程当中,是进程的一个子集。
- 进程拥有共享的资源,如内存空间,供内部的线程共享。
- 进程间通信分为两种
-
- 同一台计算机的进程通信称为IPC(Inter-process communication)
-
- 不同计算机之间的进程通信,需要通过网络,并遵守共同的协议
- 线程通信相对简单,因为他们共享进程内的内存
- 线程更轻量,线程上下文切换的成本比进程上下文切换低
1.4 并发
单核
一个人 一个时间段 做几件事情
1.5 并行
多核
多个人 一个时间段 同时做几件事情
1.5 同步
一个人必须等待上一个人的结果才能继续做事
1.5 异步
彼此互不相干
2 线程的应用
2.1 创建线程
2.1.1 Thread
直接创建Thread对象,重写run方法
Thread t1 = new Thread(() -> {
System.out.println("线程创建1");
});
2.1.2 实现Runable
实现runable接口,并将实现类作为参数传入Thread构造方法
public static void main(String[] args) {
ThreadDemo threadDemo = new ThreadDemo();
Thread thread2 = new Thread(threadDemo);
}
static class ThreadDemo implements Runnable{
@Override
public void run() {
System.out.println("线程创建2");
}
}
2.1.3 Runable和Thread的关系
Thread实现了Runable
public class Thread implements Runnable {...}
2.1.4 实现Callable
实现Callable接口,作为参数构造FutureTask
FutureTask实现了Runable接口,作为参数构造Thread
FutureTask<Integer> futureTask = new FutureTask<Integer>(() -> {
System.out.println("线程创建3");
return 1;
});
Thread thread = new Thread(futureTask);
2.2 运行线程
thread.start();
2.3 查看线程
jconsole图像化工具
2.3.1 栈和栈帧
JVM由堆、栈、方法区组成。线程使用栈内存
- 每个栈由多个栈帧(Frame)组成,对应着每次方法调用时所占用的内存
- 每个线程只能有一个活动栈帧,对应着赈灾执行的那个方法
2.3.1 上下文切换
- 线程的cpu时间片用完
- 垃圾回收
- 有更高优先级的线程需要允许
- 线程自己调用了sleep、yield、wait、join、park、synchronized、lock等方法
当发生上下文切换时,需要由操作系统保存当前线程的状态,并恢复另一个线程的状态,Java中对应的概念就是程序计数器,它的作用就是记住下一条jvm的执行地址,时线程私有的
- 状态包括程序计数器,虚拟机栈中的每个栈帧的信息,如局部遍历,操作数栈、返回地址
- 上下文频繁切换影响性能
2.4 线程API
2.4.1 start()和run()
直接调用
start() 新建线并进入就绪状态(RUNNABLE),等待cpu执行
run() 在当前线程执行run方法里的方法
2.4.2 sleep()
- 由RUNNABLE 状态 转换成 TIMED_WAITING 状态
Thread thread = new Thread(() -> {
System.out.println("start...");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("end...");
});
thread.start();
System.out.println("status->" + thread.getState());
Thread.sleep(1000);
System.out.println("status->" + thread.getState());
- 其它线程可以使用interrrupt方法打断正在睡眠的线程,这时sleep方法会抛出InterruptException
Thread thread = new Thread(() -> {
System.out.println("start...");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
System.out.println("interrupted..");
e.printStackTrace();
}
System.out.println("end...");
});
thread.start();
System.out.println("status->" + thread.getState());
Thread.sleep(1000);
// 打断线程
thread.interrupt();
System.out.println("status->" + thread.getState());
}
- 睡眠结束后的线程未必立刻得到执行
- 建议用TimeUnit的sleep代替Thread的sleep来获得更好的可读性
// 当前线程睡眠3秒
TimeUnit.SECONDS.sleep(3);
sleep应用
2.4.3 yield()
- 调用yield会当当前线程从Running进入Runnable就绪状态,然后调度执行其他线程
- 具体的实现依赖于操作系统的任务调度器
2.4.4 线程优先级
一切看大哥脸色 任务调度器
- 线程优先级会提示(hint)调度器有限调度该线程,但仅仅是一个提示,调度器可以忽略它
- 如果cpu比较忙,那么优先级高的线程会获得更多的时间片,cpu闲的时候,优先级没用
2.4.5 join()
等待线程运行结束
static int i = 0;
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
System.out.println("start...");
try {
TimeUnit.SECONDS.sleep(3);
i++;
} catch (InterruptedException e) {
System.out.println("interrupted..");
e.printStackTrace();
}
System.out.println("end...");
});
thread.start();
System.out.println(i);
// 等待线程结束
thread.join();
System.out.println(i);
}
2.4.6 interrupt()
打断sleep、wait、join的线程
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
while (true) {
// 如果被打断,退出死循环
if (Thread.interrupted()) {
System.out.println("interrupt..");
break;
}
}
});
thread.start();
TimeUnit.SECONDS.sleep(2);
// 打断线程
System.out.println("begin interrupt..");
thread.interrupt();
}
2.4.7 两阶段终止模式
1 错误示范
- 使用线程对象stop() 方法停止线程
stop方法会真正的杀死线程,如果这时线程锁住了共享资源,那么当它被杀死后就再也没用机会释放锁,其他线程永远无法获取锁。 - 使用System.exit(int)方法停止线程
目的仅是停止一个线程,但这种做法会让整个程序都停止
2 介绍
3 示范
isInterrupted() 和 interrupted() 区别
isInterrupted()不会打断清除标记
interrupted()会清除打断标记
public static void main(String[] args) throws InterruptedException {
TwoPhaseTermination termination = new TwoPhaseTermination();
termination.start();
TimeUnit.SECONDS.sleep(5);
termination.stop();
}
static class TwoPhaseTermination {
private Thread monitor;
public void start(){
monitor = new Thread(()->{
while (true) {
Thread current = Thread.currentThread();
// 如果被打断了,那就料理后事
if (current.isInterrupted()) {
System.out.println("料理后事...");
break;
}
System.out.println("处理事情...");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
// 睡眠时被打断,标记不会改变,手动改变
current.interrupt();
}
}
});
monitor.start();
}
public void stop(){
monitor.interrupt();
}
}
2.4.8 过时方法
stop() 停止线程运行
suspend() 挂起(暂停)线程运行
resume() 恢复线程运行
2.5 守护线程
默认情况下,Java进程需要等待所有线程都运行结束,才会接受,有一种特殊的线程叫做守护线程,只要其它非守护线程运行结束了,即使守护线程的代码没用执行完,也会强制结束。
// 设置线程为守护线程
thread.setDaemon(true);
- 垃圾回收器就是一种守护线程
- Timcat中Acceptor和Poller线程都是守护线程,所以Tomcat接受到shutdown命令后,不会等待他们处理完当前请求。
2.6 线程状态
2.6.1 五种状态
- 初始状态 创建了线程对象,但未与操作系统做关联
- 可运行状态(就绪状态) 线程被创建,可以由CPU调度
- 运行状态 获取到了CPU时间片
- 阻塞状态
-
- 调用了阻塞API,如BIO读写文件,这是线程不会用到CPU,会导致上下文切换,进入阻塞状态
-
- 等BIO结束,会u由操作系统唤醒阻塞的线程,转换为可运行状态
-
- 与可运行状态相比,堆阻塞状态的线程来说,只要他们一直不欢喜,调度器就一直不会考虑调度他们
- 终止状态 线程执行完毕
2.6.1 六种状态
- NEW 线程刚刚被创建,但是还没有调用start()方法
- RUNNABLE 当调用了start()方法