文章目录
章节内容
1、创建和运行线程
2、查看线程
3、线程API
4、线程状态
3.1 创建和运行线程
1、方法一:直接使用Thread
// 创建线程对象
Thread t = new Thread() {
public void run() {
// 要执行的任务
}
}
// 启动线程
t.start();
例如
// 构建方法的参数是给线程指定名字,推荐
Thread t1 = new Thread("t1") {
@Override
// run方法内实现了要执行的任务
public void run() {
log.debug("hello");
}
};
t1.start();
输出
19:19:00 [t1] c.ThreadStarter - hello
2、方法二:使用Runnable配合Thread
把【线程】和【任务】(要执行的代码)分开
1、Thread代表线程
2、Runnable可运行的任务(线程要执行的代码)
Runnable runnable = new Runnable(){
public void run(){
// 要执行的任务
}
}
// 创建线程对象
Thread t = new Thread( runnable );
// 启动线程
t.start();
例如
// 创建任务对象
Runnable task2 = new Runnable() {
@Override
public void run() {
log.debug("hello");
}
};
// 参数1是任务对象;参数2线程名字,推荐
Thread t2 = new Thread(task2,"t2");
输出
19:19:00 [t2] c.ThreadStarter - hello
Java8以后可以使用lambda精简代码
// 创建任务对象
Runnable task2 = () -> log.debug("hello");
// 参数1是任务对象;参数2是线程名字,推荐
Thread t2 = new Thread(task2,"t2");
t2.start();
ps:
1、创建线程的时候可以给线程设置名称,在生产出现问题,可以通过线程的名称很好的定位到代码;
2、t2.start();方法调用后线程就处于就绪状态,当获取到cpu资源执行的时候才是运行状态;
3、原理之Thread与Runnable的关系(重要)
分析 Thread 的源码,理清它与 Runnable 的关系
// 1、以下run方法是使用Runnable接口创建多线程时候源码中run方法部分;
// 2、查看源码后发现,实现Runnable接口最终是调用以下run的方法体,target表示的Runnable的方法体对象;而直接继承Thread类是重写了run方法。
@Override
public void run() {
if (target != null) {
target.run();
}
}
小结:
1、方法1 是把线程和任务合并在了一起,方法2 是把线程和任务分开了;
2、用 Runnable 更容易与线程池等高级 API 配合;
3、用 Runnable 让任务类脱离了 Thread 继承体系,更灵活
4、方法三:FutureTask配合Thread
FutureTask能够接收Callable类型的参数,用来处理有返回结果的情况
// 创建任务对象
FutureTask<Integer> task3 = new FutureTask<>(() -> {
log.debug("hello");
return 100;
})
// 参数1是任务对象;参数2是线程名字,推荐
new Thread(task3,"t3").start();
// 主线程阻塞,同步等待task执行完毕的结果
Integer result = task3.get();
log.debug("结果是:{}",result);
输出
19:22:27 [t3] c.ThreadStarter - hello
19:22:27 [main] c.ThreadStarter - 结果是:100
3.2 观察多个线程同时运行
主要是理解:
1、交替执行
2、谁先谁后,不由我们控制
代码:
@Slf4j(topic = "c.multi")
public class TestMultiThread {
public static void main(String[] args) {
new Thread(() -> {
while (true) {
log.debug("running");
}
},"t1").start();
new Thread(() -> {
while (true) {
log.debug("running");
}
},"t2").start();
}
}
结果:
ps:如果电脑是单核cpu的话,建议不用尝试以上代码。以上代码两个线程都是持续占用cpu,如果是单核的话,内存资源基本都被分配到这两个线程上面,其他的任务就会分配不到cpu的资源。(可能会导致cpu占用率为100%)
3.3 查看进程线程的方法
1、Windows
1.1、任务管理器可以查看进程和线程数,也可以用来杀死进程;
1.2、tasklist 查看进程;eg: tasklist | findstr java 查看关于java的进程
1.3、taskkill 杀死进程;
eg: taskkill /F /PID 1233 强制杀死pid为1233的进程
2、linux
2.1、ps -ef 查看所有进程
2.2、ps -fT -p <PID·> 查看某个进程(PID)的所有线程
2.3、kill 杀死进程
2.4、top 按大写 H 切换是否显示线程
2.5、top -H -p <PID·> 查看某个进程(PID)的所有线程
3、java
3.1、jps 命令查看所有 Java 进程
3.2、jstack <PID.> 查看某个 Java 进程(PID)的所有线程状态
3.3、jconsole 来查看某个 Java 进程中线程的运行情况(图形界面)
4、jconsole 远程监控配置
4.1、需要以如下方式运行你的 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类
4.2、修改 /etc/hosts 文件将 127.0.0.1 映射至主机名
ps:如果要认证访问,还需要做如下步骤
4.3、复制 jmxremote.password 文件
4.4、修改 jmxremote.password 和 jmxremote.access 文件的权限为 600 即文件所有者可读写
4.5、连接时填入 controlRole(用户名),R&D(密码)
3.4 原理之线程运行(重要)
1、栈与栈帧
1、Java Virtual Machine Stacks(Java虚拟机栈);
2、我们都知道JVM中由堆、栈、方法区所组成,其中栈内存是给谁用呢?其实就是线程,每个线程启动后,虚拟机就会为其分配一块栈内存。
2.1、每个栈由多个栈帧(Frame)组成,对应着每次方法调用时所占用的内存;
2.2、每个线程只能有一个活动帧,对应着当前正在执行的那个方法。
ps:方法使用完毕,栈帧的内存就会释放,不像堆中的内存还需要进行垃圾回收。
2、栈帧图解
1、运行一个类(TestFrames),首先会进行类加载,将TestFrames的字节码加载到JVM中,也就是放到方法区中。(便于理解所以下面方法区里面写的是代码,而不是字节码)
2、类加载完成后,就会启动main的主线程,随后获取一个main的线程栈,并通过调度器来获取cpu资源;随后执行一个方法,就会产生一个栈帧到栈中;
3、程序计数器是存放当前线程执行的位置;
4、在执行方法过程中new出来的对象都存放在堆中;
5、在方法执行完后就会自动从栈中退出。
ps:JVM中方法区、堆是线程共享的;虚拟机栈、本地方法栈、程序计数器是私有的;
3、线程上下文切换(Thread Context Switch)
因为以下一些原因导致cpu不再执行当前的线程,转而执行另一个线程的代码。
1、线程的cpu时间片用完;
2、垃圾回收;(ps:暂停所有工作线程,让垃圾回收线程进行垃圾回收)
3、有更高优先级的线程需要运行;
4、线程自己调用了sleep、yield、wait、join、park、synchronized、lock等方法;
当Context Switch发生时,需要由操作系统保存当前线程的状态,并恢复另一个线程的状态,Java中对应的概念就是程序计数器(Program Counter Register),它的作用是记住下一条jvm指令的执行地址,是线程私有的。
1、状态包括程序计数器、虚拟机栈中每个栈帧的信息,如局部变量、操作数栈、返回地址等;
2、Context Switch频繁发生会影响性能。
图解:main线程让出cpu资源,保存其中的状态后,cpu资源给t1线程

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.6 start与run
1、调用run
public static void main(String[] args) {
Thread t1 = new Thread("t1") {
@Override
public void run() {
log.debug(Thread.currentThread().getName());
FileReader.read(Constants.MP4_FULL_PATH);
}
};
t1.run();
log.debug("do other things ...");
}
输出
19:39:14 [main] c.TestStart - main
19:39:14 [main] c.FileReader - read [1.mp4] start ...
19:39:18 [main] c.FileReader - read [1.mp4] end ... cost: 4227 ms
19:39:18 [main] c.TestStart - do other things ...
程序仍在 main 线程运行, FileReader.read() 方法调用还是同步的
2、调用start
将上述代码t1.run()改为t1.start()
public static void main(String[] args) {
Thread t1 = new Thread("t1") {
@Override
public void run() {
log.debug(Thread.currentThread().getName());
FileReader.read(Constants.MP4_FULL_PATH);
}
};
t1.start();
log.debug("do other things ...");
}
输出
19:41:30 [main] c.TestStart - do other things ...
19:41:30 [t1] c.TestStart - t1
19:41:30 [t1] c.FileReader - read [1.mp4] start ...
19:41:35 [t1] c.FileReader - read [1.mp4] end ... cost: 4542 ms
程序在 t1 线程运行, FileReader.read() 方法调用是异步的
小结:
1、直接调用 run 是在主线程中执行了 run,没有启动新的线程;
2、使用 start 是启动新的线程,通过新的线程间接执行 run 中的代码
3.7 sleep与yield
1、sleep
1、调用 sleep 会让当前线程从 Running 进入 Timed Waiting 状态(阻塞)
2、其它线程可以使用 interrupt 方法打断正在睡眠的线程,这时 sleep 方法会抛出 InterruptedException
3、睡眠结束后的线程未必会立刻得到执行
4、建议用 TimeUnit 的 sleep 代替 Thread 的 sleep 来获得更好的可读性
2、yield
1、调用 yield 会让当前线程从 Running 进入 Runnable 就绪状态,然后调度执行其它线程
2、具体的实现依赖于操作系统的任务调度器(让出cpu后,如果没有其他线程使用,调度器还是会把cpu给到此线程)
sleep和yield区别:
1、调度器不会将cpu给予timed Waiting状态的线程,但是会给runnable状态的线程
3、线程优先级
1、线程优先级会提示(hint)调度器优先调度该线程,但它仅仅是一个提示,调度器可以忽略它;
2、如果cpu比较忙,那么优先级高的线程会获得更多的时间片,但cpu闲时,优先级几乎没作用。
Runnable task1 = () -> {
int count = 0;
for(;;) {
System.out.println("------->1" + count++);
}
};
Runnable task2 = () -> {
int count = 0;
for(;;) {
// Thread.yield();
System.out.println(" ----->2" + count++);
}
};
Thread t1 = new Thread(task1,"t1");
Thread t2 = new Thread(task2,"t2");
// t1.setPriority(Thread.MIN_PRIORITY);
// t2.setPriortty(Thread.MAX_PRIORITY);
t1.start();
t2.start();
4、案例
限制对CPU的使用(sleep实现)
在没有利用cpu来计算时,不要让while(ture)空转浪费cpu,这时可以使用yield或sleep来让出cpu的使用权给其它程序。
不使用sleep方法及cpu占用率情况
while(true) {
try {
//Thread.sleep(50);
}catch(InterruptedException e) {
e.printStackTrace();
}
}

