常用方法概述

image.png
image.png
image.png
image.png

start 与 run

start是让线程进入就绪,里面代码不一定立刻运行(CPU 的时间片还没分给它)。每个线程对象的 start 方法只能调用一次,如果调用了多次会出现 illegalThreadStateException。而 run 则是新线程启动后会调用的方法,如果在构造 Thread 对象时传递了 Runnable 参数,则线程启动后会调用 Runnable 中的 run 方法,否则默认不执行任何操作。但可以创建 Thread 的子类对象,来覆盖默认行为。

  1. @Slf4j(topic = "c.main")
  2. public class Main {
  3. public static void main(String[] args) {
  4. Thread t1 = new Thread("t1") {
  5. @SneakyThrows
  6. @Override
  7. public void run() {
  8. log.debug(Thread.currentThread().getName());
  9. Thread.sleep(3000);
  10. }
  11. };
  12. t1.run();
  13. log.debug("do other things ...");
  14. }
  15. }

image.png

  1. @Slf4j(topic = "c.main")
  2. public class Main {
  3. public static void main(String[] args) {
  4. Thread t1 = new Thread("t1") {
  5. @SneakyThrows
  6. @Override
  7. public void run() {
  8. log.debug(Thread.currentThread().getName());
  9. Thread.sleep(3000);
  10. }
  11. };
  12. t1.start();
  13. log.debug("do other things ...");
  14. }
  15. }

image.png
通过对比可以发现,只有当调用start方法的时候才是我们创建的Thread类对象t1去执行run方法的代码,但是如果直接调用run方法,则是调用这个方法的线程(即Main线程)直接去执行run方法里面的代码。

注意:

  1. 直接调用 run 是在主线程中执行了 run,没有启动新的线程
  2. 使用 start 是启动新的线程,通过新的线程间接执行 run 中的代码

sleep 与 yield

  1. sleep方法
  • 调用 sleep 会让当前线程从 Running进入 Timed Waiting 状态(阻塞)
  • 其它线程可以使用 interrupt 方法打断正在睡眠的线程,这时 sleep 方法会抛出 InterruptedException
  • 睡眠结束后的线程未必会立刻得到执行
  • 建议用 TimeUnit 的 sleep 代替 Thread 的 sleep 来获得更好的可读性
    1. Thread t1 = new Thread("t1") {
    2. @SneakyThrows
    3. @Override
    4. public void run() {
    5. log.debug(Thread.currentThread().getName());
    6. //线程睡眠3s
    7. Thread.sleep(3000);
    8. }
    9. };
    10. t1.start();
  1. yield方法
  • 调用 yield 会让当前线程从 Running 进入 Runnable就绪状态,然后调度执行其它线程
  • 具体的实现依赖于操作系统的任务调度器
  1. 线程优先级

Java中线程优先级可以指定,范围是 1~10。但是并不是所有的操作系统都支持10级优先级的划分(比如有些操作系统只支持3级划分:低,中,高),Java只是给操作系统一个优先级的参考值,线程最终在操作系统的优先级是多少还是由操作系统决定。Java默认的线程优先级为5,线程的执行顺序由调度程序来决定,线程的优先级会在线程被调用之前设定。通常情况下,高优先级的线程将会比低优先级的线程有更高的几率得到执行。我们使用方法Thread类setPriority()实例方法来设定线程的优先级。

说明:

  1. 线程优先级会提示(hint)调度器优先调度该线程,但它仅仅是一个提示,调度器可以忽略它
  2. 如果 cpu 比较忙,那么优先级高的线程会获得更多的时间片,但 cpu 闲时,优先级几乎没作用
  1. @Slf4j(topic = "c.main")
  2. public class Main {
  3. public static void main(String[] args) {
  4. Runnable task1 = () -> {
  5. int count = 0;
  6. for (; ; ) {
  7. System.out.println("---->1 " + count++);
  8. }
  9. };
  10. Runnable task2 = () -> {
  11. int count = 0;
  12. for (; ; ) {
  13. // Thread.yield();
  14. System.out.println(" ---->2 " + count++);
  15. }
  16. };
  17. Thread t1 = new Thread(task1, "t1");
  18. Thread t2 = new Thread(task2, "t2");
  19. // t1.setPriority(Thread.MIN_PRIORITY);
  20. // t2.setPriority(Thread.MAX_PRIORITY);
  21. t1.start();
  22. t2.start();
  23. }
  24. }

我们注释 Thread.yield 和 设置t1、t2的优先级,执行程序,t1、t2的默认优先级都为5,打印的结果应该比较接近。如:
image.png
现在测试在t2中调用yield方法,测试结果:
image.png
可以看到yield方法确实有一点作用,二者的结果差了一个数量级。接着我们看设置线程优先级:
image.png
可以看到居然差别不是很大,这是因为我电脑的 cpu 不忙,只有当 cpu 比较忙的时候,线程优先级才会被考虑,这样证明了Java的线程优先级只是一个参考值,真正的调度只能由操作系统来执行。

