1. 进程与线程
1.1 进程与线程
程序运行 ---> 开启进程
一个进程包含众多线程;一个线程包含众多指令
1.2 并行与并发
CPU、核(core)、线程
core ---> 单核/多核
多个线程在同一时间里需被 CPU 处理,CPU 为单核 CPU,CPU 需不停切换线程做处理 ---> 并发
多个线程在同一时间里需被 CPU 处理,CPU 为多核 CPU,当 CPU core数大于等于线程数,则每个线程都可分配一个 core 做处理 ---> 并行
并发 和 并行 可同时发生 ---> 1 < core 数 < 线程数
1.3(多线程、多核 CPU)应用
1.3.1(多线程、多核 CPU)应用之异步调用
异步、同步
需等待排在前面的指令运行结束才能运行 ---> 同步
无需等待排在前面的指令运行结束即可运行 ---> 异步
并发:多个任务在同一个时间段内同时执行,如果是单核心计算机,CPU 会不断地切换任务来完成并发操作。
并行:多任务在同一个时刻同时执行,计算机需要有多核心,每个核心独立执行一个任务,多个任务同时执行,不需要切换。
同步:多任务开始执行,任务 A、B、C 全部执行完成后才算是结束。
异步:多任务开始执行,只需要主任务 A 执行完成就算结束,主任务执行的时候,可以同时执行异步任务 B、C,主任务 A 可以不需要等待异步任务 B、C 的结果。
举一个例子
你的朋友在广州,但是有 2 辆小汽车在深圳,需要你帮忙把这 2 辆小汽车送到广州去。
同步的方式,你先开一辆小汽车到广州,然后再坐火车回深圳,再开另外一辆小汽车去广州。这是串行的方法,2 辆车需要的时间也就更长了。
异步的方式,你开一辆小汽车从深圳去广州,同时请一个代驾把另外一辆小汽车从深圳开去广州。这也就是并行方法,两个人两辆车,可以同时行驶,速度很快。
并发的方式,你一个人,先开一辆车走 500 米,停车跑回来,再开另外一辆车前行 1000 米,停车再跑回来,循环从深圳往广州开。并发的方式,你可以把 2 辆车一块送到朋友手里,但是过程还是很辛苦的。
1.3.2(多线程、多核 CPU)应用之提高效率
2. Java 线程
2.1 创建和运行线程
2.1.1 方法一 :直接使用 Thread
package com.rui.test;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.Test1")
public class Test1 {
public static void main(String[] args) {
Thread t1 = new Thread() {
@Override
public void run() {
log.debug("running");
}
};
t1.setName("t1");
t1.start();
}
}
2.1.2 :方法二 使用 Runnable 配合 Thread(推荐)
package com.rui.test;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.Test2")
public class Test2 {
public static void main(String[] args) {
Runnable runnable = new Runnable() {
@Override
public void run() {
log.debug("running");
}
};
Thread t2 = new Thread(runnable, "t2");
t2.start();
}
}
package com.rui.test;
import lombok.extern.slf4j.Slf4j;
/**
* lambda 简化
*/
@Slf4j(topic = "c.Test3")
public class Test3 {
public static void main(String[] args) {
Runnable runnable = () -> {
log.debug("running");
};
Thread t2 = new Thread(runnable, "t2");
t2.start();
}
}
方法一将线程(Thread)和任务(Runnable)合并,方法二将线程(Thread)和任务(Runnable)分开
2.1.3 方法三 :FutureTask 配合 Thread
package com.rui.test;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
@Slf4j(topic = "c.Test4")
public class Test4 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<Integer> ft = new FutureTask<>(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
log.debug("running");
Thread.sleep(1000);
return 100;
}
});
Thread t4 = new Thread(ft, "t4");
t4.start();
log.debug("{}", ft.get());
}
}
package com.rui.test;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* 练练手
*/
@Slf4j(topic = "c.Test5")
public class Test5 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<Integer> ft = new FutureTask<>(() -> {
log.debug("running");
Thread.sleep(1000);
return 100;
});
Thread t4 = new Thread(ft, "t4");
t4.start();
log.debug("{}", ft.get());
}
}
2.2 观察多个线程同时运行
package com.rui.test;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.Test6")
public class Test6 {
public static void main(String[] args) {
new Thread(() -> {
while (true) {
log.debug("running");
}
}, "t1").start();
new Thread(() -> {
while (true) {
log.debug("running");
}
}, "t2").start();
}
}
两个线程交替执行,执行先后由 CPU 决定
2.3 查看|杀死进程的方法 - Windows
2.3.1 任务管理器
2.3.2 CMD
PID - 进程 ID
tasklist - 查看进程
jps - 查看 Java 进程
taskkill - 杀死进程
格式 :taskkill /F /PID 进程 ID
2.4 原理之线程运行
2.4.1 栈与栈帧 (Java 虚拟机栈)
线程 对应 栈
方法 对应 栈帧
2.4.2 线程上下文切换
线程交还 CPU 的使用权即发生一次线程上下文切换
线程上下文切换会影响程序运行性能
导致线程上下文切换的情况:
1.线程的 CPU 时间片用完
2.垃圾回收
垃圾回收触发时, CPU 的使用权会交给垃圾回收线程,工作线程处于阻塞状态
3.有更高优先级的线程需要运行
4.线程主动调用了 sleep、yield、join等方法
2.5 常见方法
2.5.1 start 与 run
run
package com.rui.test;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.Test7")
public class Test7 {
public static void main(String[] args) {
Thread t1 = new Thread("t1") {
@Override
public void run() {
log.debug("running...");
log.debug("test...");
}
};
t1.run();
log.debug("do other things...");
}
}
start
package com.rui.test;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.Test8")
public class Test8 {
public static void main(String[] args) {
Thread t1 = new Thread("t1") {
@Override
public void run() {
log.debug("running...");
log.debug("test...");
}
};
t1.start();
log.debug("do other things...");
}
}
通过上述两条代码可以看出,执行 run 方法,由主线程调用,此时代码为同步调用;执行 start 方法,由 t1 线程调用,此时代码为异步调用。
2.5.2 sleep 与 yield
sleep
package com.rui.test;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.Test9")
public class Test9 {
public static void main(String[] args) {
Thread t1 = new Thread("t1") {
@Override
public void run() {
log.debug("enter sleep...");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("wake up...");
}
};
t1.start();
}
}
未调用 yield
package com.rui.test;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.Test11")
public class Test11 {
public static void main(String[] args) {
Thread t1 = new Thread("t1") {
@Override
public void run() {
int count = 0;
for (; ; ) {
System.out.println("--->" + count++);
}
}
};
t1.start();
Thread t2 = new Thread("t2") {
@Override
public void run() {
int count = 0;
for (; ; ) {
System.out.println(" --->" + count++);
}
}
};
t2.start();
}
}
yield
package com.rui.test;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.Test11")
public class Test11 {
public static void main(String[] args) {
Thread t1 = new Thread("t1") {
@Override
public void run() {
int count = 0;
for (; ; ) {
System.out.println("--->" + count++);
}
}
};
t1.start();
Thread t2 = new Thread("t2") {
@Override
public void run() {
int count = 0;
for (; ; ) {
Thread.yield();
System.out.println(" --->" + count++);
}
}
};
t2.start();
}
}
【2.6 知识提前了解】
调用 sleep 方法会使线程交出对 CPU 的使用权从运行状态进入阻塞状态,在该线程处于阻塞状态的时间里任务调度器不会为其分配时间片,待该线程阻塞状态结束会进入就绪状态,此时任务调度器可能会为其分配时间片
从 Java API 的角度分析,调用 sleep 方法会使线程由 RUNNABLE 转换为 TIMED_WAITING,线程进入睡眠
调用 yield 方法会使线程交出对 CPU 的使用权从运行状态进入就绪状态,此时任务调度器可能会为其分配时间片
【你可能会有疑问】为什么是可能会为其分配时间片呢?
当处于就绪状态的线程多于 CPU core 时,供不应求就会导致某些线程未分配到时间片
【让了个寂寞】若 CPU 空闲,例如 CPU core 数大于线程数时, 线程之间完全不需要谦让,此时某一线程调用 yield 方法是完全没有必要的。
【说句废话】若 CPU 繁忙时,yield 能很好地发挥作用。
【补充】线程优先级
类似于 yield 方法
线程优先级划分为 1 ~ 10 10 个等级
setPriority
public static final int MIN_PRIORITY = 1; //最小优先级
public static final int NORM_PRIORITY = 5; //默认优先级
public static final int MAX_PRIORITY = 10; //最大优先级
package com.rui.test;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.Test11")
public class Test12 {
public static void main(String[] args) {
Thread t1 = new Thread("t1") {
@Override
public void run() {
int count = 0;
for (; ; ) {
System.out.println("--->" + count++);
}
}
};
t1.start();
Thread t2 = new Thread("t2") {
@Override
public void run() {
int count = 0;
for (; ; ) {
System.out.println(" --->" + count++);
}
}
};
t2.start();
t1.setPriority(Thread.MIN_PRIORITY);
t2.setPriority(Thread.MAX_PRIORITY);
}
}
【说声抱歉】线程优先级同 yield 类似,唯有 CPU 繁忙时才可充分发挥其性能,此处由于博主的运行环境为多核 CPU,因此无法展示 CPU 繁忙时(例如单核 CPU )调用线程优先级方法后的执行效果。
2.5.3 join
未调用 join 方法
package com.rui.test;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.Test13")
public class Test13 {
static int r = 0;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread("t1") {
@Override
public void run() {
try {
log.debug("t1 start...");
Thread.sleep(2000);
r = 10;
log.debug("t1 end...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
t1.start();
log.debug("main start...");
log.debug("r = {}", r);
}
}
join - 同步等待
package com.rui.test;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.Test13")
public class Test13 {
static int r = 0;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread("t1") {
@Override
public void run() {
try {
log.debug("t1 start...");
Thread.sleep(2000);
r = 10;
log.debug("t1 end...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
t1.start();
log.debug("main start...");
t1.join();
log.debug("r = {}", r);
}
}
通过上述两条代码可以看出,调用 join 方法是一种同步调用思想的体现,在主线程中调用 t1 线程的 join 方法,使得主线程在运行到该代码时需等待 t1 线程运行结束才可继续执行
join(long millis)
package com.rui.test;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.Test13")
public class Test13 {
static int r = 0;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread("t1") {
@Override
public void run() {
try {
log.debug("t1 start...");
Thread.sleep(2000);
r = 10;
log.debug("t1 end...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
t1.start();
log.debug("main start...");
t1.join(1000);
log.debug("r = {}", r);
}
}
通过上述代码可以看出,在主线程中调用 t1 线程的 join(long millis) 方法,表示主线程在运行到该条代码时会等待 t1 线程运行 millis 时间或运行结束才继续执行。
2.5.4 interrupt
interrupt
package com.rui.test;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.Test14")
public class Test14 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread("t1") {
@Override
public void run() {
try {
log.debug("t1 start...");
Thread.sleep(2000);
log.debug("t1 end...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
t1.start();
Thread.sleep(1000);
log.debug("interrupt...");
t1.interrupt();
}
}
isInterrupt
默认的 isInterrupt(打断标记)= false
调用 sleep、wait、join 等方法的阻塞线程的 isInterrupt = false
运行线程的 isInterrupt = true
package com.rui.test;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.Test15")
public class Test15 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread("t1") {
@Override
public void run() {
log.debug("t1 start...");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
t1.start();
Thread t2 = new Thread("t2") {
@Override
public void run() {
log.debug("t2 start...");
while (true) {
}
}
};
t2.start();
Thread.sleep(1000);
t1.interrupt();
t2.interrupt();
log.debug("t1 线程的打断标记 : {}", t1.isInterrupted());
log.debug("t2 线程的打断标记 : {}", t2.isInterrupted());
}
}
2.5.5 【补充】模式之两阶段终止
两阶段终止模式在本阶段不算特别重要的设计模式,但博主觉得很不错呦~
package com.rui.test;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.Test16")
public class Test16 {
public static void main(String[] args) throws InterruptedException {
TwoPhaseTermination tpt = new TwoPhaseTermination();
tpt.start();
Thread.sleep(5000);
tpt.end();
}
}
@Slf4j(topic = "c.TwoPhaseTermination")
class TwoPhaseTermination {
private Thread t1;
public void start() {
t1 = new Thread("t1") {
@Override
public void run() {
boolean flag = Thread.currentThread().isInterrupted();
while (true) {
if (flag) {
log.debug("料理后事");
break;
}
try {
Thread.sleep(1000);
log.debug("无异常,执行监控记录");
} catch (InterruptedException e) {
e.printStackTrace();
flag = true;
}
}
}
};
t1.start();
}
public void end() {
t1.interrupt();
}
}
2.6 状态
2.6.1 五种状态 - 操作系统
初始状态、就绪状态(可运行状态)、运行状态、阻塞状态、终止状态
初始状态 : new Thread 之后,start 之前
就绪状态 : start 之后,线程还未获得 CPU 的使用权(任务调度器未给该线程分配时间片)
运行状态 : 线程获得了 CPU 的使用权
阻塞状态
终止状态 : 线程运行结束
运行状态转变为阻塞状态|就绪状态时会触发线程上下文切换
阻塞状态解除后会转换为就绪状态,而非运行状态
2.6.2 六种状态 - Java API
NEW : 对应操作系统中的初始状态
RUNNABLE : 对应操作系统中的就绪状态、运行状态、阻塞状态
TERMINATED : 对应操作系统中的结束状态
此外,Java API 中也有其自己的三种阻塞状态,需与操作系统中的阻塞状态区分开:
WAITING : join 触发
TIMED_WAITING : sleep 触发
BLOCKED : 锁触发
package com.rui.test;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.Test10")
public class Test10 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread("t1") {
@Override
public void run() {
}
};
Thread t2 = new Thread("t2") {
@Override
public void run() {
while (true) {
}
}
};
t2.start();
Thread t3 = new Thread("t3") {
@Override
public void run() {
}
};
t3.start();
Thread t4 = new Thread("t4") {
@Override
public void run() {
try {
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
t4.start();
Thread t5 = new Thread("t5") {
@Override
public void run() {
synchronized (Test10.class) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
t5.start();
Thread t6 = new Thread("t6") {
@Override
public void run() {
synchronized (Test10.class) {
}
}
};
t6.start();
Thread.sleep(500);
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());
}
}
2.7 守护线程
setDaemon(true)
线程均默认为非守护线程【setDaemon(false)】
在线程处于初始状态 | NEW 时将其设置为守护线程
进程中所有非守护线程运行结束后进程结束
若进程中所有非守护线程均已运行结束,但守护线程仍在运行,守护线程会被强制结束
package com.rui.test;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.Test17")
public class Test17 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread("t1") {
@Override
public void run() {
while (true) {
if (Thread.currentThread().isInterrupted()) {
break;
}
}
}
};
t1.setDaemon(true);
t1.start();
Thread.sleep(1000);
log.debug("end...");
}
}
2.8 习题
package com.rui.test;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.Test18")
public class Test18 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread("t1") {
@Override
public void run() {
try {
log.debug("洗水壶");
Thread.sleep(1000);
log.debug("烧开水");
Thread.sleep(15000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
t1.start();
log.debug("洗茶壶,洗茶杯,拿茶叶");
Thread.sleep(4000);
t1.join();
log.debug("泡茶");
}
}
说些废话
本篇文章为博主日常学习记录,故而会小概率存在各种错误,若您在浏览过程中发现一些,请在评论区指正,望我们共同进步,谢谢!