使用sleep方法及cpu占用率情况
while(true) {
try {
Thread.sleep(50);
}catch(InterruptedException e) {
e.printStackTrace();
}
}

1、可以用wait或条件变量达到类似的效果
2、不同的是,后两种都需要加锁,并且需要相应的唤醒操作,一般适用于要进行同步的场景
3、sleep适用于无需锁同步的场景。
3.8 join方法详解
1、为什么需要join
下面的代码执行,打印r是什么?
static int r = 0;
public static void main(String[] args) throws InterruptedException {
test1();
}
public static void test1() throws InterruptedException {
log.debug("开始");
Thread t1 = new Thread(() -> {
log.debug("开始");
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("结束");
r = 10;
},"t1");
t1.start();
log.debug("结果为:{}",r);
log.debug("结束");
}

分析:
1、因为主线程和线程1是并行执行的,t1线程需要1秒之后才能算出r=10
2、而主线程一开始就是打印r的结果,所以只能打印出r=0
解决方法
1、用sleep行不行?为什么?(不行,因为时间不固定,不能确定t1线程是1s结束还是1min结束还是更长时间)
2、用join,加在t1.start()之后即可。(join方法等待调用线程运行结束后在执行后续代码。)
效果如下
static int r = 0;
public static void main(String[] args) throws InterruptedException {
test1();
}
public static void test1() throws InterruptedException {
log.debug("开始");
Thread t1 = new Thread(() -> {
log.debug("开始");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("结束");
r = 10;
},"t1");
t1.start();
t1.join();
log.debug("结果为:{}",r);
log.debug("结束");
}

