为了简化代码,准备了一个工具类:
import java.time.LocalTime;
public class Utils {
public static void log(String format, Object... args) {
// 当前线程名称
String threadName = Thread.currentThread().getName();
// 为了方便打印结果的查看,若线程名长度不足16,则补空格
while (threadName.length() < 16) {
threadName = threadName + " ";
}
// 当前时间 (时-分-秒)
LocalTime now = LocalTime.now();
String realFormat = String.format("[%s][%s] %s\n", threadName, now, format);
System.out.printf(realFormat, args);
}
public static void sleep(int millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
log("sleep 中断异常: %s", e.getMessage());
}
}
}
创建线程的2个方式
方式1 :继承 Thread 类
示例:
public class TestThread {
public static class MyThread extends Thread {
@Override
public void run() {
System.out.printf("%s 线程运行中\n", Thread.currentThread().getName());
}
}
public static void main(String[] args) {
MyThread myThread = new MyThread();
System.out.printf("在线程 %s 中启动 myThread\n", Thread.currentThread().getName());
myThread.start(); // 注意,不要调用 run 方法
}
}
运行结果:
在线程 main 中启动 myThread
Thread-0 线程运行中
可以看到, Java 入口函数 main 函数所在线程名为 main
,我们一般称之为主线程。而我们自定义的线程,线程名是Thread-序号
。上面的工具类Utils中的log方法用到了 Thread.currentThread().getName()
获取线程名称。
两个注意:
- 启动线程,要用 start 方法,而不是 run 方法。因为 run 方法是暴露给开发者写业务逻辑的,不会开启新线程。
- 一个线程对象只能启动一次,再次启动时会报错。
// 一个线程对象只能启动一次,再次启动时会报错。
public class TestThread {
public static class MyThread extends Thread {
@Override
public void run() {
Utils.log("线程运行中");
}
}
public static void main(String[] args) {
MyThread myThread = new MyThread();
Utils.log("启动线程");
myThread.start();
Utils.sleep(200);
myThread.start();
}
}
执行结果:
[main ][17:45:38.480] 启动线程
[Thread-0 ][17:45:38.492] 线程运行中
Exception in thread "main" java.lang.IllegalThreadStateException
at java.lang.Thread.start(Thread.java:708)
at TestThread.main(TestThread.java:15)
方式2:实现 Runnable 接口
示例:
public class TestThread {
public static void main(String[] args) {
Thread myThread = new Thread(new Runnable() {
@Override
public void run() {
Utils.log("线程运行中");
}
});
Utils.log("启动线程");
myThread.start();
}
}
或者简化为 lambda 形式:
public class TestThread {
public static void main(String[] args) {
Thread myThread = new Thread(() -> Utils.log("线程运行中"));
Utils.log("启动线程");
myThread.start();
}
}
运行结果示例:
[main ][17:55:42.383] 启动线程
[Thread-0 ][17:55:42.395] 线程运行中
自定义线程名称
使用 setName
方法可以自定义线程名称。
示例:
public class TestThread {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
Utils.log("t1线程运行中");
});
Thread t2 = new Thread(() -> {
Utils.log("t2线程运行中");
});
Utils.log("主线程: 自定义线程名");
t1.setName("自定义线程名");
Thread.currentThread().setName("自定义主线程名");
Utils.log("主线程: 启动线程t1, t2");
t1.start();
t2.start();
}
}
运行结果:
[main ][18:29:45.381] 主线程: 自定义线程名
[自定义主线程名 ][18:29:45.396] 主线程: 启动线程t1, t2
[自定义线程名 ][18:29:45.396] t1线程运行中
[Thread-1 ][18:29:45.396] t2线程运行中
线程睡眠
Thread.sleep
方法会让当前线程睡眠指定时间。该方法可能会抛出受检查异常InterruptedException
,所以必须catch处理,或者调用方法上声明 throws InterruptedException
(如果允许的话)。
示例:
public class TestThread {
public static class MyThread extends Thread {
@Override
public void run() {
try {
Utils.log("开始");
Thread.sleep(1000);
Utils.log("结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
MyThread myThread = new MyThread();
Utils.log("启动线程");
myThread.start();
}
}
运行结果:
[main ][17:50:19.238] 启动线程
[Thread-0 ][17:50:19.249] 开始
[Thread-0 ][17:50:20.252] 结束
运行结果是,结束
、开始
之间的间隔是 1003 毫秒。Thread.sleep
只保证至少要睡眠的时间,线程被重新调度执行耗费的时间不被计算在内。
线程状态
可以通过 getState() 方法获取线程状态,线程状态定义在 Thread 类下的 State 枚举中。
示例:
public class TestThread {
public static void main(String[] args) {
Thread myThread = new Thread(() -> {
Utils.log("线程运行中");
Utils.sleep(100);
Utils.log("线程结束");
});
Utils.log("myThread 状态: " + myThread.getState());
Utils.log("启动线程");
myThread.start();
Utils.log("myThread 状态: " + myThread.getState());
Utils.sleep(10);
Utils.log("myThread 状态: " + myThread.getState());
Utils.sleep(1000);
Utils.log("myThread 状态: " + myThread.getState());
}
}
运行结果:
[main ][18:06:29.525] myThread 状态: NEW
[main ][18:06:29.538] 启动线程
[main ][18:06:29.538] myThread 状态: RUNNABLE
[Thread-0 ][18:06:29.539] 线程运行中
[main ][18:06:29.549] myThread 状态: TIMED_WAITING
[Thread-0 ][18:06:29.639] 线程结束
[main ][18:06:30.555] myThread 状态: TERMINATED
线程状态(Thread.State)有 NEW
、RUNNABLE
、BLOCKED
、WAITING
、TIMED_WAITING
、TERMINATED
。具体解释见 https://docs.oracle.com/javase/8/docs/api/java/lang/Thread.State.html。
主线程是非daemon线程
daemon 线程,也叫守护线程、后台线程。
示例:
public class TestThread {
public static void main(String[] args) {
Utils.log("主线程是否为 daemon 线程: %s", Thread.currentThread().isDaemon());
Thread.currentThread().setDaemon(true); // 这里会报错
Utils.log("主线程结束");
}
}
运行结果:
[main ][19:13:00.775] 主线程是否为 daemon 线程: false
Exception in thread "main" java.lang.IllegalThreadStateException
at java.lang.Thread.setDaemon(Thread.java:1359)
at TestThread.main(TestThread.java:5)
所有非daemon线程结束后,JVM 进程才会结束
在非daemon 线程中建立的线程默认是非 daemon 线程。在daemon线程里新建的线程默认是 daemon 线程。
public class TestThread {
public static void main(String[] args) {
Thread t = new Thread(() -> {
Utils.sleep(2000);
Utils.log("Hello");
});
t.start();
Utils.log("主线程结束");
}
}
运行结果:
[main ][13:07:59.827] 主线程结束
[Thread-0 ][13:08:01.809] Hello
上面的示例中是主线程先结束,而线程t后结束。我们可以在线程t中查看主线程的状态:
public class TestThread {
public static void main(String[] args) {
Thread mainThread = Thread.currentThread();
Thread t = new Thread(() -> {
Utils.sleep(2000);
Utils.log("Hello");
Utils.log("主线程状态: %s", mainThread.getState());
});
t.start();
Utils.log("主线程的状态: %s", t.getState());
Utils.log("线程t的状态: %s", t.getState());
Utils.log("主线程结束");
}
}
运行结果:
[main ][13:13:13.241] 主线程的状态: RUNNABLE
[main ][13:13:13.252] 线程t的状态: TIMED_WAITING
[main ][13:13:13.252] 主线程结束
[Thread-0 ][13:13:15.215] Hello
[Thread-0 ][13:13:15.216] 主线程状态: TERMINATED
主线程结束后,daemon 线程会马上结束
public class TestThread {
public static void main(String[] args) {
Thread t = new Thread(() -> {
Utils.sleep(2000);
Utils.log("Hello");
});
t.setDaemon(true);
t.start();
Utils.log("主线程的状态: %s", t.getState());
Utils.log("线程t的状态: %s", t.getState());
Utils.log("主线程结束");
}
}
执行结果:
[main ][19:11:07.199] 主线程的状态: RUNNABLE
[main ][19:11:07.215] 线程t的状态: TIMED_WAITING
[main ][19:11:07.215] 主线程结束
可以看到,线程t没有输出任何内容。
daemon 线程内创建的线程默认是daemon线程
public class TestThread {
public static void main(String[] args) {
Thread t = new Thread(() -> {
Thread innerThread = new Thread(() -> {
Utils.sleep(1000);
Utils.log("World");
});
innerThread.start();
Utils.sleep(2000);
Utils.log("Hello");
});
t.setDaemon(true);
t.start();
Utils.log("主线程结束");
}
}
执行结果:
[main ][08:06:18.574] 主线程结束
线程 t、innerThread 都没有执行,进程旧结束了。
如果我们将 innerThread 线程显式设置为 非daemon,会怎样?示例:
public class TestThread {
public static void main(String[] args) {
Thread t = new Thread(() -> {
Thread innerThread = new Thread(() -> {
Utils.sleep(1000);
Utils.log("World");
});
innerThread.setDaemon(false); // 设置 innerThread 为 非 daemon
innerThread.start();
Utils.sleep(2000);
Utils.log("Hello");
});
t.setDaemon(true);
t.start();
Utils.log("主线程结束");
}
}
执行结果:
[main ][08:09:00.004] 主线程结束
[Thread-1 ][08:09:00.983] World
使用 stop() 停止线程
该方法已经不建议使用。
示例:
public class TestThread {
public static void main(String[] args) {
Thread t = new Thread(() -> {
Utils.sleep(2000);
Utils.log("Hello");
});
t.start();
Utils.sleep(1000);
t.stop();
Utils.log("主线程结束");
}
}
执行结果:
[main ][08:06:44.816] 主线程结束
可以看到线程t中的 Hello
没有打印出来。
线程优先级
线程优先级用 int 类型数字表示,数字越大,优先级越高,获取的CPU资源也更多。
优先级范围是[1,10]
。Thread 类中内置了3个优先级常量:
常量 | 值 |
---|---|
Thread.MAX_PRIORITY | 10 |
Thread.NORM_PRIORITY | 5 |
Thread.MIN_PRIORITY | 1 |
getPriority 方法可以查看优先级。
setPriority 可以设置优先级。
代码示例:
public class TestThread {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
Utils.log("t1线程运行中");
});
Utils.log("%s", t1.getPriority());
t1.setPriority(Thread.MIN_PRIORITY);
Utils.log("%s", t1.getPriority());
t1.setPriority(10);
Utils.log("%s", t1.getPriority());
try {
t1.setPriority(11); // 超出范围,抛异常
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
执行结果:
[main ][18:59:41.945] 5
[main ][18:59:41.960] 1
[main ][18:59:41.960] 10
java.lang.IllegalArgumentException
at java.lang.Thread.setPriority(Thread.java:1089)
at TestThread.main(TestThread.java:16)
一个线程出现异常,不会影响其他线程的运行
代码示例:
public class TestThread {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
Utils.log("t1线程运行中");
Utils.sleep(100);
throw new RuntimeException("异常");
});
Thread t2 = new Thread(() -> {
Utils.log("t2线程运行中");
Utils.sleep(1000);
Utils.log("t2线程运行结束");
});
t1.start();
t2.start();
}
}
执行结果:
[Thread-1 ][19:04:32.911] t2线程运行中
[Thread-0 ][19:04:32.911] t1线程运行中
Exception in thread "Thread-0" java.lang.RuntimeException: 异常
at TestThread.lambda$main$0(TestThread.java:7)
at java.lang.Thread.run(Thread.java:748)
[Thread-1 ][19:04:33.930] t2线程运行结束
使用 Thread.yield() 重新调度线程
在某个线程 t 中执行 Thread.yield()
方法后,当前线程会暂停执行,线程调度器会重新选择一个优先级不低于线程t的处于就绪状态的线程执行。新线程可能还是线程 t 。
使用 join() 等待线程执行完成
代码示例:
public class TestThread {
public static void main(String[] args) throws InterruptedException {
Thread t2 = new Thread(() -> {
Utils.log("t2线程运行中");
Utils.sleep(1000);
Utils.log("t2线程运行结束");
});
t2.start();
t2.join(); // 等t2完成后,再往下走
Utils.log("主线程执行完成");
}
}
执行结果是:
[Thread-0 ][19:14:04.943] t2线程运行中
[Thread-0 ][19:14:05.966] t2线程运行结束
[main ][19:14:05.966] 主线程执行完成
若注释掉t2.join()
,执行结果是:
[Thread-0 ][19:12:28.570] t2线程运行中
[main ][19:12:28.570] 主线程执行完成
[Thread-0 ][19:12:29.589] t2线程运行结束