进程: 资源分配的最小单位
- 进程是线程的容器, 一个进程中包含多个线程, 真正执行任务的是线程
线程: 资源调度的最小单位
程序
由指令
和数据
组成,但是这些 指令要运行,数据要读写,就必须将指令加载到cpu,数据加载至内存。在指令运行过程中还需要用到磁盘,网络等设备,进程就是用来加载指令,管理内存,管理IO的
进程
就可以视为程序
的一个实例
,大部分程序都可以运行多个实例进程(例如记事本,浏览器等),部分只可以运行一个实例进程(例如360安全卫士)多个线程
。一个线程
就是一个指令流
,将指令流中的一条条指令以一定的顺序交给 CPU 执行相互独立的
,而线程存在于进程内,是进程的一个子集
不同计算机之间的进程通信,需要通过网络,并遵守共同的协议,例如 HTTP
轻量
,线程上下文切换成本一般上要比进程上下文切换低并发: 在单核CPU下, 一定是
并发执行
的, 也就是在同一个时间段内一起执行. 实际还是串行执行, CPU的时间片切换非常快, 给人一种同时运行的感觉。
并行: 在多核CPU下, 能真正意义上实现
并行执行
, 在同一个时刻, 多个线程同时执行; 比如说2核cpu, 同时执行4个线程. 理论上同时可以有2个线程是并行执行的. 此时还是存在并发
, 因为2个cpu也会同时切换不同的线程执行任务罢了
微观串行, 宏观并行
单核 cpu
下,线程
实际还是串行执行
的。操作系统中有一个组件叫做任务调度器,将 cpu 的时间片(windows下时间片最小约为 15 毫秒)分给不同的程序使用,只是由于cpu 在线程间(时间片很短)的切换非常快
,给人的 感觉是同时运行的 。一般会将这种线程轮流使用 CPU
的做法称为并发(concurrent)
线程轮流使用cput
称为并发(concurrent)
并发(concurrent)
: 是同一时间应对(dealing with)多件事情的能力并行(parallel)
: 是同一时间动手做(doing)多件事情的能力例子
并发
既有并发,也有并行
(这时会产生竞争,例如锅只有一口,一个人用锅时,另一个人就得等待)并行
以调用方
的角度讲
需要等待结果返回才能继续运行
的话就是同步
不需要等待
就是异步
异步
的(即不要巴巴干等着)比如说读取磁盘文件时,假设读取操作花费了 5 秒钟,如果没有线程调度机制,这5秒cpu什么都做不了,其它代码都得暂停费时
,这时开一个新线程处理视频转换
,避免阻塞主线程让用户线程处理耗时较长的操作,避免阻塞 tomcat 的工作线程
重点
)public class CreateThread {
public static void main(String[] args) {
Thread myThread = new MyThread();
// 启动线程
myThread.start();
}
}
class MyThread extends Thread {
@Override
public void run() {
System.out.println("my thread running...");
}
}
继承方式
的好处是,在run()方法
内获取当前线程直接使用this
就可以了,无须使用Thread.currentThread()方法;不好的地方是Java不支持多继承,如果继承了Thread类,那么就不能再继承其他类。另外任务与代码没有分离,当多个线程执行一样的任务时需要多份任务代码
public class Test2 {
public static void main(String[] args) {
//创建线程任务
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println("Runnable running");
}
};
//将Runnable对象传给Thread
Thread t = new Thread(r);
//启动线程
t.start();
}
}
或者
public class CreateThread2 {
private static class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("my runnable running...");
}
}
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
thread.start();
}
}
实现Runnable接口
,并且实现run()方法
。在创建线程时作为参数传入该类的实例即可@FunctionalInterface注解
时,是可以使用lambda来简化操作的public class Test2 {
public static void main(String[] args) {
//创建线程任务
Runnable r = () -> {
//直接写方法体即可
System.out.println("Runnable running");
System.out.println("Hello Thread");
};
//将Runnable对象传给Thread
Thread t = new Thread(r);
//启动线程
t.start();
}
}
Thread
的源码,理清它与 Runnable 的关系小结
使用FutureTask可以用泛型指定线程的返回值类型(Runnable的run方法没有返回值)
public class Test3 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//需要传入一个Callable对象
FutureTask<Integer> task = new FutureTask<Integer>(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
System.out.println("线程执行!");
Thread.sleep(1000);
return 100;
}
});
Thread r1 = new Thread(task, "t2");
r1.start();
//获取线程中方法执行后的返回结果
System.out.println(task.get());
}
}
或
public class UseFutureTask {
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<String> futureTask = new FutureTask<>(new MyCall());
Thread thread = new Thread(futureTask);
thread.start();
// 获得线程运行后的返回值
System.out.println(futureTask.get());
}
}
class MyCall implements Callable<String> {
@Override
public String call() throws Exception {
return "hello world";
}
}
/**
* 创建线程的方式四:使用线程池
*
* 好处:
* 1.提高响应速度(减少了创建新线程的时间)
* 2.降低资源消耗(重复利用线程池中线程,不需要每次都创建)
* 3.便于线程管理
* corePoolSize:核心池的大小
* maximumPoolSize:最大线程数
* keepAliveTime:线程没有任务时最多保持多长时间后会终止
*
*
* 面试题:创建多线程有几种方式?四种!
*/
class NumberThread implements Runnable{
@Override
public void run() {
for(int i = 0;i <= 100;i++){
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
}
}
class NumberThread1 implements Runnable{
@Override
public void run() {
for(int i = 0;i <= 100;i++){
if(i % 2 != 0){
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
}
}
public class ThreadPool {
public static void main(String[] args) {
//1. 提供指定线程数量的线程池
ExecutorService service = Executors.newFixedThreadPool(10);
ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
//设置线程池的属性
// System.out.println(service.getClass());
// service1.setCorePoolSize(15);
// service1.setKeepAliveTime();
//2.执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象
service.execute(new NumberThread());//适合适用于Runnable
service.execute(new NumberThread1());//适合适用于Runnable
// service.submit(Callable callable);//适合使用于Callable
//3.关闭连接池
service.shutdown();
}
}
使用Runnable方式
,则只能使用主线程里面被声明为final的变量。不好的地方是Java不支持多继承,如果继承了Thread类,那么子类不能再继承其他类,而Runable则没有这个限制。前两种方式都没办法拿到任务的返回结果,但是Futuretask方式可以
线程池
的方式重点
)虚拟机栈
描述的是Java方法执行的内存模型
:每个方法被执行的时候都会同时创建一个栈帧(stack frame)
用于存储局部变量表、操作数栈、动态链接、方法出口
等信息,是属于线程的私有的。当Java中使用多线程时,每个线程都会维护它自己的栈帧!每个线程只能有一个活动栈帧(在栈顶),对应着当前正在执行的那个方法因为以下一些原因导致 cpu 不再执行当前的线程,转而执行另一个线程
sleep
、yield
、wait
、join
、park
、synchronized
、lock
等方法当Thread Context Switch
发生时,需要由操作系统保存当前线程的状态
,并恢复另一个线程的状态
,Java 中对应的概念就是程序计数器(Program Counter Register),它的作用是记住下一条 jvm 指令的执行地址,是线程私有的
线程的状态
包括程序计数器、虚拟机栈中每个栈帧的信息,如局部变量、操作数栈、返回地址等影响性能
public static void main(String[] args) {
Thread thread = new Thread(){
@Override
public void run(){
log.debug("我是一个新建的线程正在运行中");
FileReader.read(fileName);
}
};
thread.setName("新建线程");
thread.start();
log.debug("主线程");
}
t1 线程运行
, run()
方法里面内容的调用是异步的代码11:59:40.711 [main] DEBUG com.concurrent.test.Test4 - 主线程
11:59:40.711 [新建线程] DEBUG com.concurrent.test.Test4 - 我是一个新建的线程正在运行中
11:59:40.732 [新建线程] DEBUG com.concurrent.test.FileReader - read [test] start ...
11:59:40.735 [新建线程] DEBUG com.concurrent.test.FileReader - read [test] end ... cost: 3 ms
thread.start();
改为 thread.run();
输出结果如下:程序仍在 main 线程运行, run()
方法里面内容的调用还是同步的12:03:46.711 [main] DEBUG com.concurrent.test.Test4 - 我是一个新建的线程正在运行中
12:03:46.727 [main] DEBUG com.concurrent.test.FileReader - read [test] start ...
12:03:46.729 [main] DEBUG com.concurrent.test.FileReader - read [test] end ... cost: 2 ms
12:03:46.730 [main] DEBUG com.concurrent.test.Test4 - 主线程
run()
是在主线程中执行了 run()
,没有启动新的线程start()
是启动新的线程,通过新的线程间接执行 run()
方法中的代码sleep()
会让当前线程从 Running(运行状态)
进入 Timed Waiting 状态(阻塞)
interrupt 方法打断正在睡眠的线程
,那么被打断的线程这时就会抛出 InterruptedException异常
【注意:这里打断的是正在休眠的线程,而不是其它状态的线程】TimeUnit
的 sleep()
代替 Thread 的 sleep()
来获得更好的可读性Running
进入 Runnable 就绪状态
,然后调度执行其它线程但是cpu可能会再分配时间片给该线程
;而sleep需要等过了休眠时间之后才有可能被分配cpu时间片
优先级
会提示(hint)调度器优先调度该线程
,但它仅仅是一个提示,调度器可以忽略它, 如果 cpu 比较忙,那么优先级高的线程会获得更多的时间片,但 cpu 闲时,优先级几乎没作用thread1.setPriority(Thread.MAX_PRIORITY); //设置为优先级最高
主线程
中调用t1.join
,则主线程
会等待t1线程执行完之后
再继续执行
private static void test1() throws InterruptedException {
log.debug("开始");
Thread t1 = new Thread(() -> {
log.debug("开始");
sleep(1);
log.debug("结束");
r = 10;
},"t1");
t1.start();
// t1.join();
// 这里如果不加t1.join(), 此时主线程不会等待t1线程给r赋值, 主线程直接就输出r=0结束了
// 如果加上t1.join(), 此时主线程会等待到t1线程执行完才会继续执行.(同步), 此时r=10;
log.debug("结果为:{}", r);
log.debug("结束");
}
下图, 因为开辟了t1线程. 此时程序中有两个线程; main线程和t1线程; 此时在main线程中调用
t1.join
, 所以main线程只能阻塞
等待t1线程执行完.t1线程在1s后将r=10
, t1线程执行完, 此时main线程才会接着执行
sleep,wait,join
的线程, 在阻塞期间cpu不会分配给时间片interrupt()方法
的相关知识:博客地址一个线程在在运行中被打断
,打断标记会被置为true因sleep wait join方法而被阻塞的线程
,会将打断标记置为falsesleep,wait,join
的线程,这几个方法都会让线程进入阻塞状态
,以 sleep 为例
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
System.out.println("sleep...");
try {
Thread.sleep(5000); // wait, join
} catch (InterruptedException e) {
e.printStackTrace();
}
});
t1.start();
Thread.sleep(1000);
System.out.println("iterrupt..");
t1.interrupt();
System.out.println(t1.isInterrupted()); // 如果是打断sleep,wait,join的线程, 即使打断了, 标记也为false
}
}
sleep...
iterrupt..
打断标记为:false
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at com.guizy.ThreadPrintDemo.lambda$main$0(ThreadPrintDemo.java:14)
at java.lang.Thread.run(Thread.java:748)
Process finished with exit code 0
Thread.currentThread().isInterrupted();
的返回值为true,可以判断Thread.currentThread().isInterrupted();
的值来手动停止线程public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
while(true) {
boolean interrupted = Thread.currentThread().isInterrupted();
if(interrupted) {
System.out.println("被打断了, 退出循环");
break;
}
}
}, "t1");
t1.start();
Thread.sleep(1000);
System.out.println("interrupt");
t1.interrupt();
System.out.println("打断标记为: "+t1.isInterrupted());
}
interrupt
被打断了, 退出循环
打断标记为: true
Process finished with exit code 0
当我们在执行线程一时,想要终止线程二,这是就需要使用interrupt方法来优雅的停止线程二。
Two Phase Termination,就是考虑在一个线程T1中如何优雅地终止另一个线程T2?这里的优雅指的是给T2线程一个处理其他事情的机会(如释放锁)。
如下所示:那么线程的isInterrupted()
方法可以取得线程的打断标记
sleep
期间被打断,打断标记是不会变的,为false
,但是sleep
期间被打断会抛出异常,我们据此手动设置打断标记为true
;程序正常运行期间被打断
的,那么打断标记就被自动设置为true
。处理好这两种情况那我们就可以放心地来料理后事啦!代码实现如下:
public class Test7 {
public static void main(String[] args) throws InterruptedException {
Monitor monitor = new Monitor();
monitor.start();
Thread.sleep(3500);
monitor.stop();
}
}
class Monitor {
Thread monitor;
/**
* 启动监控器线程
*/
public void start() {
//设置线控器线程,用于监控线程状态
monitor = new Thread() {
@Override
public void run() {
//开始不停的监控
while (true) {
//判断当前线程是否被打断了
if(Thread.currentThread().isInterrupted()) {
System.out.println("处理后续任务");
//终止线程执行
break;
}
System.out.println("监控器运行中...");
try {
//线程休眠
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
//如果是在休眠的时候被打断,不会将打断标记设置为true,这时要重新设置打断标记
Thread.currentThread().interrupt();
}
}
}
};
monitor.start();
}
/**
* 用于停止监控器线程
*/
public void stop() {
//打断线程
monitor.interrupt();
}
}
补充:
- sleep,join,yield,interrupted是Thread类中的方法
- wait/notify是object中的方法
- sleep 不释放锁、释放cpu
- join 释放锁、抢占cpu
- yiled 不释放锁、释放cpu
- wait 释放锁、释放cpu
Java进程
中有多个线程
在执行时,只有当所有非守护线程都执行完毕后,Java进程才会结束。但当非守护线程全部执行完毕后,守护线程无论是否执行完毕,也会一同结束。
注意:
垃圾回收器线程
就是一种守护线程- Tomcat 中的
Acceptor 和 Poller 线程
都是守护线程,所以 Tomcat 接收到 shutdown 命令后,不会等
初始状态
,仅仅是在语言层面上创建了线程对象,即Thead thread = new Thead();
,还未与操作系统线程关联可运行状态
,也称就绪状态
,指该线程已经被创建,与操作系统相关联,等待cpu给它分配时间片就可运行运行状态
,指线程获取了CPU时间片,正在运行
阻塞状态
与【可运行状态】的区别是,只要操作系统一直不唤醒线程,调度器就一直不会考虑调度它们,CPU就一直不会分配时间片
终止状态
,表示线程已经执行完毕,生命周期已经结束,不会再转换为其它状态Thread.State 枚举,分为六种状态
新建状态
、运行状态
(就绪状态, 运行中状态)、阻塞状态
、等待状态
、定时等待状态
、终止状态
NEW (新建状态)
线程刚被创建,但是还没有调用 start() 方法RUNNABLE (运行状态)
当调用了 start() 方法之后
,注意,Java API 层面的RUNNABLE 状态涵盖了操作系统层面的 【就绪状态】、【运行中状态】和【阻塞状态】
(由于 BIO 导致的线程阻塞,在 Java 里无法区分,仍然认为 是可运行)BLOCKED (阻塞状态)
, WAITING (等待状态)
, TIMED_WAITING(定时等待状态)
都是 Java API 层面对【阻塞状态】的细分,如sleep就位TIMED_WAITING, join为WAITING状态。后面会在状态转换一节详述。TERMINATED (结束状态)
当线程代码运行结束@Slf4j(topic = "c.TestState")
public class TestState {
public static void main(String[] args) throws IOException {
Thread t1 = new Thread("t1") { // new 状态
@Override
public void run() {
log.debug("running...");
}
};
Thread t2 = new Thread("t2") {
@Override
public void run() {
while(true) { // runnable 状态
}
}
};
t2.start();
Thread t3 = new Thread("t3") {
@Override
public void run() {
log.debug("running...");
}
};
t3.start();
Thread t4 = new Thread("t4") {
@Override
public void run() {
synchronized (TestState.class) {
try {
Thread.sleep(1000000); // timed_waiting 显示阻塞状态
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
t4.start();
Thread t5 = new Thread("t5") {
@Override
public void run() {
try {
t2.join(); // waiting 状态
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
t5.start();
Thread t6 = new Thread("t6") {
@Override
public void run() {
synchronized (TestState.class) { // blocked 状态
try {
Thread.sleep(1000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
t6.start();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("t1 state {}", t1.getState());
log.debug("t2 state {}", t2.getState());
log.debug("t3 state {}", t3.getState());
log.debug("t4 state {}", t4.getState());
log.debug("t5 state {}", t5.getState());
log.debug("t6 state {}", t6.getState());
}
}