2.线程
创建线程
方法一
@Slf4j(topic = "c.test")
public class Test1 {
public static void main(String[] args) {
// 创建线程对象
Thread t = new Thread() {
public void run() {
// 要执行的任务
log.debug("create thread method");
}
};
t.setName("t1");
// 启动线程
t.start();
}
}
方法二(推荐)
@Slf4j(topic = "c")
public class Test2 {
public static void main(String[] args) {
Runnable r = new Runnable() {
@Override
public void run() {
log.debug("create thread method 2");
}
};
Thread thread = new Thread(r, "t2");
thread.start();
}
}
lambda 表达式写法
@Slf4j(topic = "c")
public class Test2 {
public static void main(String[] args) {
Runnable r = () -> log.debug("create thread method 2");
Thread thread = new Thread(r, "t2");
thread.start();
}
}
差异
- 方法一是将任务和线程绑定在一起,方法二是将任务和线程分开
- Runnable 更容易与线程池高级API配合
- Runnable让任务类脱离了Thread继承体系,更加灵活
方法三
@Slf4j(topic = "c")
public class Test3 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<Integer> task = new FutureTask<>(() ->{
log.debug("running");
Thread.sleep(2000);
return 100;
});
Thread t3 = new Thread(task, "t3");
t3.start();
Integer integer = task.get();
log.debug("return result: {}", integer);
}
}
jdk 自带工具的使用
jconsole
jconsole
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类
# 案例
nohup java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=15005 -Djava.rmi.server.hostname=192.168.19.140 -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=28020 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false -jar webvi-1.0.1-SNAPSHOT-boot.jar >nohup.out 2>&1 &
- 修改 /etc/hosts 文件将 127.0.0.1 映射至主机名
如果要认证访问,还需要做如下步骤
- 复制 jmxremote.password 文件
- 修改 jmxremote.password 和 jmxremote.access 文件的权限为 600 即文件所有者可读写
- 连接时填入 controlRole(用户名),R&D(密码)
线程原理
栈与栈帧
jvm由堆,栈,方法区组成,线程启动以后,虚拟机就会为每个线程分配一块栈内存
- 每个栈由多个栈帧组成,对应着每次方法调用时所占用的内存
- 每个线程只有一个活动的栈帧,它对应着正在调用的那个方法
public class Test4 {
public static void main(String[] args) {
method1(10);
}
public static void method1(int x){
int y = x + 1;
Object object = method2();
System.out.println(object);
}
public static Object method2(){
Object m = new Object();
return m;
}
}
栈帧如图所示:
线程上下文切换(Thread Context Switch)
因为一些原因导致cpu不再执行当前的线程,转而执行另一个线程的代码
可能是一下因素:
- 线程的cpu时间片用完了
- 垃圾回收
- 有更高优先级的线程要执行
- 线程自己调用了sleep, wait, yield,join ,park,synchronized,lock等方法
当Context Switch发生时,需要由操作系统保存当前线程的状态,并恢复另一个线程的状态,java中对应的概念是程序计数器(Program Counter Register),记录下一条jvm指令执行的地址,是每个线程私有的。
- 状态包括:程序计数器,虚拟机栈中每个栈帧的信息,如局部变量,操作数栈,返回地址等
- Context Switch频繁发生会影响性能
线程中常见的方法
方法名 | static | 功能说明 | 注意 |
---|---|---|---|
start() | 启动一个新的线程,在新的线程中运行run 方法中的代码 |
start 方法只是让线程进入就绪,里面的代码不一定立刻运行(这是cpu的时间片还未分配给它),每个线程的start 方法只能调用一次,如果调用多次会出现IllegalThreadStateException |
|
run() | 新线程启动后会调用的方法 | 在构造Thread的时候如果传入了Runnable 参数,则线程启动后会调用Runnable 中的run 方法,否则默认不会执行任何操作,但是可以创建Thread 的子类对象,来覆盖默认行为 |
|
join() | 等待线程运行结束 | ||
join(long l) | 等待线程运行结束,最多等待l 毫秒 |
||
getId() | 获取线程长整形的ID | 唯一id | |
getName() | 获取线程名称 | ||
setName(String name) | 修改线程名称 | ||
getPriority() | 获取线程优先级 | ||
setPriority(int) | 设置线程优先级 | java中规定现成的优先级是1-10的整数,较大的优先级能提高该线程被cpu调度的机率 | |
getState() | 获取线程状态 | ||
isInterrupted() | 判断是否被打断 | ||
isAlive() | 线程是否存活(是否运行完) | ||
currentThread() | static | 获取当前正在执行的线程 | |
sleep(long n) | static | 线程睡眠n毫秒 | |
yield() | static | 提示线程调度器,让出当前线程对CPu的使用 |
start VS run
@Slf4j(topic = "c.test5")
public class Test5 {
public static void main(String[] args) {
Thread t1 = new Thread(()->{
log.debug("running....");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}, "t1");
t1.run();
log.debug("main t1");
Thread t2 = new Thread(() -> {
log.debug("running....");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}, "t2");
t2.start();
log.debug("main t2");
}
}
17:41:36.054 c.test5 [main] - running....
17:41:37.060 c.test5 [main] - main t1
17:41:37.061 c.test5 [main] - main t2
17:41:37.061 c.test5 [t2] - running....
- run方法是main线程在执行,没有启动新的线程
- start是新线程t2在执行
sleep 与 yield
sleep
-
- 调用 sleep 会让当前线程从 Running进入 Timed Waiting 状态(阻塞)
-
-
其它线程可以使用
interrupt
方法打断正在睡眠的线程,这时 sleep 方法会抛出InterruptedException
@Slf4j(topic = "c.test6") public class Test6 { public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(() -> { log.debug("enter sleep..."); try { Thread.sleep(2000); } catch (InterruptedException e) { log.debug("wake up"); throw new RuntimeException(e); } }, "t1"); t1.start(); log.debug(String.valueOf(t1.getState())); Thread.sleep(500); log.debug(String.valueOf(t1.getState())); log.debug("interrupt...."); t1.interrupt(); } }
17:56:20.968 c.test6 [main] - RUNNABLE 17:56:20.968 c.test6 [t1] - enter sleep... 17:56:21.471 c.test6 [main] - TIMED_WAITING 17:56:21.471 c.test6 [main] - interrupt.... 17:56:21.471 c.test6 [t1] - wake up Exception in thread "t1" java.lang.RuntimeException: java.lang.InterruptedException: sleep interrupted at cn.com.wuhm.juc.three.day01.Test6.lambda$main$0(Test6.java:20) at java.lang.Thread.run(Thread.java:750) Caused by: java.lang.InterruptedException: sleep interrupted at java.lang.Thread.sleep(Native Method) at cn.com.wuhm.juc.three.day01.Test6.lambda$main$0(Test6.java:17) ... 1 more Process finished with exit code 0
-
-
- 睡眠结束后的线程未必会立刻得到执行
-
- 建议用 TimeUnit 的 sleep 代替 Thread 的 sleep 来获得更好的可读性
yield
-
- 调用 yield 会让当前线程从 Running 进入 Runnable就绪状态,然后调度执行其它线程
-
- 具体的实现依赖于操作系统的任务调度器
sleep的运用
在没有利用cpu的时候不要让
while(true)
空转,浪费cpu,这时可以利用yield
或sleep
来让出cpu的使用权给其他程序。可以利用wait 或条件变量来达到这个效果,不同的是,后两种都需要加锁,并且需要相应的唤醒操作,一般适用于进行同步的场景,sleep适用于无需锁同步的场景
join方法
@Slf4j(topic = "c.test7")
public class Test7 {
static int r = 0;
public static void main(String[] args) throws InterruptedException {
log.debug("start");
Thread t7 = new Thread(() -> {
log.debug("start");
try {
sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
log.debug("end");
r = 10;
}, "t7");
t7.start();
// t7.join();
log.debug("result is:{}", r);
log.debug("end");
}
}
未添加 t7.join();
的执行结果
15:38:46.278 c.test7 [main] - start
15:38:46.320 c.test7 [t7] - start
15:38:46.320 c.test7 [main] - result is:0
15:38:46.321 c.test7 [main] - end
15:38:48.321 c.test7 [t7] - end
添加 t7.join();
的执行结果:
15:37:25.455 c.test7 [main] - start
15:37:25.489 c.test7 [t7] - start
15:37:27.490 c.test7 [t7] - end
15:37:27.490 c.test7 [main] - result is:10
15:37:27.492 c.test7 [main] - end
join 同步运用
当某个线程需要等待另一个线程执行结束返回结果再向下继续运行的时候,可以采用join方法
@Slf4j(topic = "c.test7")
public class TestJoin {
static int r1 = 0;
static int r2 = 0;
public static void main(String[] args) throws InterruptedException {
test2();
}
public static void test2() throws InterruptedException{
Thread t1 = new Thread(() -> {
try {
sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
r1 = 10;
}, "t1");
Thread t2 = new Thread(() -> {
try {
sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
r2 = 20;
}, "t2");
long start = System.currentTimeMillis();
t1.start();
t2.start();
log.debug("t1 begin");
t1.join();
log.debug("t1 end");
t2.join();
log.debug("t2 end");
long end = System.currentTimeMillis();
log.debug("r1={}; r2={}; wait time = {}", r1, r2, end - start);
}
}
结果:
15:56:01.529 c.test7 [main] - t1 begin
15:56:02.530 c.test7 [main] - t1 end
15:56:03.532 c.test7 [main] - t2 end
15:56:03.532 c.test7 [main] - r1=10; r2=20; wait time = 2005
interrupt 方法
打断阻塞状态(sleep,wait,join)的线程
private static void test1() throws InterruptedException {
Thread thread = new Thread(() -> {
log.debug("sleep");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
thread.start();
// 主线程先睡眠一秒,等待线程进入sleep状态
Thread.sleep(1000);
// 打断线程
log.debug("interrupt");
thread.interrupt();
thread.join();
log.debug("打断标记: {}", thread.isInterrupted());
}
结果
16:35:40.062 c [Thread-0] - sleep
16:35:41.062 c [main] - interrupt
16:35:41.063 c [main] - 打断标记: false
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at cn.com.wuhm.juc.three.n3.TestInterrupt.lambda$test1$0(TestInterrupt.java:21)
at java.lang.Thread.run(Thread.java:750)
Process finished with exit code 0
打断正常状态的线程
private static void test2() throws InterruptedException {
Thread thread = new Thread(() -> {
log.debug("start while");
while (true) {
boolean interrupted = Thread.currentThread().isInterrupted();
if(interrupted){
log.debug("被打断了,退出循环");
break;
}
}
});
log.debug("线程开始前打断标记:{}", thread.isInterrupted());
thread.start();
Thread.sleep(1000);
log.debug("interrupt");
thread.interrupt();
log.debug("线程打断后标记:{}", thread.isInterrupted());
}
结果:
16:32:24.913 c [main] - 线程开始前打断标记:false
16:32:24.916 c [Thread-0] - start while
16:32:25.921 c [main] - interrupt
16:32:25.921 c [main] - 线程打断后标记:true
16:32:25.921 c [Thread-0] - 被打断了,退出循环
设计模式-两阶段终止(interrupt的运用)
场景:在一个线程T1中如何优雅的终止另一个线程T2
错误的方案
- 通过线程对象的stop()方法停止线程,但是stop方法会杀死线程,如果线程这是锁住了某个共享资源,那么他被杀死以后就永远不能释放锁了,其他线程也无法获取锁
- 使用
System.exit(int);
方法停止线程,他会使整个程序都停止
两阶段终止实际运用案例
可以用作运用程序的健康检测,如下图:
graph TD w("while(true)") ---> a a("有没有被打断") --- 是 ---> b("料理后事") b ---> c("结束") a --- 否 ---> d("睡眠2s") d --- 无异常 ---> e("执行监控记录") d --- 有异常 ---> i("设置打断标记") e ---> w i ---> wgraph TD w("while(true)") ---> a a("有没有被打断") --- 是 ---> b("料理后事") b ---> c("结束") a --- 否 ---> d("睡眠2s") d --- 无异常 ---> e("执行监控记录") d --- 有异常 ---> i("设置打断标记") e ---> w i ---> w
@Slf4j(topic = "c.test")
public class TwoPhaseTerminationTest {
public static void main(String[] args) throws InterruptedException {
TwoPhaseTermination twoPhaseTermination = new TwoPhaseTermination();
twoPhaseTermination.start();
Thread.sleep(5000);
twoPhaseTermination.stop();
}
}
@Slf4j(topic = "c.TwoPhaseTermination")
class TwoPhaseTermination{
private Thread monitor;
public void start(){
monitor = new Thread(()->{
while(true){
if(Thread.currentThread().isInterrupted()){
log.debug("线程被打断,处理后续操作");
break;
}else{
try {
Thread.sleep(2000);
log.debug("记录日志,监控系统");
} catch (InterruptedException e) {
e.printStackTrace();
// sleep 被打断后会清除打断标记,所以这里需要再次标记为打断
// Thread.currentThread().interrupt();
}
}
}
}, "t1");
monitor.start();
}
public void stop(){
monitor.interrupt();
}
}
如果没有28行代码,线程不会停止
17:19:30.804 c.TwoPhaseTermination [t1] - 记录日志,监控系统
17:19:32.807 c.TwoPhaseTermination [t1] - 记录日志,监控系统
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at cn.com.wuhm.juc.three.n3.TwoPhaseTermination.lambda$start$0(TwoPhaseTerminationTest.java:32)
at java.lang.Thread.run(Thread.java:750)
17:19:35.804 c.TwoPhaseTermination [t1] - 记录日志,监控系统
17:19:37.807 c.TwoPhaseTermination [t1] - 记录日志,监控系统
17:19:39.808 c.TwoPhaseTermination [t1] - 记录日志,监控系统
17:19:41.810 c.TwoPhaseTermination [t1] - 记录日志,监控系统
......
加上以后:
17:21:20.732 c.TwoPhaseTermination [t1] - 记录日志,监控系统
17:21:22.738 c.TwoPhaseTermination [t1] - 记录日志,监控系统
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at cn.com.wuhm.juc.three.n3.TwoPhaseTermination.lambda$start$0(TwoPhaseTerminationTest.java:32)
at java.lang.Thread.run(Thread.java:750)
17:21:23.732 c.TwoPhaseTermination [t1] - 线程被打断,处理后续操作
Process finished with exit code 0
主线程和守护线程
默认情况下,java进程需要等待所有的java线程都结束后才会结束,有一种特殊的线程叫守护线程,只要其他非守护线程执行结束了,即使守护线程的代码没有执行完,也会立刻终止。
@Slf4j(topic = "c.test7")
public class Test7 {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
while (true) {
if (Thread.currentThread().isInterrupted()) {
break;
}
}
}, "t1");
// 设置该线程为守护线程,默认是false,非守护线程
thread.setDaemon(true);
thread.start();
Thread.sleep(1000);
log.debug("main end");
}
}
10:08:33.572 c.test7 [main] - main end
Process finished with exit code 0
当设置为守护线程时,程序等待一秒后会立即自动停止
- 垃圾回收器线程就是一种守护线程
- Tomcat 中的 Acceptor 和 Poller 线程都是守护线程,所以 Tomcat 接收到 shutdown 命令后,不会等待它们处理完当前请求
线程状态
五种状态(操作系统)
- 初始状态:仅在语言层面创建线程对象,还未与操作系统关联
- 可运行状态:(就绪状态)指该线程已经被创建(与操作系统线程关联),可以由 CPU 调度执行
- 运行状态:获取了CPU时间片运行中的状态
- 当 CPU 时间片用完,会从【运行状态】转换至【可运行状态】,会导致线程的上下文切换
- 阻塞状态:
- 如果调用了阻塞 API,如 BIO 读写文件,这时该线程实际不会用到 CPU,会导致线程上下文切换,进入【阻塞状态】
- 等 BIO 操作完毕,会由操作系统唤醒阻塞的线程,转换至【可运行状态】
- 与【可运行状态】的区别是,对【阻塞状态】的线程来说只要它们一直不唤醒,调度器就一直不会考虑调度它们
- 终止状态】表示线程已经执行完毕,生命周期已经结束,不会再转换为其它状态
六种状态(Thread.State)
从 Java API 层面来描述 ,根据 Thread.State 枚举,分为六种状态
NEW
: 现成刚被创建,但是还没有调用start
方法RUNNABLE
当调用了start()
方法之后,注意,Java API 层面的RUNNABLE
状态涵盖了 操作系统 层面的【可运行状态】、【运行状态】和【阻塞状态】(由于 BIO 导致的线程阻塞,在 Java 里无法区分,仍然认为是可运行)BLOCKED , WAITING , TIMED_WAITING
都是 Java API 层面对【阻塞状态】的细分TERMINATED
当线程代码运行结束
测试六种状态
@Slf4j(topic = "c.test")
public class TestState {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
log.debug("running......");
}, "t1");
Thread t2 = new Thread("t2") {
@Override
public void run() {
while (true){
}
}
};
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);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
};
t4.start();
Thread t5 = new Thread("t5") {
@Override
public void run() {
try {
t2.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
};
t5.start();
Thread t6 = new Thread("t6") {
@Override
public void run() {
synchronized (TestState.class){
try {
Thread.sleep(1000000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
};
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());
}
}
11:37:07.016 c.test [t3] - running......
11:37:07.515 c.test [main] - t1 state: NEW
11:37:07.517 c.test [main] - t2 state: RUNNABLE
11:37:07.517 c.test [main] - t3 state: TERMINATED
11:37:07.517 c.test [main] - t4 state: TIMED_WAITING
11:37:07.517 c.test [main] - t5 state: WAITING
11:37:07.517 c.test [main] - t6 state: BLOCKED