2、应用之同步(案例1)
以调用方式角度来讲,如果
1、需要等待结果返回,才能继续运行就是同步;
2、不需要等待结果返回,就能继续运行就是同步;

3、等待多个结果
问,下面代码cost大约多少秒?
static int r1 = 0;
static int r2 = 0;
public static void main(String[] args) throws InterruptedException {
test1();
}
public static void test1() throws InterruptedException {
//log.debug("开始");
Thread t1 = new Thread(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
r1 = 10;
},"t1");
Thread t2 = new Thread(() -> {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
r2 = 20;
},"t2");
t1.start();
t2.start();
long start = System.currentTimeMillis();
log.debug("join begin");
t1.join();
log.debug("t1 join end");
t2.join();
log.debug("t2 join end");
long end = System.currentTimeMillis();
log.debug("r1:{} r2:{} cost:{}" ,r1,r2,end - start);
}

分析如下:
1、第一个join:等待t1时,t2并没有停止,而在运行;
2、第二个join:1s后,执行到此,t2也运行了1s,因此也只需再等待1s。
如果颠倒两个join呢?
最终都是输出
20:45:43.239 [main] c.TestJoin - r1: 10 r2: 20 cost: 2005

4、有时效的join
4.1等够时间
static int r = 0;
public static void main(String[] args) throws InterruptedException {
test2();
}
public static void test2() throws InterruptedException {
Thread t1 = new Thread(() -> {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
r = 10;
},"t1");
t1.start();
log.debug("join debug");
t1.join(3000); // 线程执行结束会导致 join 结束
log.debug("结果为:{}",r);
}
线程执行完就不用继续等待,所以2s后就继续执行后续代码

