3.1. 创建和运行线程
3.1.1. 方法一,直接使用Thread
/**
* @description: Thread 创建线程
* @author: teago
* @time: 2020/5/16 08:39
*/
@Slf4j(topic = "Example1")
public class Example1 {
public static void main(String[] args) {
Thread createThread = new Thread(() -> {
log.debug("thread mode create thread");
});
createThread.start();
}
}
3.1.2. 方法二,使用Runnable配合Thread
把【线程】和【任务】(要执行的代码)分开
- Thread 代表线程
- Runnable 可运行的任务(线程要执行的代码
package com.bloom.concurrent.three;
import lombok.extern.slf4j.Slf4j;
/**
* @description: Runnable 创建线程
* @author: teago
* @time: 2020/5/16 08:39
*/
@Slf4j
public class Example2 {
public static void main(String[] args) {
Runnable runnable = () -> log.debug(Thread.currentThread().getName());
Thread thread = new Thread(runnable);
thread.start();
}
}
3.1.3. 原理之Thread与Runnable
分析Thread的源码,理清它与Runnable的关系
小结:
3.1.4. FutureTask配合Thread
FutureTask 能够接收 Callable 类型的参数,用来处理有返回结果的情况,线程阻塞等待task执行完毕的结果。
package com.bloom.concurrent.three;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* @description: FutureTask配合Thread
* @author: teago
* @time: 2020/5/16 08:39
*/
@Slf4j
public class Example3 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<String> futureTask = new FutureTask<>(() -> "futureTask");
Thread thread = new Thread(futureTask);
thread.setName("futureTask mode create thread ");
thread.start();
thread.join();
// 主线程阻塞,同步等待 task 执行完毕的结果
log.debug(futureTask.get());
}
}
3.2. 观察多个线程同时运行
主要是理解:
- 交替执行
- 谁先谁后,不由我们控制
package com.bloom.concurrent.three;
import java.util.concurrent.TimeUnit;
/**
* @description:
* @author: teago
* @time: 2020/5/16 08:53
*/
public class Example4 {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
while (true){
System.out.println("test jconsole use");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"t4");
thread.start();
}
}
运行结果:
3.3. 查看进程线程的方法
3.3.1. Windows
3.3.2. Linux
-
ps -ef
查看所有进程 -
ps -fT -p <PID>
查看某个进程(PID)的所有线程 -
kill
杀死进程 -
top
按大写 H 切换是否显示线程 -
top -H -p <PID>
查看某个进程(PID)的所有线程
3.3.3. Java
3.3.3.1. jconsole远程监控配置
- 编写自己的程序上传到服务器
package com.bloom.concurrent.three;
import java.util.concurrent.TimeUnit;
/**
* @description:
* @author: teago
* @time: 2020/5/16 08:53
*/
public class Example4 {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
while (true){
System.out.println("test jconsole use");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"t4");
thread.start();
}
}
- 使用javac Example4.java 编译程序。
- 使用如下代码运行Example4
java -Djava.rmi.server.hostname=127.0.0.1
-Dcom.sun.management.jmxremote
-Dcom.sun.management.jmxremote.port=1234
-Dcom.sun.management.jmxremote.ssl=false
-Dcom.sun.management.jmxremote.authenticate=false Example4
- 然后出现一下错误:
- 这时候需要进行如下操作:
- 复制 jmxremote.password 文件
- 修改jmxremote.password信息
controlRole 1234
- 再次运行还是出现以下错误:
- 需要修改jmxremote.password 和 jmxremote.access 文件的权限为600即文件所有者可读写:
chmod -R 600 jmxremote.password
chmod -R 600 jmxremote.access
再次运行,结果如下所示:
- 在本地启动jconsole,然后进行如下操作:
- 选择连接-->不安全连接
- 运行如下:
总结:
- 需要以如下方式运行你的 java 类
java -Djava.rmi.server.hostname=`ip地址`
-Dcom.sun.management.jmxremote
-Dcom.sun.management.jmxremote.port=`连接端口`
-Dcom.sun.management.jmxremote.ssl=是否安全连接
-Dcom.sun.management.jmxremote.authenticate=是否认证 java类
- 复制 jmxremote.password 文件
- 修改 jmxremote.password 和 jmxremote.access 文件的权限为 600 即文件所有者可读写
- 连接时填入 controlRole(用户名),R&D(密码
如果要认证访问,还需要做如下步骤:
3.4. 原理之线程运行
3.4.1. 栈与栈帧
Java Virtual Machine Stacks (Java虚拟机栈)
我们都知道JVM中有堆、栈、方法区所组成,其中栈内存是给谁用的呢?其实就是线程,每个线程启动后,虚拟机都会为其分配一块栈内存。
3.4.1.1. Debug方式演示栈帧的调用关系
- 单线程演示:
package com.bloom.concurrent.three;
/**
* @description: Debug方式演示栈帧的调用关系
* @author: teago
* @time: 2020/5/16 09:06
*/
public class TestFrames {
public static void main(String[] args) {
method1(10);
}
private static void method1(int x) {
int y = x + 1 ;
Object m = method2();
System.out.println(m);
}
private static Object method2() {
Object n = new Object();
return n ;
}
}
3.4.2. 线程上线文切换(Thread Context Switch)
因为以下一些原因导致CPU不再执行当前的线程,转而执行另一个线程的代码:
- 被动的:
- 线程的CPU时间片用完
- 垃圾回收
- 有更高优先级的线程需要运行
- 主动的:
- 线程自己调用了sleep、yield、wait、join、park、synchronized、lock等方法
当Context Switch发生时,需要由操作系统保存当前线程的状态信息,并恢复另一个线程的状态,Java中对应的概念就是程序计数器(Program Counter Register),它的作用是记住下一条JVM指令的执行地址,是线程私有的。
3.5. 常见方法
方法名 | static | 功能说明 | 注意 |
---|---|---|---|
start() | 启动一个新线程,在新的线程运行 run 方法中的代码 | start 方法只是让线程进入就绪,里面代码不一定立刻运行(CPU 的时间片还没分给它)。每个线程对象的start方法只能调用一次,如果调用了多次会出现 IllegalThreadStateException | |
run() | 新线程启动后会调用的方法 | 如果在构造 Thread 对象时传递了 Runnable 参数,则线程启动后会调用 Runnable 中的 run 方法,否则默认不执行任何操作。但可以创建 Thread 的子类对象,来覆盖默认行为 | |
join() | 等待线程运行结束 | ||
join(long n) | 等待线程运行结束,最多等待 n 毫秒 | ||
getId() | 获取线程长整型的 id | id 唯一 | |
getName() | 获取线程名 | ||
setName(String) | 修改线程名 | ||
getPriority() | 获取线程优先级 | ||
setPriority(int) | 修改线程优先级 | java中规定线程优先级是1~10 的整数,较大的优先级能提高该线程被 CPU 调度的机率 | |
getState() | 获取线程状态 | Java 中线程状态是用 6 个 enum 表示,分别为:NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED | |
isInterrupted() | 判断是否被打断, | 不会清除 打断标记
|
|
isAlive() | 线程是否存活(还没有运行完毕) | ||
interrupt() | 打断线程 | 如果被打断线程正在 sleep,wait,join 会导致被打断的线程抛出 InterruptedException,并清除打断标记 ;如果打断的正在运行的线程,则会设置 打断标记 ;park 的线程被打断,也会设置打断标记
|
|
interrupted() | static | 判断当前线程是否被打断 | 会清除 打断标记
|
currentThread() | static | 获取当前正在执行的线程 | |
sleep(long n) | static | 让当前执行的线程休眠n毫秒,休眠时让出 cpu 的时间片给其它线程 | |
yield() | static | 提示线程调度器让出当前线程对CPU的使用 | 主要是为了测试和调试 |
3.5.1. start与run
3.5.1.1. 调用run
package com.bloom.concurrent.three;
import lombok.extern.slf4j.Slf4j;
/**
* @description:
* @author: teago
* @time: 2020/5/16 10:22
*/
@Slf4j
public class Example5 {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
log.info(Thread.currentThread().getName() + ">>>>>> t1 execute");
});
// log.info(thread.getState().name());
thread.run();
// log.info(thread.getState().name());
// thread.start();
// log.info(thread.getState().name());
}
}
运行结果
10:57:48 [main] com.bloom.concurrent.three.Example5 - main>>>>>> t1 execute
3.5.1.2. 调用start
package com.bloom.concurrent.three;
import lombok.extern.slf4j.Slf4j;
/**
* @description:
* @author: teago
* @time: 2020/5/16 10:22
*/
@Slf4j
public class Example5 {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
log.info(Thread.currentThread().getName() + ">>>>>> t1 execute");
});
// log.info(thread.getState().name());
// thread.run();
// log.info(thread.getState().name());
thread.start();
// log.info(thread.getState().name());
}
}
运行结果:
10:58:53 [Thread-0] com.bloom.concurrent.three.Example5 - Thread-0>>>>>> t1 execute
小结
- 直接调用 run 是在主线程中执行了 run,没有启动新的线程
- 使用 start 是启动新的线程,通过新的线程间接执行 run 中的代码
修改以上代码:
log.info(thread.getState().name());
thread.run();
log.info(thread.getState().name());
thread.start();
log.info(thread.getState().name());
运行结果:(也说明了上述的问题)
1:00:04 [main] com.bloom.concurrent.three.Example5 - NEW
11:00:04 [main] com.bloom.concurrent.three.Example5 - main>>>>>> t1 execute
11:00:04 [main] com.bloom.concurrent.three.Example5 - NEW
11:00:04 [main] com.bloom.concurrent.three.Example5 - RUNNABLE
11:00:04 [Thread-0] com.bloom.concurrent.three.Example5 - Thread-0>>>>>> t1 execute
3.5.2. sleep与yield
3.5.2.1. sleep
- 调用sleep会让当前线程从Running进入Timed Waiting状态(阻塞);
- 但是这种状态只能通过一些工具可以看到,使用如下是不能知道的
package com.beatshadow.concurrent.chapter3;
import java.util.concurrent.TimeUnit;
/**
*
* @author : <a href="mailto:gnehcgnaw@gmail.com">gnehcgnaw</a>
* @since : 2020/4/27 22:12
*/
public class Example6 {
public static void main(String[] args) {
new Thread("td1"){
@Override
public void run() {
try {
System.out.println(this.getState());
TimeUnit.MINUTES.sleep(1);
System.out.println(this.getState());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();
}
}
- 其他线程可以使用interrupt方法打断正在睡眠的线程,这时sleep方法会抛出InterruptedException;
- 不打断的情况
package com.bloom.concurrent.three;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
/**
* @description:
* @author: teago
* @time: 2020/5/16 10:47
*/
@Slf4j
public class Example7 {
@SneakyThrows
public static void main(String[] args) {
Thread thread = new Thread("td2"){
@Override
public void run() {
try {
log.info("thread execute");
Thread.sleep(2000);
log.info("thread execute end");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
thread.start();
log.info("main thread execute start");
Thread.sleep(1000);
log.info("main thread execute end");
// thread_execute.interrupt();
}
}
运行结果如下所示:(程序没有出错,线程在休眠之后继续执行)
11:12:31 [main] com.bloom.concurrent.three.Example7 - main thread execute start
11:12:31 [td2] com.bloom.concurrent.three.Example7 - thread execute
11:12:32 [main] com.bloom.concurrent.three.Example7 - main thread execute end
11:12:33 [td2] com.bloom.concurrent.three.Example7 - thread execute end
修改程序,添加interrupt()的调用:
thread.interrupt();
运行结果如下所示:
11:13:23 [main] com.bloom.concurrent.three.Example7 - main thread execute start
11:13:23 [td2] com.bloom.concurrent.three.Example7 - thread execute
11:13:24 [main] com.bloom.concurrent.three.Example7 - main thread execute end
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at com.bloom.concurrent.three.Example7$1.run(Example7.java:21)
总结:线程被打断,sleep之后的未被执行,因为程序出现异常。
3.5.2.2. yield
让出,谦让的意思。
- 调用yield会让当前线程从Running进入Runnable就绪状态,然后调度执行其它同优先级的线程。如果这时没有同优先级的线程,那么不能保证让当前线程暂停的效果;
- 具体的实现依赖于操作系统的任务调度器。(想让没让出去)
3.5.2.3. sleep与yield的区别
sleep让线程从running变成waiting状态,yield让线程从running变成runnable状态;
runnable状态的线程有可能被执行,而waiting状态的线程只有能到线程休眠之后才又可能被执行。
3.5.2.4. 线程优先级(不靠谱)
3.5.2.5. 案例——效率篇:防止CPU占用100%(sleep实现)
示例代码:
package com.bloom.concurrent.three;
import lombok.extern.slf4j.Slf4j;
/**
* @description:
* @author: teago
* @time: 2020/5/16 11:23
*/
@Slf4j
public class Example8 {
public static void main(String[] args) {
new Thread(() -> {
while (true){
//业务代码
}
}).start();
}
}
1.sleep实现
让线程睡眠一段时间。
package com.bloom.concurrent.three;
/**
* @description:
* @author: teago
* @time: 2020/5/16 11:26
*/
public class Example9 {
public static void main(String[] args) {
new Thread(() -> {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
在没有利用CPU计算时,不要让while(true)空转去浪费cpu,这时可以使用sleep或者yield来让出cpu的使用权给其他程序。
3.5.3. join方法详解
package com.bloom.concurrent.three;
import lombok.extern.slf4j.Slf4j;
import static java.lang.Thread.sleep;
/**
* @description:
* @author: teago
* @time: 2020/5/16 11:28
*/
@Slf4j
public class Example10 {
static int r = 0;
public static void main(String[] args) {
test1();
}
private static void test1() {
log.debug("开始");
Thread t1 = new Thread(() -> {
log.debug("开始");
try {
sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("结束");
r = 10;
});
t1.start();
log.debug("结果为:{}", r);
log.debug("结束");
}
}
3.5.3.1. 为什么需要join
上面的代码执行,打印 r 是什么?
- 因为主线程和线程 t1 是并行执行的,t1 线程需要 1 秒之后才能算出
r=10
- 而主线程一开始就要打印 r 的结果,所以只能打印出
r=0
解决方法
3.5.3.2. 应用之同步(案例一)
以调用方角度来讲,如果
3.5.3.3. 等待多个结果
问,下面代码 cost 大约多少秒?
package com.bloom.concurrent.three;
import lombok.extern.slf4j.Slf4j;
import static java.lang.Thread.sleep;
/**
* @description:
* @author: teago
* @time: 2020/5/16 16:36
*/
@Slf4j
public class Example11 {
static int r1 = 0;
static int r2 = 0;
public static void main(String[] args) throws InterruptedException {
test2();
}
private static void test2() throws InterruptedException {
Thread t1 = new Thread(() -> {
try {
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
r1 = 10;
});
Thread t2 = new Thread(() -> {
try {
sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
r2 = 20;
});
long start = System.currentTimeMillis();
t1.start();
t2.start();
t1.join();
t2.join();
long end = System.currentTimeMillis();
log.debug("r1: {} r2: {} cost: {}", r1, r2, end - start);
}
}
分析如下
- 第一个 join:等待 t1 时, t2 并没有停止, 而在运行
- 第二个 join:1s 后, 执行到此, t2 也运行了 1s, 因此也只需再等待 1s
如果颠倒两个 join 呢?
最终都是输出
16:38:53 [main] com.bloom.concurrent.three.Example11 - r1: 10 r2: 20 cost: 2003
3.5.3.4. 有时效的join
等够时间
static int r1 = 0;
static int r2 = 0;
public static void main(String[] args) throws InterruptedException {
test3();
}
public static void test3() throws InterruptedException {
Thread t1 = new Thread(() -> {
sleep(1);
r1 = 10;
});
long start = System.currentTimeMillis();
t1.start();
// 线程执行结束会导致 join 结束
t1.join(1500);
long end = System.currentTimeMillis();
log.debug("r1: {} r2: {} cost: {}", r1, r2, end - start);
}
输出
20:48:01.320 [main] c.TestJoin - r1: 10 r2: 0 cost: 1010
没等够时间
static int r1 = 0;
static int r2 = 0;
public static void main(String[] args) throws InterruptedException {
test3();
}
public static void test3() throws InterruptedException {
Thread t1 = new Thread(() -> {
sleep(2);
r1 = 10;
});
long start = System.currentTimeMillis();
t1.start();
// 线程执行结束会导致 join 结束
t1.join(1500);
long end = System.currentTimeMillis();
log.debug("r1: {} r2: {} cost: {}", r1, r2, end - start);
}
输出
20:52:15.623 [main] c.TestJoin - r1: 0 r2: 0 cost: 1502
3.6. interrupt方法详解
3.6.1. 打断sleep、wait、join的线程(阻塞)
这几个方法都会让线程进入阻塞状态
打断 sleep 的线程, 会清空打断状态(即:Thread.isInterrupt()= false),以 sleep 为例
package com.bloom.concurrent.three;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.TimeUnit;
/**
* @description:
* @author: teago
* @time: 2020/5/16 16:44
*/
@Slf4j
public class Example12 {
@SneakyThrows
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "t1");
log.info("t1 start");
t1.start();
TimeUnit.SECONDS.sleep(1);
t1.interrupt();
log.info("t1 interrupt");
log.info(t1.isInterrupted() + "");
}
}
运行结果:
16:48:55 [main] com.bloom.concurrent.three.Example12 - t1 start
16:48:56 [main] com.bloom.concurrent.three.Example12 - t1 interrupt
16:48:56 [main] com.bloom.concurrent.three.Example12 - false
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at java.lang.Thread.sleep(Thread.java:340)
at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
at com.bloom.concurrent.three.Example12.lambda$main$0(Example12.java:20)
at java.lang.Thread.run(Thread.java:748)
Process finished with exit code 0
3.6.2. 打断正常运行的线程
package com.beatshadow.concurrent.chapter3;
import lombok.extern.slf4j.Slf4j;
/**
* @author : <a href="mailto:gnehcgnaw@gmail.com">gnehcgnaw</a>
* @since : 2020/4/28 11:58
*/
@Slf4j
public class Example13 {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
while (true){
}
});
log.debug("thread start");
thread.start();
log.debug("thread interrupt");
thread.interrupt();
}
}
运行结果:
正常运行的线程如果被打断,线程并不会直接结束运行,只是说将interrupt的状态置为了true,这时候需要我们认为的代码进行干涉从而结束线程。
修改程序,使其优雅打断执行的线程:
package com.beatshadow.concurrent.chapter3;
import lombok.extern.slf4j.Slf4j;
/**
* @author : <a href="mailto:gnehcgnaw@gmail.com">gnehcgnaw</a>
* @since : 2020/4/28 11:58
*/
@Slf4j
public class Example13 {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
while (true){
//认为打断线程
if (Thread.currentThread().isInterrupted()) {
log.debug("线程被打断");
break;
}
}
});
log.debug("thread start");
thread.start();
log.debug("thread interrupt");
//这时候用户线程并未结束,只是说被打断的线程的interrupt=true ,这时候需要我们根据这个状态进行人工干涉
thread.interrupt();
}
}
3.6.3. 模式之两阶段终止模式
Two Phase Termination
在一个线程T1只如何“优雅”终止线程T2?这里的【优雅】指的是给T2一个料理后事的机会。
3.6.3.1. 错误思路
- 使用线程对象的stop()方法停止线程
- stop方法会真正杀死线程,如果这时线程锁住了共享资源,那么当它被杀死后就再也没有机会释放锁,其他线程永远无法获取锁。
- 使用System.exit(int)方法停止线程
3.6.3.2. 两阶段终止模式
Java多线程编程实战指南
——两阶段终止模式
演示代码:
package com.bloom.concurrent.three;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.TimeUnit;
/**
* @description:
* @author: teago
* @time: 2020/5/16 17:09
*/
public class Example15 {
public static void main(String[] args) {
TwoPhaseTermination twoPhaseTermination = new TwoPhaseTermination();
twoPhaseTermination.start("demo");
try {
Thread.sleep(3500);
} catch (InterruptedException e) {
e.printStackTrace();
}
twoPhaseTermination.stop();
}
}
@Slf4j
class TwoPhaseTermination {
private Thread thread;
public void start(String threadName) {
log.info(threadName + "线程启动");
thread = new Thread(() -> {
while (true) {
if (thread.isInterrupted()) {
log.info("料理后事");
break;
} else {
try {
TimeUnit.SECONDS.sleep(2); // 打断1
log.info("执行监控记录");
} catch (InterruptedException e) {
e.printStackTrace();
// 重新设置打断标记
thread.interrupt();
}
}
}
}, threadName);
thread.start();
}
public void stop() {
if (null != thread) {
thread.interrupt();
}
}
}
运行结果:
17:16:17 [main] com.bloom.concurrent.three.TwoPhaseTermination - demo线程启动
17:16:19 [demo] com.bloom.concurrent.three.TwoPhaseTermination - 执行监控记录
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at java.lang.Thread.sleep(Thread.java:340)
at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
at com.bloom.concurrent.three.TwoPhaseTermination.lambda$start$0(Example15.java:40)
at java.lang.Thread.run(Thread.java:748)
17:16:21 [demo] com.bloom.concurrent.three.TwoPhaseTermination - 料理后事
3.6.4. 打断park线程
park:https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/locks/LockSupport.html#park--
Thread thread = new Thread(() -> {
log.debug("thread execute");
LockSupport.park();
log.debug("unpark");
log.debug("continue to execute");
});
thread.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// thread.interrupt();
}
运行结果:
使用了park()方法,以下的程序不会执行。
使用Thread.interrupt()可以打断park的中断
package com.bloom.concurrent.three;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.locks.LockSupport;
/**
* @description:
* @author: teago
* @time: 2020/5/16 17:23
*/
@Slf4j
public class Example16 {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
log.debug("thread execute");
LockSupport.park();
log.debug("unPark");
log.debug("continue to execute");
});
thread.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread.interrupt();
}
}
运行结果:
但是被中断的线程如果interrupt = true,后续要执行park,则不会被停止中断
演示代码:
package com.bloom.concurrent.three;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.locks.LockSupport;
/**
* @description:
* @author: teago
* @time: 2020/5/16 17:23
*/
@Slf4j
public class Example16 {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
log.debug("thread execute");
LockSupport.park();
log.debug("unPark");
log.debug("continue to execute");
LockSupport.park();
log.debug("seconds continue to execute ");
});
thread.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread.interrupt();
}
}
运行结果:(发现第二次调用park(),并没有被终止后续代码的执行)
这时候需要使用Thread.interrupted()清除打断标记,具体如下所示:
package com.bloom.concurrent.three;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.locks.LockSupport;
/**
* @description:
* @author: teago
* @time: 2020/5/16 17:23
*/
@Slf4j
public class Example16 {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
log.debug("thread execute");
LockSupport.park();
//清除打断标记
Thread.interrupted();
log.debug("unPark");
log.debug("continue to execute");
LockSupport.park();
log.debug("seconds continue to execute ");
});
thread.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread.interrupt();
}
}
3.7. 不推荐使用的方法
还有一些不推荐使用的方法,这些方法已过时,容易破坏同步代码块,造成线程死锁
方法名 | static | 功能说明 |
---|---|---|
stop() | 停止线程运行 | |
suspend() | 挂起(暂停)线程运行 | |
resume() | 恢复线程运行 |
3.8. 主线程与守护线程
默认情况下,Java 进程需要等待所有线程都运行结束,才会结束。
package com.beatshadow.concurrent.chapter3;
import lombok.extern.slf4j.Slf4j;
/**
* @author : <a href="mailto:gnehcgnaw@gmail.com">gnehcgnaw</a>
* @since : 2020/4/28 14:45
*/
@Slf4j
public class Example16 {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
while (true){
}
});
thread.start();
try {
Thread.sleep(1000);
log.debug("main execute finish");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行结果:
原因:(主线程执行完成,但是进程并未停止,原因是thread线程还在执行)
验证:在main线程执行完之后,在if处打断点,有如下情况:
说明:此时thread线程还在执行。
那如何结束这个线程呢?
设置为守护线程,有一种特殊的线程叫做守护线程,只要其它非守护线程运行结束了,即使守护线程的代码没有执行完,也会强制结束。
使用thread.setDaemon(true);
package com.beatshadow.concurrent.chapter3;
import lombok.extern.slf4j.Slf4j;
/**
* @author : <a href="mailto:gnehcgnaw@gmail.com">gnehcgnaw</a>
* @since : 2020/4/28 14:45
*/
@Slf4j
public class Example16 {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
while (true){
if (Thread.currentThread().isInterrupted()){
break;
}
}
});
//设置当前线程为守护线程,默认为false
thread.setDaemon(true);
thread.start();
try {
Thread.sleep(1000);
log.debug("main execute finish");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
**注意 **
- 垃圾回收器线程就是一种守护线程(当线程结束之后,就不需要进行垃圾回收了)
- Tomcat 中的 Acceptor 和 Poller 线程都是守护线程,所以 Tomcat 接收到 shutdown 命令后,不会等待它们处理完当前请求
3.9. 线程——五种状态(操作系统层面)
这是从 操作系统 层面来描述的
- 【初始状态】仅是在语言层面创建了线程对象,还未与操作系统线程关联
- 【可运行状态】(就绪状态)指该线程已经被创建(与操作系统线程关联),可以由 CPU 调度执行
- 【运行状态】指获取了 CPU 时间片运行中的状态
- 当 CPU 时间片用完,会从【运行状态】转换至【可运行状态】,会导致线程的上下文切换
- 【阻塞状态】
- 如果调用了阻塞 API,如 BIO 读写文件,这时该线程实际不会用到 CPU,会导致线程上下文切换,进入【阻塞状态】
- 等 BIO 操作完毕,会由操作系统唤醒阻塞的线程,转换至【可运行状态】
- 与【可运行状态】的区别是,对【阻塞状态】的线程来说只要它们一直不唤醒,调度器就一直不会考虑调度它们
- 【终止状态】表示线程已经执行完毕,生命周期已经结束,不会再转换为其它状态
3.10. 线程——六种状态
3.10.1. 概述
这是从 Java API 层面来描述的
根据 Thread.State 枚举,分为六种状态
-
NEW
线程刚被创建,但是还没有调用start()
方法 -
RUNNABLE
当调用了start()
方法之后,注意,Java API 层面的RUNNABLE
状态涵盖了 操作系统 层面的【可运行状态】、【运行状态】和【阻塞状态】(由于 BIO 导致的线程阻塞,在 Java 里无法区分,仍然认为是可运行) -
BLOCKED
,WAITING
,TIMED_WAITING
都是 Java API 层面对【阻塞状态】的细分,后面会在状态转换一节详述 -
TERMINATED
当线程代码运行结束
3.10.2. 演示代码
package com.bloom.concurrent.three;
import lombok.extern.slf4j.Slf4j;
/**
* @author : <a href="mailto:gnehcgnaw@gmail.com">gnehcgnaw</a>
* @since : 2020/4/28 15:21
*/
@Slf4j
public class TestState {
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
}, "t1");
Thread thread2 = new Thread(() -> {
while (true) {
}
}, "t2");
thread2.start();
Thread thread3 = new Thread(() -> {
try {
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "t3");
thread3.start();
Thread thread4 = new Thread(() -> {
synchronized (TestState.class) {
try {
Thread.sleep(100000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "t4");
thread4.start();
Thread thread5 = new Thread(() -> {
synchronized (TestState.class) {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "t5");
thread5.start();
Thread thread6 = new Thread(() -> {
}, "t6");
thread6.start();
log.debug(String.valueOf(thread1.getState()));
log.debug(String.valueOf(thread2.getState()));
log.debug(String.valueOf(thread3.getState()));
log.debug(String.valueOf(thread4.getState()));
log.debug(String.valueOf(thread5.getState()));
log.debug(String.valueOf(thread6.getState()));
}
}
运行结果:
3.11. 应用之统筹
3.11.1. 分析
阅读华罗庚《统筹方法》,给出烧水泡茶的多线程解决方案,提示
-
参考图二,用两个线程(两个人协作)模拟烧水泡茶过程
- 文中办法乙、丙都相当于任务串行
- 而图一相当于启动了 4 个线程,有点浪费
- 用 sleep(n) 模拟洗茶壶、洗水壶等耗费的时间
附:华罗庚《统筹方法》
统筹方法,是一种安排工作进程的数学方法。它的实用范围极广泛,在企业管理和基本建设中,以及关系复杂的科研项目的组织与管理中,都可以应用。
怎样应用呢?主要是把工序安排好。
比如,想泡壶茶喝。当时的情况是:开水没有;水壶要洗,茶壶、茶杯要洗;火已生了,茶叶也有了。怎么办?
- 办法甲:洗好水壶,灌上凉水,放在火上;在等待水开的时间里,洗茶壶、洗茶杯、拿茶叶;等水开了,泡茶喝。
- 办法乙:先做好一些准备工作,洗水壶,洗茶壶茶杯,拿茶叶;一切就绪,灌水烧水;坐待水开了,泡茶喝。
- 办法丙:洗净水壶,灌上凉水,放在火上,坐待水开;水开了之后,急急忙忙找茶叶,洗茶壶茶杯,泡茶喝。
哪一种办法省时间?我们能一眼看出,第一种办法好,后两种办法都窝了工。
这是小事,但这是引子,可以引出生产管理等方面有用的方法来。
水壶不洗,不能烧开水,因而洗水壶是烧开水的前提。没开水、没茶叶、不洗茶壶茶杯,就不能泡茶,因而这些又是泡茶的前提。它们的相互关系,可以用下边的箭头图来表示:
> 从这个图上可以一眼看出,办法甲总共要16分钟(而办法乙、丙需要20分钟)。如果要缩短工时、提高工作效率,应当主要抓烧开水这个环节,而不是抓拿茶叶等环节。同时,洗茶壶茶杯、拿茶叶总共不过4分钟,大可利用“等水开”的时间来做。
是的,这好像是废话,卑之无甚高论。有如走路要用两条腿走,吃饭要一口一口吃,这些道理谁都懂得。但稍有变化,临事而迷的情况,常常是存在的。在近代工业的错综复杂的工艺过程中,往往就不是像泡茶喝这么简单了。任务多了,几百几千,甚至有好几万个任务。关系多了,错综复杂,千头万绪,往往出现“万事俱备,只欠东风”的情况。由于一两个零件没完成,耽误了一台复杂机器的出厂时间。或往往因为抓的不是关键,连夜三班,急急忙忙,完成这一环节之后,还得等待旁的环节才能装配。
洗茶壶,洗茶杯,拿茶叶,或先或后,关系不大,而且同是一个人的活儿,因而可以合并成为:
看来这是“小题大做”,但在工作环节太多的时候,这样做就非常必要了。
这里讲的主要是时间方面的事,但在具体生产实践中,还有其他方面的许多事。这种方法虽然不一定能直接解决所有问题,但是,我们利用这种方法来考虑问题,也是不无裨益的。
3.11.2. 实现
3.11.2.1. join方式实现
package com.bloom.concurrent.three;
import lombok.extern.slf4j.Slf4j;
/**
* @description:
* @author: teago
* @time: 2020/5/16 18:44
*/
@Slf4j
public class Example18 {
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
try {
log.debug("洗水壶");
Thread.sleep(5000);
log.debug("烧开水");
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "老王");
Thread thread2 = new Thread(() -> {
try {
log.debug("洗茶壶");
Thread.sleep(1000);
log.debug("拿茶叶");
thread1.join();
log.debug("泡茶");
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "小王");
thread1.start();
thread2.start();
}
}
运行结果:
18:45:26 [老王] com.bloom.concurrent.three.Example18 - 洗水壶
18:45:26 [小王] com.bloom.concurrent.three.Example18 - 洗茶壶
18:45:27 [小王] com.bloom.concurrent.three.Example18 - 拿茶叶
18:45:31 [老王] com.bloom.concurrent.three.Example18 - 烧开水
18:45:31 [小王] com.bloom.concurrent.three.Example18 - 泡茶
缺陷:(后续改进)
- 上面模拟的是小王等老王水烧开了,小王再去泡茶,如果反过来要实现老王等小王的茶叶拿来了,老王泡茶呢?代码最好能适应这两种情况。
- 上面的两个线程其实是各执行各自的,如果要模拟老王吧水壶交给小王泡茶呢,或模拟小王吧茶叶交给老王泡茶呢?
3.12. 本章小节
本章的重点在于掌握
- 线程创建
- 线程重要 api,如 start,run,sleep,join,interrupt 等
- 线程状态
- 应用方面
- 异步调用:主线程执行期间,其它线程异步执行耗时操作
- 提高效率:并行计算,缩短运算时间
- 同步等待:join
- 统筹规划:合理使用线程,得到最优效果
- 原理方面
- 线程运行流程:栈、栈帧、上下文切换、程序计数器
- Thread 两种创建方式 的源码
- 模式方面
- 终止模式之两阶段终止