pom.xml依赖如下:
<dependencies>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>1.18.10version>
dependency>
<dependency>
<groupId>ch.qos.logbackgroupId>
<artifactId>logback-classicartifactId>
<version>1.2.7version>
dependency>
dependencies>
@Slf4j(topic = "c.Test1")
public class Test1 {
public static void main(String[] args) {
Thread t = new Thread() {
@Override
public void run() {
log.debug("running");
}
};
t.setName("t1");
t.start();
log.debug("running");
}
}
输出:
21:36:28.304 [main] DEBUG c.Test1 - running
21:36:28.304 [t1] DEBUG c.Test1 - running
把【线程】和【任务】(要执行的代码)分开
@Slf4j(topic = "c.Test2")
public class Test2 {
public static void main(String[] args) {
Runnable r = new Runnable() {
@Override
public void run() {
log.debug("running");
}
};
Thread t = new Thread(r, "t2");
t.start();
}
}
使用lambda表达式简化:
@Slf4j(topic = "c.Test2")
public class Test2 {
public static void main(String[] args) {
Runnable r = () -> log.debug("running");
Thread t = new Thread(r, "t2");
t.start();
}
}
再简化:
@Slf4j(topic = "c.Test2")
public class Test2 {
public static void main(String[] args) {
Thread t = new Thread(() -> log.debug("running"), "t2");
t.start();
}
}
Thread
实现了Runnable
class Thread implements Runnable {...}
所以,在Thread
内要实现Runnable
接口内的方法run()
。可以看到,当target不为空时,在run()
方法中又调用target.run()
。
@Override
public void run() {
if (target != null) {
target.run();
}
}
所以接下来要看看target
是什么,在初始化方法init()
中。
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
...
this.target = target;
...
}
所以,target
就是一个实现Runnable
接口所创建的对象。
所以,如果创建了一个Runnable
对象给Thread,那么Thread
运行的就是一个Runnable
对象中的run
方法;如果没创建Runnable
对象,就需要重写run
方法,Thread
将运行这个重写的run
方法。
方法1是把线程和任务合并在了一起,方法2是把线程和任务分开了,具有如下优点:
@Slf4j(topic = "c.Test3")
public class Test3 {
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("running...");
Thread.sleep(1000);
log.debug("running...");
return 100;
}
});
Thread t = new Thread(task, "t1");
t.start();
// 阻塞
log.debug("{}", task.get());
log.debug("over");
}
}
只有当log.debug("{}", task.get());
运行完才会执行后面的语句,主线程将阻塞在这里。
@Slf4j(topic = "c.TestMultiThread")
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();
}
}
tasklist
taskkill
tasklist | findstr 进程包含的字符串
taskkill /F /PID 进程号
ps -ef
ps -ef | grep java
jps
kill 进程号
top -H -p 进程号
jstack 进程号
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;
}
}
在method1(10)
位置打上断点,debug调试程序:
详细过程:https://www.bilibili.com/video/BV16J411h7Rd?p=21
public class TestFrames {
public static void main(String[] args) {
Thread t1 = new Thread(){
@Override
public void run() {
method1(20);
}
};
t1.setName("t1");
t1.start();
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;
}
}
在method1处打上端点。
断点模式选择Thread
debug进行调试,可以选择不同的栈帧进行调试:
线程上下文切换(Thread Context Switch)
因为以下一些原因导致cpu不再执行当前的线程,转而执行另一个线程的代码
当Context Switch发生时,需要由操作系统保存当前线程的状态,并恢复另一个线程的状态,Java 中对应的概念就是程序计数器(Program Counter Register),它的作用是记住下一条jvm指令的执行地址, 是线程私有的
方法名 | 功能说明 | 注意 |
---|---|---|
start() | 启动一个新线程,在新的线程运行run方法中的代码 | start方法只是让线程进入就绪,里面代码不一定立刻运行(CPU的时间片还没分给它)。每个线程对象的start方法只能调用一次,如果调用了多次会出现IllegalThreadStateException |
run() | 新线程启动后会调用的方法 | 如果在构造Thread对象时传递了Runnable 参数,则线程启动后会调用Runnable中的run方法,否则默认不执行任何操作。但可以创建Thread的子类对象,来覆盖默认行为 |
join() | 等待线程运行结束 | |
join(long n) | 等待线程运行结束,最多等待n毫秒 | n毫秒内线程还没有结束就不等了 |
getId() | 获取线程长整型的id | id唯一 |
getName() | 获取线程名 | |
setName(String) | 修改线程名 | |
getPriority() | 获取线程优先级 | |
setPriority(int) | 修改线程优先级 | java中规定线程优先级是1~10的整数,较大的优先级能提高该线程被CPU调度的机率 |
getState | 获取线程状态 | Java中线程状态是用6个enum表示,分别为: NEW,RUNNABLE, BLOCKED, WAITING, TIMED _WAITNG,TERMINATED |
isInterrupted() | 判断当前线程是否被打断 | 不会清除打断标记 |
isAlive() | 线程是否存活(还没有运行完毕) | |
interrupt() | 打断线程 | 如果被打断线程正在sleep,wait, join 会导致被打断的线程抛出InterruptedException,并清除打断标记 ;如果打断的正在运行的线程,则会设置打断标记 ;park 的线程被打断,也会设置打断标记 |
currentThead() | 获取当前正在执行的线程 | |
sleep(long n) | 让当前执行的线程休眠n毫秒,休眠时让出cpu的时间片给其它线程. | |
yield() | 提示线程调度器让出当前线程对CPU的使用. | 主要为了测试和调试 |
直接调用run()方法,就仅仅是调用了run()方法,没有启动线程,并不会提高效率。
@Slf4j(topic = "c.Test4")
public class Test4 {
public static void main(String[] args) {
Thread t1 = new Thread("t1") {
@Override
public void run() {
log.debug("running...");
}
};
System.out.println(t1.getState());
t1.run(); // 仅仅调用了run()方法,没有启动线程
System.out.println(t1.getState());
}
}
运行结果
NEW
20:34:52.813 [main] DEBUG c.Test4 - running...
NEW
使用start()开启线程:
@Slf4j(topic = "c.Test4")
public class Test4 {
public static void main(String[] args) {
Thread t1 = new Thread("t1") {
@Override
public void run() {
log.debug("running...");
}
};
System.out.println(t1.getState());
t1.start();
System.out.println(t1.getState());
}
}
运行结果:
NEW
RUNNABLE
20:35:47.879 [t1] DEBUG c.Test4 - running...
测试1:调用sleep会让当前线程从Running进入Timed Waiting状态
@Slf4j(topic = "test6")
public class Test6 {
public static void main(String[] args) {
Thread t1 = new Thread("t1") {
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
t1.start();
log.debug("t1 state: {}", t1.getState());
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("t1 state: {}", t1.getState());
}
}
运行结果:
20:41:03.324 [main] DEBUG test6 - t1 state: RUNNABLE
20:41:03.827 [main] DEBUG test6 - t1 state: TIMED_WAITING
解释:
程序启动时,主线程先运行,此时t1线程还未进入睡眠状态,当主线程等待了500ms后,t1线程此时已经进入睡眠状态,此时主线程获取的t1线程状态为睡眠状态。
测试2:其它线程可以使用interrupt 方法打断正在睡眠的线程,这时sleep方法会抛出InterruptedException
@Slf4j(topic = "test7")
public class Test7 {
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) {
log.debug("wake up");
e.printStackTrace();
}
}
};
t1.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("interrupt...");
t1.interrupt();
}
}
t1线程启动后,进入睡眠状态持续2000ms。主线程睡眠1000ms后打断t1线程。
运行结果:
20:47:50.398 [t1] DEBUG test7 - enter sleep...
20:47:51.397 [main] DEBUG test7 - interrupt...
20:47:51.397 [t1] DEBUG test7 - wake up
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at Chapter3.Test7$1.run(Test7.java:23)
测试4:建议用TimeUnit的sleep代替Thread的sleep来获得更好的可读性
@Slf4j(topic = "test8")
public class Test8 {
public static void main(String[] args) {
log.debug("enter");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("end");
}
}
睡眠前后各打印一次,输出结果:
20:52:46.795 [main] DEBUG test8 - enter
20:52:47.797 [main] DEBUG test8 - end
案例:sleep()防止CPU占用100%
在没有利用cpu来计算时,不要让while(true)空转浪费cpu,这时可以使用yield 或sleep来让出cpu的使用
权给其他程序
while (true) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
线程优先级会提示(hint) 调度器优先调度该线程,但它仅仅是一个提示, 调度器可以忽略它
如果cpu比较忙,那么优先级高的线程会获得更多的时间片,但cpu闲时,优先级几乎没作用
@Slf4j(topic = "test9")
public class Test9 {
public static void main(String[] args) {
Runnable task1 = () -> {
int count = 0;
while (true) {
System.out.println("----->1 " + count++);
}
};
Runnable task2 = () -> {
int count = 0;
while (true) {
// 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();
}
}
通过yeild()或是优先级的设置,可以起到一定效果,让其中的一个线程的count增长的快一些。
@Slf4j(topic = "test10")
public class Test10 {
static int r = 0;
public static void main(String[] args) {
test1();
}
private static void test1() {
log.debug("start");
Thread t1 = new Thread(() -> {
log.debug("start");
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("end");
r = 10;
}, "t1");
t1.start();
log.debug("r is {}", r);
log.debug("end");
}
}
运行结果:
10:04:14.629 [main] DEBUG test10 - start
10:04:14.658 [t1] DEBUG test10 - start
10:04:14.658 [main] DEBUG test10 - r is 0
10:04:14.659 [main] DEBUG test10 - end
10:04:14.669 [t1] DEBUG test10 - end
分析:
因为主线程和线程t1是并行执行的,t1 线程需要1秒之后才能算出r=10
而主线程一开始就要打印r的结果,所以只能打印出r=0
解决方法:
用join, 加在t1.start()之后即可
@Slf4j(topic = "test10")
public class Test10 {
static int r = 0;
public static void main(String[] args) throws InterruptedException {
test1();
}
private static void test1() throws InterruptedException {
log.debug("start");
Thread t1 = new Thread(() -> {
log.debug("start");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("end");
r = 10;
}, "t1");
t1.start();
t1.join(); // 加上这句就可以了
log.debug("r is {}", r);
log.debug("end");
}
}
运行结果:
10:07:36.843 [main] DEBUG test10 - start
10:07:36.872 [t1] DEBUG test10 - start
10:07:37.872 [t1] DEBUG test10 - end
10:07:37.872 [main] DEBUG test10 - r is 10
10:07:37.873 [main] DEBUG test10 - end
以调用方角度来讲,如果
当有两个线程同时运行并使用join时,线程1需要睡眠1s,线程2需要睡眠2s。
此时程序的总运行时间需要2s,t1.join()
和t2.join()
互换位置也一样。
@Slf4j
public class TestJoin {
public static void main(String[] args) throws InterruptedException {
// t1线程运行2s
Thread t1 = new Thread(() -> {
log.debug("start");
try {
TimeUnit.SECONDS.sleep(2);
log.debug("end");
} catch (InterruptedException e) {
e.printStackTrace();
}
},"t1");
t1.start();
t1.join(1000);
log.debug("end");
}
}
运行结果:
10:25:36.407 [t1] DEBUG Chapter3.TestJoin - start
10:25:37.406 [main] DEBUG Chapter3.TestJoin - end
10:25:38.410 [t1] DEBUG Chapter3.TestJoin - end
从运行时间上看,主线程只等待了1s。而t1线程花费2s才运行完。
@Slf4j
public class Test11 {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
log.debug("sleep....");
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
},"t1");
t1.start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("interrupt");
t1.interrupt();
log.debug("打断标记:{}", t1.isInterrupted());
}
}
运行结果:
10:42:50.898 [t1] DEBUG Chapter3.Test11 - sleep....
10:42:51.897 [main] DEBUG Chapter3.Test11 - interrupt
10:42:51.897 [main] DEBUG Chapter3.Test11 - 打断标记: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 Chapter3.Test11.lambda$main$0(Test11.java:24)
at java.lang.Thread.run(Thread.java:748)
@Slf4j
public class Test12 {
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();
TimeUnit.SECONDS.sleep(1);
log.debug("interrupt");
t1.interrupt();
}
}
运行结果:
10:48:21.350 [main] DEBUG Chapter3.Test12 - interrupt
10:48:21.352 [t1] DEBUG Chapter3.Test12 - 被打断,退出循环
通过验证打断标记来决定是否退出循环,否则即使打断t1线程,while循环也不会结束。
Two Phase Termination
在一个线程T1中如何“优雅”终止线程T2;这里的【优雅】指的是给T2一个料理后事的机会。
错误思路:
@Slf4j(topic = "test13")
public class Test13 {
public static void main(String[] args) throws InterruptedException {
TwoPhaseTermination twoPhaseTermination = new TwoPhaseTermination();
twoPhaseTermination.start();
Thread.sleep(3500);
twoPhaseTermination.stop();
}
}
@Slf4j(topic = "twoPhaseTermination")
class TwoPhaseTermination {
private Thread monitor;
// 启动监控线程
public void start() {
monitor = new Thread(() -> {
while (true) {
Thread currentThread = Thread.currentThread();
if (currentThread.isInterrupted()) {
log.debug("料理后事");
break;
}
try {
Thread.sleep(1000); // 情况1:被打断时,打断标记为加,抛出InterruptedException异常
log.debug("执行监控记录"); // 情况2:被打断时,打断标记置为真
} catch (InterruptedException e) {
e.printStackTrace();
// 重新设置打断标记
currentThread.interrupt();
}
}
});
monitor.start();
}
// 停止监控线程
public void stop() {
monitor.interrupt();
}
}
运行结果
11:16:51.748 [Thread-0] DEBUG twoPhaseTermination - 执行监控记录
11:16:52.750 [Thread-0] DEBUG twoPhaseTermination - 执行监控记录
11:16:53.750 [Thread-0] DEBUG twoPhaseTermination - 执行监控记录
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at Chapter3.TwoPhaseTermination.lambda$start$0(Test13.java:39)
at java.lang.Thread.run(Thread.java:748)
11:16:54.247 [Thread-0] DEBUG twoPhaseTermination - 料理后事
注意:
@Slf4j
public class Test14 {
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();
TimeUnit.SECONDS.sleep(1);
t1.interrupt();
}
}
运行结果:
11:26:13.031 [t1] DEBUG Chapter3.Test14 - park...
11:26:14.030 [t1] DEBUG Chapter3.Test14 - unpark...
11:26:14.030 [t1] DEBUG Chapter3.Test14 - 打断状态:true
如果不打断,将一直卡在LockSupport.park()
这个位置。
默认情况下,Java 进程需要等待所有线程都运行结束,才会结束。有一种特殊的线程叫做守护线程,只要其它非守护线程运行结束了,即使守护线程的代码没有执行完,也会强制结束。
@Slf4j
public class Test15 {
public static void main(String[] args) throws InterruptedException {
log.debug("start");
Thread t1 = new Thread(() -> {
log.debug("start");
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("end");
}, "daemon");
t1.setDaemon(true);
t1.start();
TimeUnit.SECONDS.sleep(1);
log.debug("end");
}
}
将t1设置为守护线程,当主线程结束时,守护线程被强制结束。
运行结果:
12:30:25.900 [main] DEBUG Chapter3.Test15 - start
12:30:25.929 [daemon] DEBUG Chapter3.Test15 - start
12:30:26.930 [main] DEBUG Chapter3.Test15 - end
垃圾回收器线程就是一种守护线程
Tomcat中的Acceptor和Poller线程都是守护线程,所以Tomcat接收到shutdown命令后,不会等待它们处理完当前请求
从JAVA API层描述,根据Thread.State枚举,分为六种状态:
NEW
:线程刚被创建,还没有调用start()方法RUNNABLE
:当调用了start() 方法之后,注意,Java API层面的RUNNABLE状态涵盖了操作系统层面的【可运行状态】、【运行状态】和【阻塞状态】 (由于 BIO导致的线程阻塞,在Java里无法区分,仍然认为是可运行)BLOCKED
,WAITING
, TIMED_WAITING
都是Java API层面对【阻塞状态】的细分,后面会在状态换节一详述TERMINATED
当线程代码运行结束测试6种状态
@Slf4j
public class Test16 {
public static void main(String[] args) {
// NEW
Thread t1 = new Thread(() -> {
log.debug("running...");
},"t1");
// RUNNABLE
Thread t2 = new Thread(() -> {
while (true) {
}
},"t2");
t2.start();
// TERMINATED
Thread t3 = new Thread(() -> {
log.debug("running...");
},"t3");
t3.start();
// TIMED_WAITING
Thread t4 = new Thread(() -> {
synchronized (Test16.class) {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"t4");
t4.start();
// WAITING
Thread t5 = new Thread(() -> {
try {
t4.join();
log.debug("running...");
} catch (InterruptedException e) {
e.printStackTrace();
}
},"t5");
t5.start();
// BLOCKED
Thread t6 = new Thread(() -> {
synchronized (Test16.class) {
log.debug("running");
}
},"t6");
t6.start();
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());
}
}
NEW
:线程没有start()
RUNNABLE:while
循环,线程正在运行
TERMINATED
:线程运行完毕
TIMED_WAITING
:线程睡眠种sleep
WAITING
:线程等待join
BLOCKED
:线程阻塞