4.2没等够时间
static int r = 0;
public static void main(String[] args) throws InterruptedException {
test1();
}
public static void test1() throws InterruptedException {
Thread t1 = new Thread(() -> {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
r = 10;
},"t1");
t1.start();
log.debug("join debug");
t1.join(1000); // 线程执行结束会导致 join 结束
log.debug("结果为:{}",r);
}
等了1s,就不再继续等待,并执行后续代码

ps:如果join中传入参数时间大于线程执行时间,则按照线程实际执行完成时间执行后续代码;如果join传入参数小于线程执行时间,则按照join设定时间完成后执行后续代码。
3.9 interrupt方法详解
主要分为阻塞状态线程打断和正常状态线程打断
1、打断sleep、wait、join的线程(阻塞状态线程)
1、这几个方法都会让线程进入阻塞状态
2、打断sleep的线程,会清空打断状态,以sleep为例。
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
log.debug("sleep----");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
},"t1");
t1.start();
Thread.sleep(1000); // 保证t1线程进入阻塞状态(sleep)
t1.interrupt();
Thread.sleep(1000); // 保证t1线程清除打断标志
log.debug("打断标记:{}",t1.isInterrupted());
}
输出

2、打断正常运行的线程
打断正常运行的线程,不会清空打断状态
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
while (true) {
boolean interrupted = Thread.currentThread().isInterrupted();
if (interrupted) {
log.debug("被打断了,退出循环。。。");
break;
}
}
},"t1");
t1.start();
t1.interrupt();
log.debug("打断状态:{}" , t1.isInterrupted());
}
输出

interrupt方法的作用:如阻断正常的线程,可以通过打断状态优雅的让线程进行终止
*3、模式之两阶段终止
Two Phase Termination
在一个线程T1中如何“优雅”终止线程T2?这里的【优雅】指的是给T2一个料理后事的机会。
3.1 错误思路
1、使用线程对象的stop()方法停止线程
stop方法灰真正杀死线程,如果这时线程锁住了共享资源,那么当它被杀死后就再也没有机会释放锁,其他线程灰永远无法获取锁。
2、使用System.exit(int)方法停止线程
目的仅是停止一个线程,但这种做法会让整个程序都停止。
3.2 两阶段终止模式

3.2.1 利用isInterrupted
interrupt可以打断正在执行的线程,无论这个线程是在sleep、wait,还是正常运行。
@Slf4j
public class TestInterrupt2 {
public static void main(String[] args) throws InterruptedException {
TwoPhaseTermination termination = new TwoPhaseTermination();
termination.start();
Thread.sleep(5000);
termination.stop();
}
}
@Slf4j
class TwoPhaseTermination {
private Thread monitor;
// 启动监控线程
public void start() {
monitor = new Thread(() -> {
while (true) {
Thread thread = Thread.currentThread();
if (thread.isInterrupted()) {
log.debug("料理后事");
break;
}
try {
Thread.sleep(1000); //情况1
log.debug("执行监控记录"); //情况2
} catch (InterruptedException e) {
e.printStackTrace();
// 重新设置打断标记
thread.interrupt();
}
}
});
monitor.start();
}
// 停止监控线程
public void stop() {
monitor.interrupt();
}
}
输出

3.3打断park线程
1、打断park线程,不会清空打断状态
public class TestPark {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
log.debug("park...");
LockSupport.park();
log.debug("unpark...");
log.debug("打断状态:{}", Thread.currentThread().isInterrupted());
},"t1");
t1.start();
Thread.sleep(1000);
t1.interrupt();
}
}
输出

2、如果打断标记已经是true,则park会失效
public class TestPark2 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
log.debug("park...");
LockSupport.park();
log.debug("打断标记:{}", Thread.currentThread().isInterrupted());
//log.debug("打断标记:{}", Thread.currentThread().interrupted());
}
}, "t1");
t1.start();
Thread.sleep(1000);
t1.interrupt();
}
}
输出

提示:使用Thread.interrupted()清除打断状态
public class TestPark2 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
log.debug("park...");
LockSupport.park();
//log.debug("打断标记:{}", Thread.currentThread().isInterrupted());
log.debug("打断标记:{}", Thread.currentThread().interrupted());
}
}, "t1");
t1.start();
Thread.sleep(1000);
t1.interrupt();
}
}
输出:在第二次循环的时候卡在了park中,说明状态修改成功。

