1、预热
idea新建maven工程,pom.xml如下:
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.10</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
</dependencies>
进程 && 线程
并发 && 并行
ps:RobPike :golang 语言的创造者,go可以写JVM
CPU多核单核与多线程单线程
单核 cpu 下,多线程不能实际提高程序运行效率,只是为了能够在不同的任务之间切换,不同线程轮流使用cpu ,不至于一个线程总占用 cpu,别的线程没法干活
多核 cpu 可以并行跑多个线程,但能否提高程序运行效率还是要分情况的。有些任务,经过精心设计,将任务拆分,并行执行,当然可以提高程序的运行效率。但不是所有计算任务都能拆分(参考后文的【阿姆达尔定律】)也不是所有任务都需要拆分,任务的目的如果不同,谈拆分和效率没啥意义
IO 操作不占用 cpu,只是我们一般拷贝文件使用的是【阻塞 IO】,这时相当于线程虽然不用 cpu,但需要一直等待 IO 结束,没能充分利用线程。所以才有后面的【非阻塞 IO】和【异步 IO】优化
2、java线程
2.1 线程状态
2.1.1 OS层面
2.1.2 java的API层面
package A03;
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
@Slf4j
public class Main10_thread_state {
public static void main(String[] args) throws IOException {
Thread t1 = new Thread("t1") {
@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 (Main10_thread_state.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 (Main10_thread_state.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());
System.in.read();
}
}
2.2 创建线程
2.2.1 创建方式一:Thread直接创建
@Slf4j(topic = "c.03001")
public class Main01 {
public static void main(String[] args) {
Thread t1 = new Thread("t1") {
@Override
public void run(){
log.debug("t1--hello");
}
};
t1.start();
log.debug("main--hello");
}
}
结果:
01:22:16.501 [main] DEBUG c.03001 - main--hello
01:22:16.501 [t1] DEBUG c.03001 - t1--hello
Process finished with exit code 0
2.2.2 创建方拾二:使用 Runnable 配合 Thread
@Slf4j(topic = "03002")
public class Main02 {
public static void main(String[] args) {
Runnable runnable = new Runnable() {
@Override
public void run() {
log.debug("Runnable--hello");
}
};
Thread t2 = new Thread(runnable, "t2");
t2.start();
log.debug("Main02--hello");
}
}
结果:
01:23:16.900 [main] DEBUG 03002 - Main02--hello
01:23:16.900 [t2] DEBUG 03002 - Runnable--hello
Process finished with exit code 0
思考:
原理之 Thread 与 Runnable 的关系
分析 Thread 的源码,理清它与 Runnable 的关系
小结
方法1 是把线程和任务合并在了一起,方法2 是把线程和任务分开了
用 Runnable 更容易与线程池等高级 API 配合
用 Runnable 让任务类脱离了 Thread 继承体系,可以更灵活
加入Lambda表达式
@Slf4j(topic = "03003")
public class Main03 {
public static void main(String[] args) {
/**
* 组合优于继承
*/
Runnable runnable = () -> log.debug("Runnable--hello");
Thread t2 = new Thread(runnable, "t2");
t2.start();
log.debug("Main03--hello");
Thread t3 = new Thread(() -> {log.debug("t3--hello");}, "t3");
t3.start();
}
}
**2.2.3 方式三,**FutureTask 配合 Thread
@Slf4j(topic = "c.03005")
public class Main04 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<Integer> task = new FutureTask<>(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
log.debug("call -- hello");
Thread.sleep(2000);
return 100;
}
});
Thread t1 = new Thread(task, "t1");
t1.start();
log.debug("{}",task.get());
}
}
加入Lambda
@Slf4j(topic = "c.03005")
public class Main05 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<Integer> task1 = new FutureTask<>(()-> {
log.debug("call -- hello");
Thread.sleep(2000);
return 100;
});
new Thread(task1, "t1").start();
log.debug("{}",task1.get());
}
}
2.3 java线程内幕
写一个程序,把debug调到线程模式
可以看到两条线程
代码
2.4 线程常见方法
start()
说明:启动一个新线程,在新的线程运行 run 方法中的代码
注意:start 方法只是让线程进入就绪,里面代码不一定立刻运行(CPU 的时间片还没分给它)。每个线程对象的start方法只能调用一次,如果调用了多次会出现IllegalThreadStateException
run()
说明;新线程启动后会调用的方法
注意:如果在构造 Thread 对象时传递了 Runnable 参数,则线程启动后会调用 Runnable 中的 run 方法,否则默认不执行任何操作。但可以创建 Thread 的子类对象,来覆盖默认行为。参考本文 2.2.2
join()
说明:等待线程运行结束
join(long n) :等待线程运行结束,最多等待 n 毫秒
拿来直接用,不要思考
isAlive():线程是否存活(还没有运行完毕)
interrupt()
isInterrupted() 和 interrupted()
ps: interrupted() 是 static方法
currentThread()
static 方法
获取当前正在执行的线程
sleep(long n)和yield()
其他不推荐的方法
2.5 操作一下
2.5.1 run 和 start
run方法启动都在main线程里面
start方法启动是新的线程
结论
直接调用 run 是在主线程中执行了 run,没有启动新的线程
使用 start 是启动新的线程,通过新的线程间接执行 run 中的代码
2.5.2 sleep 与 yield
sleep方法让线程进入Timed Waiting 状态(阻塞)
其它线程可以使用 interrupt 方法打断正在睡眠的线程,这时 sleep 方法会抛出 InterruptedException
sleep时间的可读性加强
sleep还可以保护CPU
线程优先级:两个线程代码没加干扰,累加数差距不大
使用yield方法,可以看到拉开差距
我们也可以设置线程的优先级
计算型程序CPU刷的一下就上去了,计算结束就掉下来了
2.5.3 join
没join之前得不到正确的结果,sleep了也没有用
join之后
评价
-
需要外部共享变量,不符合面向对象封装的思想
-
必须等待线程结束,不能配合线程池使用
下面看等待多个结果
t2和t1调换一下
有时效的join,等得不耐烦就不等了
子线任务提前完成不会死等,结果来了就结束等待
2.5.4 interrupt 方法
打断 sleep,wait,join 的线程
这几个方法都会让线程进入阻塞状态,但是打断标记会置为假
打断 sleep 的线程, 会清空打断状态,以 sleep 为例
打断正常运行的线程
打断正常运行的线程, 不会清空打断状态
判断打断标记来输出信息,优雅的方式,自己料理后事
终止模式之两阶段终止模式
Two Phase Termination
在一个线程 T1 中如何“优雅”终止线程 T2?这里的【优雅】指的是给 T2 一个料理后事的机会。
- 错误思路
使用线程对象的 stop() 方法停止线程
stop 方法会真正杀死线程,如果这时线程锁住了共享资源,那么当它被杀死后就再也没有机会释放锁,
其它线程将永远无法获取锁
使用 System.exit(int) 方法停止线程
目的仅是停止一个线程,但这种做法会让整个程序都停止 - 两阶段终止模式
代码实现
/**
* 模拟监控进程被打断
*/
@Test
public void test3() throws InterruptedException {
TwoPhaseTermination tpt = new TwoPhaseTermination();
tpt.start();
// tpt.start();
// tpt.start();
Thread.sleep(3500);
log.debug("停止监控");
tpt.stop();
}
@Test
public void test4() throws InterruptedException {
TwoPhaseTermination2 tpt = new TwoPhaseTermination2();
tpt.start();
Thread.sleep(3500);
log.debug("停止监控");
tpt.stop();
}
// 利用 isInterrupted
@Slf4j(topic = "c.TwoPhaseTermination")
class TwoPhaseTermination {
// 监控线程
private Thread monitorThread;
// 停止标记
private volatile boolean stop = false;
// 判断是否执行过 start 方法
private boolean starting = false;
// 启动监控线程
public void start() {
synchronized (this) {
if (starting) { // false
return;
}
starting = true;
}
monitorThread = new Thread(() -> {
while (true) {
Thread current = Thread.currentThread();
// 是否被打断
if (stop) {
log.debug("料理后事");
break;
}
try {
Thread.sleep(1000);
log.debug("执行监控记录");
} catch (InterruptedException e) {
}
}
}, "monitor");
monitorThread.start();
}
// 停止监控线程
public void stop() {
stop = true;
monitorThread.interrupt();
}
}
// 利用停止标记
@Slf4j(topic = "c.TwoPhaseTermination")
class TwoPhaseTermination2 {
// 监控线程
private Thread monitorThread;
// 判断是否执行过 start 方法
private boolean starting = false;
// 启动监控线程
public void start() {
synchronized (this) {
if (starting) { // false
return;
}
starting = true;
}
monitorThread = new Thread(() -> {
while (true) {
Thread current = Thread.currentThread();
// 是否被打断
if (current.isInterrupted()) {
log.debug("料理后事");
break;
}
try {
Thread.sleep(1000);
log.debug("执行监控记录");
} catch (InterruptedException e) {
//sleep出现异常之后,会把打断标记置为 false
// 这边需要重置打断标记
current.interrupt();
}
}
}, "monitor");
monitorThread.start();
}
// 停止监控线程
public void stop() {
monitorThread.interrupt();
}
}
2.5.5 park
执行了park方法就蚌住了,程序不走了
他又行了
3、主线程与守护线程
4、练习
烧水泡茶
办法1最好,但是思考一下
代码敲一敲
5、总结
线程创建
- 线程重要 api,如 start,run,sleep,join,interrupt 等
线程状态
应用方面
异步调用:主线程执行期间,其它线程异步执行耗时操作
提高效率:并行计算,缩短运算时间
同步等待:join
统筹规划:合理使用线程,得到最优效果
原理方面
线程运行流程:栈、栈帧、上下文切换、程序计数器
Thread 两种创建方式 的源码
模式方面
了
[外链图片转存中…(img-SuV9KXcn-1652945436725)]
[外链图片转存中…(img-ajQO7eFb-1652945436725)]
他又行了
[外链图片转存中…(img-jFTYIy7k-1652945436726)]
3、主线程与守护线程
[外链图片转存中…(img-g1gqlBCt-1652945436726)]
4、练习
烧水泡茶
[外链图片转存中…(img-hN2qRKpi-1652945436726)]
办法1最好,但是思考一下[外链图片转存中…(img-FrbNxLdv-1652945436726)]
代码敲一敲
[外链图片转存中…(img-tejvfZBN-1652945436727)]
5、总结
线程创建
- 线程重要 api,如 start,run,sleep,join,interrupt 等
线程状态
应用方面
异步调用:主线程执行期间,其它线程异步执行耗时操作
提高效率:并行计算,缩短运算时间
同步等待:join
统筹规划:合理使用线程,得到最优效果
原理方面
线程运行流程:栈、栈帧、上下文切换、程序计数器
Thread 两种创建方式 的源码
模式方面
- 终止模式之两阶段终止