join 方法

  1. @Slf4j(topic = "c.JoinTest")
  2. public class JoinTest {
  3. static int r = 0;
  4. public static void main(String[] args) {
  5. test1();
  6. }
  7. private static void test1() {
  8. log.debug("开始");
  9. Thread t1 = new Thread(() -> {
  10. log.debug("开始");
  11. try {
  12. Thread.sleep(1000);
  13. } catch (InterruptedException e) {
  14. e.printStackTrace();
  15. }
  16. log.debug("结束");
  17. r = 10;
  18. });
  19. t1.start();
  20. log.debug("结果为:{}", r);
  21. log.debug("结束");
  22. }
  23. }

思考: 打印结果r是什么,是0还是10?为什么?

image.png
可以看到打印的结果是0,而不是10,分析如下:首先,线程t1和main线程是并行执行的,t1线程需要1s后才能计算出r=10,但是main线程是立刻就需要打印r的值,因此打印出来的r还是原来的0。

思考: 那么有什么方法可以让r打印的值变成10?

首先,我们可以实验sleep方法,但是需要让main线程sleep的时间长于t1线程,如:
image.png
main线程3s后在打印r值,而t1线程计算r=10的时间只需要1s,因此这样就可以达到目的了,测试截图:
image.png
但是,这样让线程sleep的方法明显有点硬编码的意思,不够灵活,这时候join方法就有用处了。join方法的作用是在当前线程等待其它线程运行结束再运行该线程,即同步。如下代码:

  1. private static void test1() throws InterruptedException {
  2. log.debug("开始");
  3. Thread t1 = new Thread(() -> {
  4. log.debug("开始");
  5. try {
  6. Thread.sleep(1000);
  7. } catch (InterruptedException e) {
  8. e.printStackTrace();
  9. }
  10. log.debug("结束");
  11. r = 10;
  12. });
  13. t1.start();
  14. //在main线程中等待t1线程的结束才继续执行main线程的代码
  15. t1.join();
  16. log.debug("结果为:{}", r);
  17. log.debug("结束");
  18. }

可以看到结果依然可以打印到10
image.png
其线程运行关系图如下所示:
image.png

思考: 下面的代码运行时,main线程需要等待多少时间?如果t1和t2 join方法 的顺序颠倒了?

image.png
分析:
如果t1、t2是并行的,则等待t1时,t2并没有停止运行,因此main线程只需要2s即可,t1和t2颠倒调用顺序也是一样;如果是单核cpu,那么t1和t2无法并行,只能并发执行,因此main线程一共需要等待3s。

此外,sleep方法还可以具有等待时效,如果超过等待时间,将不会再等待其它线程的执行了,如:
image.png
从测试结果可以看到,500ms之后不再等待t1计算r=10,而是直接输出r=0
image.png

interrupt 方法

interrupt方法是打断线程的方法,但是关于打断的线程,需要分情况而论:

  • 如果是打断正在sleep、wait、join的线程则会抛出 InterruptedException 异常,并且打断标志置不会返回true,而是返回false
  • 如果打断的是正常运行的线程,该线程的打断标记会置为true,但是不会去停止被打断的线程,只是告诉它我想要打断,要真正打断还是需要它自己去停止自己,即给它处理后事的机会

    说明: 可以调用Thread类的isInterrupted方法获取线程打断标志

打断正在sleep、wait、join的线程:

  1. private static void test1() throws InterruptedException {
  2. Thread t1 = new Thread(() -> {
  3. try {
  4. Thread.sleep(1000);
  5. } catch (InterruptedException e) {
  6. e.printStackTrace();
  7. }
  8. }, "t1");
  9. t1.start();
  10. Thread.sleep(500);
  11. t1.interrupt();
  12. log.debug(" 打断状态: {}", t1.isInterrupted());
  13. }

image.png

打断正常运行的线程:

  1. private static void test3() throws InterruptedException {
  2. Thread t2 = new Thread(()->{
  3. while(true) {
  4. Thread current = Thread.currentThread();
  5. boolean interrupted = current.isInterrupted();
  6. if(interrupted) {
  7. log.debug(" 打断状态: {}", interrupted);
  8. break;
  9. }
  10. }
  11. }, "t2");
  12. t2.start();
  13. Thread.sleep(500);
  14. t2.interrupt();
  15. }

image.png
可以看到,真正停止线程的还是t2自己,只是main线程告诉t2线程说“我要打断你,你处理好后事后自己停止”

注意: Thread类有一个静态方法 Thread.interrupted(),它也会返回线程打断标志,但是和isInterrupted不同的是返回之后会置打断标志为false。

park/unpark方法

  • LockSupport.park():暂停当前线程
  • LockSupport.unpark(Thread t):恢复线程 t 的运行

    注意:

    • park之后的线程状态是WAITING状态
    • 可以提前unpark,要是执行到park时,发现提交提前被调用了unpark,就继续执行
    • 不必和Monitor配合使用

image.png
image.png
image.png
image.png