目录
1、创建和运行线程
1.1、方法1——直接使用Thread
1.2、方法2——使用Runnable配合Thread
1.3、方法3——FutureTask配合Thread
2、原理之Thread与Runnable的关系
3、观察多个线程同时运行
4、查看进程线程的方法
4.1、Windows
4.2、Linux
4.3、Java
5、原理之线程运行
5.1、栈与栈帧
5.2、线程上下文切换(Thread Context Switch)
6、线程中的常见方法
6.1、start()与run()
6.2、sleep()与yield()
6.2.1、线程优先级
6.3、join()
6.3.1、为什么需要join()
6.3.2、有时效的join()
6.4、interrupt()
6.4.1、打断sleep(),wait(),join()的线程
6.4.2、打断正常运行的线程
6.4.3、模式之两阶段终止
6.4.4、打断park线程
6.5、不推荐的方法
6.6、主线程与守护线程
6.7、线程的五种状态
6.8、线程的六种状态
7、案例
7.1、案例1——防止CPU占用100%
7.1.1、sleep实现
7.1.2、wait实现
7.2、案例2——应用之同步
// 创建线程对象
Thread t = new Thread() {
public void run() {
// 要执行的任务
}
};
// 启动线程
t.start();
例如:
// 构造方法的参数是给线程指定名字,推荐
Thread t1 = new Thread("t1") {
public void run() {
log.debug("hello");
}
};
t1.start();
把【线程】和【任务】(要执行的代码)分开。
Runnable runnable = new Runnable() {
@Override
public void run() {
log.debug("hello");
}
};
// 参数1是任务对象,参数2是线程名字,推荐
Thread t2 = new Thread(runnable, "t2");
t2.start();
Java8以后可以使用lambda精简代码。
// 创建任务对象
Runnable task = () -> log.debug("hello");
// 参数1是任务对象,参数2是线程名字,推荐
Thread t3 = new Thread(task, "t3");
t3.start();
FutureTask能够接收Callable类型的参数,用来处理有返回结果的情况。
// 创建任务对象
FutureTask task3 = new FutureTask(new Callable() {
@Override
public Integer call() throws Exception {
log.debug("hello");
return 100;
}
});
// 参数1 是任务对象;参数2 是线程名字,推荐
new Thread(task3, "t3").start();
// 主线程调用get()方法导致自身阻塞,同步等待task执行完毕的结果
Integer result = task3.get();
log.debug("结果是:{}", result);
结果:
18:47:17.417 [t3] DEBUG com.bfbc.test.Test2 - hello
18:47:17.420 [main] DEBUG com.bfbc.test.Test2 - 结果是:100
new Thread(() -> {
while(true) {
log.debug("running");
}
}, "t1").start();
new Thread(() -> {
while(true) {
log.debug("running");
}
}, "t2").start();
jconsole远程监控配置:
Java Virtual Machine Stacks(Java虚拟机栈)。我们都知道JVM中由堆、栈、方法区所组成,其中栈内存是给谁使用的呢?其实就是线程,每个线程启动后,虚拟机就会为其分配一块栈内存。
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 o = new Object();
return o;
}
}
因为以下一些原因导致CPU不再执行当前的线程,转而执行另一个线程的代码。
当Context Switch发生时,需要由操作系统保存当前线程的状态,并恢复另一个线程的状态,Java中对应的概念就是程序计数器(Program Counter Register),它的作用是记住下一条JVM指令的执行地址,是线程私有的。
方法名 | static | 功能说明 | 注意 |
---|---|---|---|
start() | 启动一个新线程,在新的线程运行run()方法中的代码 | start()方法只是让线程进入就绪,里面代码不一定立刻运行(CPU的时间片还没分给它)。每个线程对象的start()方法只能调用一次,如果调用了多次会出现IllegalThreadStateException。 |
|
run() | 新线程启动后会调用的方法 | 如果在构造Thread对象时传递了Runnable参数,则 线程启动后会调用Runnable中的run()方法,否则默认不执行任何操作,但可以创建Thread的子类对象,来覆盖默认行为。 |
|
join() | 等待线程运行结束 | ||
join(long n) | 等待线程运行结束,最多等待n毫秒 | ||
getId() | 获取线程长整型的id | id唯一。 | |
getName() | 获取线程名 | ||
setName() | 修改线程名 | ||
getPriority() | 获取线程优先级 | ||
setPriority() | 修改线程优先级 | Java中规定线程优先级是1-10的整数,较大的优先级能提高该线程被CPU调度的几率。 |
|
getState() | 获取线程状态 | Java中线程状态使用6个enum表示,分别为:NEW,RUNNABLE,BLOCKED,WAITING ,TIMED_WAITING,TERMINATED |
|
isInterrupted() | 判断是否被打断 | 不会清除打断标记 | |
isAlive() | 线程是否存活(还没有运行完毕) | ||
interrupt() | 打断线程 | 如果打断线程正在sleep、wait、join会导致被打断的线程抛出InterruptException,并清除打断标记;如果打断的正在运行的线程,则会设置打断标记;park的线程被打断,也会设置打断标记。 | |
interrupted() | static | 判断当前线程是否被打断 | 会清除打断标记 |
currentThread() | static | 获取当前正在执行的线程 | |
sleep(long n) | static | 让当前执行的线程休眠n毫秒,休眠时间让出CPU的时间片给其他线程 | |
yield() | static | 提示线程调度器让出当前线程对CPU的使用 | 主要是为了测试和调试 |
public static void main(String[] args) {
Thread t1 = new Thread("t1") {
@Override
public void run() {
log.debug("running...");
}
};
t1.run();
}
结果:
19:24:07.580 [main] DEBUG com.bfbc.test.Test5 - running...
public static void main(String[] args) {
Thread t2 = new Thread() {
@Override
public void run() {
log.debug("running...");
}
};
System.out.println(t2.getState());
t2.start();
System.out.println(t2.getState());
}
结果:
NEW
RUNNABLE
19:24:49.309 [Thread-0] DEBUG com.bfbc.test.Test6 - running...
sleep:
yield:
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread() {
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
t1.start();
Thread.sleep(500);
log.debug("t1 state : {}", t1.getState());
}
结果:
19:32:06.399 [main] DEBUG com.bfbc.test.Test7 - t1 state : TIMED_WAITING
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread() {
@Override
public void run() {
log.debug("enter sleep...");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
log.debug("wake up...");
e.printStackTrace();
}
}
};
t1.start();
Thread.sleep(1000);
// TimeUnit.SECONDS.sleep(1); // 同Thread.sleep(1000)效果一样
log.debug("interrupt...");
t1.interrupt();
}
结果:
19:36:22.727 [Thread-0] DEBUG com.bfbc.test.Test8 - enter sleep...
19:36:23.725 [main] DEBUG com.bfbc.test.Test8 - interrupt...
19:36:23.725 [Thread-0] DEBUG com.bfbc.test.Test8 - wake up...
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at com.bfbc.test.Test8$1.run(Test8.java:13)
public static void main(String[] args) {
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.setPriority(Thread.MAX_PRIORITY);
t1.start();
t2.start();
}
private static int r = 0;
public static void main(String[] args) {
test1();
}
private static void test1() {
log.debug("开始");
Thread t1 = new Thread(() -> {
log.debug("开始");
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("结束");
r = 10;
});
t1.start();
log.debug("结果为:{}", r);
log.debug("结束");
}
结果:
10:22:00.870 [main] DEBUG com.bfbc.test.Test12 - 开始
10:22:00.928 [Thread-0] DEBUG com.bfbc.test.Test12 - 开始
10:22:00.928 [main] DEBUG com.bfbc.test.Test12 - 结果为:0
10:22:00.930 [main] DEBUG com.bfbc.test.Test12 - 结束
10:22:00.930 [Thread-0] DEBUG com.bfbc.test.Test12 - 结束
分析:
解决方法:用join(),加载t1.start()之后即可。
private static int r = 0;
public static void main(String[] args) throws InterruptedException {
test1();
}
private 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.start();
t1.join(); // 主线程等待t1运行结束
log.debug("结果为:{}", r);
log.debug("结束");
}
结果:
10:26:17.260 [main] DEBUG com.bfbc.test.Test12 - 开始
10:26:17.310 [Thread-0] DEBUG com.bfbc.test.Test12 - 开始
10:26:17.325 [Thread-0] DEBUG com.bfbc.test.Test12 - 结束
10:26:17.325 [main] DEBUG com.bfbc.test.Test12 - 结果为:10
10:26:17.325 [main] DEBUG com.bfbc.test.Test12 - 结束
private static void test2() {
Thread t1 = new Thread(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
r = 10;
});
t1.start();
Thread t2 = new Thread(() -> {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
r = 20;
});
t2.start();
try {
log.debug("join begin");
t1.join();
log.debug("t1 join end");
t2.join();
log.debug("t2 join end");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
结果:
10:37:12.509 [main] DEBUG com.bfbc.test.Test12 - join begin
10:37:13.510 [main] DEBUG com.bfbc.test.Test12 - t1 join end
10:37:14.510 [main] DEBUG com.bfbc.test.Test12 - t2 join end
private static int r = 0;
public static void main(String[] args) throws InterruptedException {
test1();
}
private static void test1() throws InterruptedException {
Thread t1 = new Thread(()-> {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
r = 10;
});
t1.start();
//线程执行结束会导致join()结束
log.debug("join begin");
t1.join(1500);
log.debug("{}", r);
}
结果:
10:43:50.235 [main] DEBUG com.bfbc.test2.Test1 - join begin
10:43:51.739 [main] DEBUG com.bfbc.test2.Test1 - 0
打断sleep()的线程,会清空打断状态(打断标记为false),以sleep()为例。
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
log.debug("sleep...");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "t1");
t1.start();
Thread.sleep(1000);
log.debug("interrupt...");
t1.interrupt();
log.debug("打断标记:{}", t1.isInterrupted());
}
结果:
10:50:40.342 [t1] DEBUG com.bfbc.test2.Test2 - sleep...
10:50:41.354 [main] DEBUG com.bfbc.test2.Test2 - interrupt...
10:50:41.354 [main] DEBUG com.bfbc.test2.Test2 - 打断标记:false
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at com.bfbc.test2.Test2.lambda$main$0(Test2.java:11)
at java.lang.Thread.run(Thread.java:748)
打断正常运行的线程,不会清空打断状态(打断标记仍为true)。
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
while(!Thread.currentThread().isInterrupted()) {
log.debug("running...");
}
log.debug("被打断了,退出循环...");
}, "t1");
t1.start();
Thread.sleep(1000);
log.debug("interrupt...");
t1.interrupt();
log.debug("打断标记:{}", t1.isInterrupted());
}
结果:
...
10:57:34.985 [t1] DEBUG com.bfbc.test2.Test2 - running...
10:57:34.985 [t1] DEBUG com.bfbc.test2.Test2 - running...
10:57:34.985 [t1] DEBUG com.bfbc.test2.Test2 - running...
10:57:34.993 [main] DEBUG com.bfbc.test2.Test2 - interrupt...
10:57:34.994 [main] DEBUG com.bfbc.test2.Test2 - 打断标记:true
10:57:34.996 [t1] DEBUG com.bfbc.test2.Test2 - 被打断了,退出循环...
两阶段终止模式(Two Phase Termination):在一个线程T1中如何“优雅”地终止线程T2?这里的“优雅”指的是给T2一个料理后事的机会。
错误思路:
正确思路:
package com.bfbc.test2;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class Test3 {
public static void main(String[] args) throws InterruptedException {
TwoPhaseTermination twoPhaseTermination = new TwoPhaseTermination();
twoPhaseTermination.start();
Thread.sleep(3500);
twoPhaseTermination.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();
}
}
结果:
11:24:13.268 [Thread-0] DEBUG com.bfbc.test2.TwoPhaseTermination - 执行监控记录...
11:24:14.269 [Thread-0] DEBUG com.bfbc.test2.TwoPhaseTermination - 执行监控记录...
11:24:15.269 [Thread-0] DEBUG com.bfbc.test2.TwoPhaseTermination - 执行监控记录...
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at com.bfbc.test2.TwoPhaseTermination.lambda$start$0(Test3.java:30)
at java.lang.Thread.run(Thread.java:748)
11:24:15.770 [Thread-0] DEBUG com.bfbc.test2.TwoPhaseTermination - 料理后事...
打断park线程,不会清空打断状态(打断标记仍为true)。
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
log.debug("park...");
LockSupport.park(); // 让当前线程停下来
log.debug("unpark...");
log.debug("打断状态:{}", Thread.interrupted());
LockSupport.park();
log.debug("unpark");
}, "t1");
t1.start();
Thread.sleep(1000);
t1.interrupt(); // t1停下来之后,打断t1,t1继续执行后面的代码
Thread.sleep(1000);
t1.interrupt(); // t1停下来之后,打断t1,t1继续执行后面的代码
}
结果:
11:36:55.081 [t1] DEBUG com.bfbc.test2.Test4 - park...
11:36:56.096 [t1] DEBUG com.bfbc.test2.Test4 - unpark...
11:36:56.096 [t1] DEBUG com.bfbc.test2.Test4 - 打断状态:true
11:36:57.115 [t1] DEBUG com.bfbc.test2.Test4 - unpark
这些方法已经过时,容易破坏同步代码块,造成线程死锁。
方法名 | static | 功能说明 |
---|---|---|
stop() | 停止线程运行 | |
suspend() | 挂起(暂停)线程运行 | |
resume() | 恢复线程运行 |
默认情况下,Java进程需要等待所有线程都运行结束,才会结束。有一种特殊的线程叫做守护线程,只要其它非守护线程运行结束了,即使守护线程的代码没有执行完,也会强制结束。
public static void main(String[] args) throws InterruptedException {
log.debug("开始运行...");
Thread t1 = new Thread(()->{
log.debug("开始运行...");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("运行结束");
}, "daemon");
t1.setDaemon(true);
t1.start();
Thread.sleep(1000);
log.debug("运行结束...");
}
结果:
13:46:15.764 [main] DEBUG com.bfbc.test2.Test5 - 开始运行...
13:46:15.815 [daemon] DEBUG com.bfbc.test2.Test5 - 开始运行...
13:46:16.828 [main] DEBUG com.bfbc.test2.Test5 - 运行结束...
注意:
这是从操作系统层面来描述的。
这是从Java API层面来描述的。根据Thread.State枚举,分为六种状态:
@Slf4j
public class Test6 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
log.debug("running...");
}, "t1");
Thread t2 = new Thread(() -> {
while (true) {
// 运行 RUNNABLE 状态
}
}, "t2");
t2.start();
Thread t3 = new Thread(() -> {
log.debug("running...");
}, "t3");
t3.start();
Thread t4 = new Thread(() -> {
synchronized (Test6.class) {
try {
Thread.sleep(1000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "t4");
t4.start();
Thread t5 = new Thread(()->{
try {
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"t5");
t5.start();
Thread t6 = new Thread(()->{
synchronized (Test6.class) { // BLOCKED
try {
Thread.sleep(1000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"t6");
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());
}
}
结果:
14:20:37.523 [t3] DEBUG com.bfbc.test2.Test6 - running...
14:20:38.025 [main] DEBUG com.bfbc.test2.Test6 - t1 state NEW
14:20:38.026 [main] DEBUG com.bfbc.test2.Test6 - t2 state RUNNABLE
14:20:38.026 [main] DEBUG com.bfbc.test2.Test6 - t3 state TERMINATED
14:20:38.026 [main] DEBUG com.bfbc.test2.Test6 - t4 state TIMED_WAITING
14:20:38.026 [main] DEBUG com.bfbc.test2.Test6 - t5 state WAITING
14:20:38.026 [main] DEBUG com.bfbc.test2.Test6 - t6 state BLOCKED
在没有利用CPU来讲计算时,不要让while(true)空转浪费CPU,这时可以使用yield或sleep来让出CPU的使用权给其他程序。
public static void main(String[] args) {
Thread t1 = new Thread() {
@Override
public void run() {
while (true) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
t1.start();
}
以调用方角度来讲,如果: