0%

java | 多线程的常见方法

常见方法。

方法名 功能说明 注意
start() 启动新线程
在新线程中运行 run 方法
start 方法只是让线程进入就绪状态,里面的代码不一定立刻执行。
每一个线程对象的 start 只能调一次,否则会报错。
run() 新线程启动后调用的方法 如果在构造 Thread 对象时传递了 Runnable 参数,则线程启动后会调用 Runnable 中的 run 方法。
否则默认不执行任何操作
join() 等待线程结束
join(long n) 等待线程运行结束
最多等待 n 毫秒
getId() 获取线程长整型的 id id 唯一
getName() 获取线程名
setName(String) 修改线程名
getPriority() 获取线程优先级
setPriority(int) 修改线程优先级 java 中规定线程优先级是 1 - 10
较大的优先级能提高该线程被 CPU 调度的几率
getState() 获取线程状态 Java 中线程状态用 6 个 enum 表示
NEW、RUNNABLE、BLOCKRD、WAITING、TIMED_WAITING、TERMINATED
isInterrupted() 判断线程是否被打断 不会清楚打断标记
isAlive() 线程是否存活(还没有运行完毕)
interrupt() 打断线程 如果打断的线程正在
sleep、wait、join
会导致被打断的线程抛出 InterruptedException,并清除打断标记
如果打断的正在运行的线程,则会设置打断标记
park 的线程被打断,也会设置打断标记
interrupted() static 判断当前线程是否被打断 会清除打断标记
currentThread() static 获取当前正在执行的线程
sleep(long n) static 让当前线程休眠 n 毫秒
yield() static 提示线程调度器让出当前线程对 CPU 的使用 主要是为了测试和调试
stop() 过时方法 停止线程运行
suspend() 过时方法 挂起(暂时)线程运行
resume() 过时方法 恢复线程运行

start 与 run

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.redisc;

import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.Test")
public class Test {
public static void main(String[] args) {
Thread t1 = new Thread("t1") {
@Override
public void run() {
log.debug("runing...");

}
};

t1.run(); // 如果直接使用的话
}
}

输出

1
21:41:03.354 [main] DEBUG c.Test - runing...

可以发现调用的主体是 main 而不是 t1,这种调用,只是普通方法的执行。

如果改成 start 的话,则会输出

1
21:44:03.663 [t1] DEBUG c.Test - runing...

线程状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.redisc;

import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.Test")
public class Test {
public static void main(String[] args) {
Thread t1 = new Thread("t1") {
@Override
public void run() {
log.debug("runing...");
}
};
System.out.println(t1.getState());
t1.start();
System.out.println(t1.getState());
}
}

输出

1
2
3
NEW
RUNNABLE
21:45:20.461 [t1] DEBUG c.Test - runing...

sleep 和 yield

sleep 让当前线程从 Running 进入 Timed Waiting 状态「就绪」

查看 sleep 的状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package com.redisc;

import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.Test")
public class Test {
public static void main(String[] args) {
Thread t1 = new Thread("t1") {
@Override
public void run() {
log.debug("runing...");
try {
Thread.sleep(1000);
} 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());
}
}

输出

1
2
3
21:49:52.759 [t1] DEBUG c.Test - runing...
21:49:52.759 [main] DEBUG c.Test - t1 state RUNNABLE
21:49:53.265 [main] DEBUG c.Test - t1 state TIMED_WAITING

sleep 打断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package com.redisc;

import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.Test")
public class Test {
public static void main(String[] args) {
Thread t1 = new Thread("t1") {
@Override
public void run() {
log.debug("runing...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
log.debug("wake up");
e.printStackTrace();
}
}
};
t1.start();
log.debug("t1 state {}", t1.getState());

try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}

log.debug("interrupt...");
t1.interrupt();
}
}

输出

1
2
3
4
5
6
7
21:54:19.895 [main] DEBUG c.Test - t1 state RUNNABLE
21:54:19.895 [t1] DEBUG c.Test - runing...
21:54:20.403 [main] DEBUG c.Test - interrupt...
21:54:20.403 [t1] DEBUG c.Test - wake up
java.lang.InterruptedException: sleep interrupted
at java.base/java.lang.Thread.sleep(Native Method)
at com.redisc.Test$1.run(Test.java:13)

TimeUnit 更好的可读性

1
2
Thread.sleep(1000);
TimeUnit.SECONDS.sleep(1);

上面两个语句是一样的效果,只不过,下面的可读性更好

yield

调用 yield 会让出当前线程从 Runing 进入 Runnable 就绪状态,然后调度执行其他线程

线程优先级

线程优先级会提示调度器优先调用该线程,但是,它仅仅是一个提示,调度器可以忽略它。

如果 CPU 比较忙,那么,优先级高的线程会获得更多的时间片,但是,CPU 空闲时,优先级几乎没用。

join

先看一个程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package com.redisc;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.TimeUnit;

@Slf4j(topic = "c.Test")
public class Test {

static int r = 0;

public static void main(String[] args) throws InterruptedException {
test();
}

private static void test() throws InterruptedException {
log.debug("开始");
Thread t1 = new Thread(() -> {
log.debug("开始");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("结束");
r = 10;
});
t1.start();
log.debug("结束 {}", r);
}
}

输出

1
2
3
4
22:09:24.918 [main] DEBUG c.Test - 开始
22:09:24.922 [Thread-0] DEBUG c.Test - 开始
22:09:24.922 [main] DEBUG c.Test - 结束 0
22:09:25.927 [Thread-0] DEBUG c.Test - 结束

t1 睡眠的时候,主线程已经执行完了。

添加 join

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package com.redisc;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.TimeUnit;