3.10 不推荐的方法
还有一些不推荐使用的方法,这些方法已过时,容易破坏同步代码块,造成线程死锁。
| 方法名 | static | 功能说明 |
|---|---|---|
| stop() | 停止线程运行 | |
| suspend() | 挂起(暂停)线程运行 | |
| resume() | 恢复线程运行 |
3.11 主线程与守护线程
默认情况下,Java进程需要等待所有进程都运行结束,才会结束。有一种特殊的线程叫做守护线程,只要其它非守护线程运行结束了,即使守护线程的代码没有执行完,也会强制结束。
public class TestDaemon {
public static void main(String[] args) throws InterruptedException {
log.debug("主线程运行开始");
Thread t1 = new Thread(() -> {
log.debug("t1运行开始");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("t1运行结束");
}, "t1");
t1.setDaemon(true); // 设置为true
t1.start();
Thread.sleep(1000);
log.debug("主线程运行结束");
}
}
输出如下:
设置t1线程为守护线程,当主线程执行完毕,t1线程也立刻退出,没有执行到“t1运行结束”语句。

注意:
1、垃圾回收器线程就是一种守护线程
2、Tomcat中的Acceptor和Poller线程都是守护线程,所以Tomcat接收到shutdown命令后,不会等待它们处理完当前请求。
3.12 五种状态
这里从操作系统层面来描述

【初始状态】:仅是在语言层面创建了线程对象,还未与操作系统线程关联;
【可运行状态】(就绪状态):指该线程已经被创建(与操作系统线程关联),可以由CPU调度执行。
【运行状态】:指获取了CPU时间片运行中的状态当CPU时间片用完,会从【运行状态】转换至【可运行状态】,会导致线程的上下文切换。
【阻塞状态】:
1、如果调用了阻塞API,如BIO读写文件,这时该线程实际不会用到CPU,会导致线程上下文切换,进入【阻塞状态】
2、等待BIO操作完毕,会由操作系统唤醒阻塞的线程,转换至【可运行状态】
3、与【可运行状态】的区别是,对【阻塞状态】的线程来说只要它们一直不唤醒,调度器就一直不会考虑调度它们【终止状态】:表示线程已经执行完毕,生命周期已经结束,不会再转换为其它状态
3.13 六种状态
这里是从Java API层面来描述的
根据Thread.State枚举,分为六种状态

1、new线程刚被创建,但是还没有调用start()方法;
2、RUNNABLE当调用了start()方法之后,注意,Java API层面的RUNNABLE状态涵盖了操作系统层面的【可运行状态】、【运行状态】和【阻塞状态】(由于BIO导致的线程阻塞,在Java里无法区分,仍然认为是可运行);
3、BLOCKED、WAITING、TIMED_WAITING都是Java API层面对【阻塞状态】的戏分,后面会在状态转换一节详述;
4、TERMINATED当线程代码运行结束。
演示状态代码如下:
@Slf4j(topic = "c.TestState")
public class TestState {
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 (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());
System.in.read();
}
}
输出:

3.14 习题



解法1:join
Thread t1 = new Thread(() -> {
log.debug("洗水壶");
sleep(1);
log.debug("烧开水");
sleep(15);
}, "老王");
Thread t2 = new Thread(() -> {
log.debug("洗茶壶");
sleep(1);
log.debug("洗茶杯");
sleep(2);
log.debug("拿茶叶");
sleep(1);
try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("泡茶");
}, "小王");
t1.start();
t2.start();
输出

解法1的缺陷:
1、上面模拟的是小王等老王的水烧开了,小王泡茶,如果反过来要实现老王等小王的茶叶拿来了,老王泡茶呢?代码最好能适应两种情况。
2、上面的两个线程其实是各执行各的,如果要模拟老王把水壶交给小王泡茶,或模拟小王把茶叶交给老王泡茶呢?
本章小结
本章重点在于掌握
1、线程创建
2、线程重要api,如start,run,sleep,join,interrupt等
3、线程状态
4、应用方面4.1 一步调用:主线程执行期间,其它线程异步执行耗时操作
4.2 提高效率:并行计算,缩短运算时间
4.3 同步等待:join
4.4 统筹规划:合理使用线程,得到最优效果5、原理方面
线程运行流程:栈、栈帧、上下文切换、程序计数器
Thread两种创建方式的源码6、模式方面
终止模式之两阶段终止



1万+

被折叠的 条评论
为什么被折叠?



