Java 线程


#Java 笔记


为了简化代码,准备了一个工具类:

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() 获取线程名称。

两个注意:

  1. 启动线程,要用 start 方法,而不是 run 方法。因为 run 方法是暴露给开发者写业务逻辑的,不会开启新线程。
  2. 一个线程对象只能启动一次,再次启动时会报错。
// 一个线程对象只能启动一次,再次启动时会报错。
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)有 NEWRUNNABLEBLOCKEDWAITINGTIMED_WAITINGTERMINATED。具体解释见 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线程运行结束


( 本文完 )