@Slf4j(topic = "c.Test")
public class Test {

static int r = 0;

public static void main(String[] args) throws InterruptedException {
test();
}

private static void test() throws InterruptedException {
log.debug("开始");
Thread t1 = new Thread(() -> {
log.debug("开始");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("结束");
r = 10;
});
t1.start();
t1.join();
log.debug("结束 {}", r);
}
}

输出

1
2
3
4
22:10:33.217 [main] DEBUG c.Test - 开始
22:10:33.219 [Thread-0] DEBUG c.Test - 开始
22:10:34.222 [Thread-0] DEBUG c.Test - 结束
22:10:34.222 [main] DEBUG c.Test - 结束 10

join 就是等待某线程运行结束。

join 的执行开始时间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package com.redisc;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.TimeUnit;

@Slf4j(topic = "c.Test")
public class Test {

static int r = 0;

public static void main(String[] args) throws InterruptedException {
test();
}

private static void test() throws InterruptedException {
Thread t1 = new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
});

Thread t2 = new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
});

long start = System.currentTimeMillis();

t1.start();
t2.start();

t1.join();
t2.join();

long end = System.currentTimeMillis();
log.debug("{}", end - start);
}
}

输出

1
22:18:43.010 [main] DEBUG c.Test - 2002

这是因为,当 start() 开始的时候,线程就已经开始运行了。虽然, t1.join() 等待 t1 执行完,但是,t2 此时也已经执行了 1 秒。

当然, join 还可以设置时间,但是有一点需要注意,join(time)。如果 线程执行时间小于 time,那么线程也会结束,即,设置的 time 是线程运行的最大时长。

interrupt 方法

打断 sleep、wait、join 的线程

打算 sleep

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package com.redisc;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.TimeUnit;

@Slf4j(topic = "c.Test")
public class Test {

static int r = 0;

public static void main(String[] args) throws InterruptedException {
test();
}

private static void test() throws InterruptedException {
Thread t1 = new Thread(() -> {
try {
log.debug("sleep");
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
});


t1.start();
Thread.sleep(2000);
log.debug("interrupt");
t1.interrupt();
Thread.sleep(200);
log.debug("打断标记: {}", t1.isInterrupted());
}
}

输出

1
2
3
4
5
6
7
8
9
22:29:59.914 [Thread-0] DEBUG c.Test - sleep
22:30:01.913 [main] DEBUG c.Test - interrupt
java.lang.InterruptedException: sleep interrupted
at java.base/java.lang.Thread.sleep(Native Method)
at java.base/java.lang.Thread.sleep(Thread.java:337)
at java.base/java.util.concurrent.TimeUnit.sleep(TimeUnit.java:446)
at com.redisc.Test.lambda$test$0(Test.java:20)
at java.base/java.lang.Thread.run(Thread.java:832)
22:30:02.114 [main] DEBUG c.Test - 打断标记: false

这里注意一下 Thread.sleep(200)。从流程上来讲,打断 sleep 的线程后,该线程的打断标记应该是 false,但是,如果去掉睡眠 200 ,则会显示 true

我的理解如下

首先,打断 sleep 的线程后,该线程的打断标记应该是 false 这句话是对的,之所以出现 true,是因为还没有来得及设定打断标记,就被输出了。

打断正常运行的线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package com.redisc;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.TimeUnit;

@Slf4j(topic = "c.Test")
public class Test {

static int r = 0;

public static void main(String[] args) throws InterruptedException {
test();
}

private static void test() throws InterruptedException {
Thread t1 = new Thread(() -> {
while (true) {
if (Thread.currentThread().isInterrupted()) {
log.debug("退出循环");
break;
}
}
}, "t1");


t1.start();
Thread.sleep(2000);
log.debug("interrupt");
t1.interrupt();
}
}

输出

1
2
22:34:46.295 [main] DEBUG c.Test - interrupt
22:34:46.298 [t1] DEBUG c.Test - 退出循环

为什么有

1
2
3
4
if (Thread.currentThread().isInterrupted()) {
log.debug("退出循环");
break;
}

判断,是因为,即便是打断线程后,该循环依然会不断的运行。

两阶段终止

打断 park 线程

打断 park 线程不会清空打断状态。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.redisc;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;

@Slf4j(topic = "c.Test")
public class Test {

public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
log.debug("park");
LockSupport.park();
log.debug("unpark");
}, "t1");

t1.start();
// TimeUnit.SECONDS.sleep(1);
// t1.interrupt();
}
}

如果执行的话,会一直停留在

1
23:20:05.252 [t1] DEBUG c.Test - park

如果加上打断状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.redisc;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;

@Slf4j(topic = "c.Test")
public class Test {

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();
}
}

输出

1
2
3
23:20:59.948 [t1] DEBUG c.Test - park
23:21:00.947 [t1] DEBUG c.Test - unpark
23:21:00.947 [t1] DEBUG c.Test - 打断状态 true

另外,LockSupport.park(); 只能执行一次,如果想要执行多次,需要在前面使用 Thread.interrupted();

守护线程

默认情况下,java 进程需要等待所有线程都运行结束,才会结束。有一种特殊的线程叫做守护线程,只要其他非守护线程运行结束,即使守护线程的代码没有执行完,也会强制结束。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.redisc;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.TimeUnit;

@Slf4j(topic = "c.Test")
public class Test {

public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
log.debug("开始运行");
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("运行结束");
}, "t1");

t1.setDaemon(true);
t1.start();
TimeUnit.SECONDS.sleep(1);
log.debug("运行结束");
}
}

输出

1
2
23:30:40.786 [t1] DEBUG c.Test - 开始运行
23:30:41.785 [main] DEBUG c.Test - 运行结束
请我喝杯咖啡吧~