线程与进程

    创建线程

    原文地址 https://www.liaoxuefeng.com/wiki/1252599548343744/1306580710588449

    Java 语言内置了多线程支持。当 Java 程序启动的时候,实际上是启动了一个 JVM 进程,然后,JVM 启动主线程来执行main()方法。在main()方法中,我们又可以启动其他线程。

    要创建一个新线程非常容易,我们需要实例化一个Thread实例,然后调用它的start()方法:

    1. public class Main {
    2. public static void main(String[] args) {
    3. Thread t = new Thread();
    4. t.start(); // 启动新线程
    5. }
    6. }

    但是这个线程启动后实际上什么也不做就立刻结束了。我们希望新线程能执行指定的代码,有以下几种方法:

    方法一:从Thread派生一个自定义类,然后覆写run()方法:

    1. public class Main {
    2. public static void main(String[] args) {
    3. Thread t = new MyThread();
    4. t.start(); // 启动新线程
    5. }
    6. }
    7. class MyThread extends Thread {
    8. @Override
    9. public void run() {
    10. System.out.println("start new thread!");
    11. }
    12. }

    执行上述代码,注意到start()方法会在内部自动调用实例的run()方法。

    方法二:创建Thread实例时,传入一个Runnable实例:

    1. public class Main {
    2. public static void main(String[] args) {
    3. Thread t = new Thread(new MyRunnable());
    4. t.start(); // 启动新线程
    5. }
    6. }
    7. class MyRunnable implements Runnable {
    8. @Override
    9. public void run() {
    10. System.out.println("start new thread!");
    11. }
    12. }

    或者用 Java8 引入的 lambda 语法进一步简写为:

    1. public class CreatThreadTest {
    2. public static void main(String[] args) {
    3. Thread threadC = new Thread(() -> {
    4. System.out.println("start new thread C...");
    5. });
    6. threadC.start();
    7. }
    8. }

    有童鞋会问,使用线程执行的打印语句,和直接在main()方法执行有区别吗?

    区别大了去了。我们看以下代码:

    1. public class Main {
    2. public static void main(String[] args) {
    3. System.out.println("main start...");
    4. Thread t = new Thread() {
    5. public void run() {
    6. System.out.println("thread run...");
    7. System.out.println("thread end.");
    8. }
    9. };
    10. t.start();
    11. System.out.println("main end...");
    12. }
    13. }

    我们用蓝色表示主线程,也就是main线程,main线程执行的代码有 4 行,首先打印main start,然后创建Thread对象,紧接着调用start()启动新线程。当start()方法被调用时,JVM 就创建了一个新线程,我们通过实例变量t来表示这个新线程对象,并开始执行。

    接着,main线程继续执行打印main end语句,而t线程在main线程执行的同时会并发执行,打印thread runthread end语句。

    run()方法结束时,新线程就结束了。而main()方法结束时,主线程也结束了。

    我们再来看线程的执行顺序:

    main线程肯定是先打印main start,再打印main end
    t线程肯定是先打印thread run,再打印thread end

    但是,除了可以肯定,main start会先打印外,main end打印在thread run之前、thread end之后或者之间,都无法确定。因为从t线程开始运行以后,两个线程就开始同时运行了,并且由操作系统调度,程序本身无法确定线程的调度顺序。

    要模拟并发执行的效果,我们可以在线程中调用Thread.sleep(),强迫当前线程暂停一段时间:

    1. /**
    2. * @author hanliukui
    3. * @date 2021/8/29
    4. * @description 类描述信息...
    5. */
    6. public class MyThreadTest2 {
    7. public static void main(String[] args) {
    8. System.out.println("main start ...");
    9. Thread thread = new Thread() {
    10. @Override
    11. public void run() {
    12. System.out.println("thread start ...");
    13. super.run();
    14. try {
    15. Thread.sleep(5000);
    16. } catch (InterruptedException e) {
    17. e.printStackTrace();
    18. }
    19. System.out.println("thread end ...");
    20. }
    21. };
    22. thread.start();
    23. System.out.println("main end ...");
    24. }
    25. }

    sleep()传入的参数是毫秒。调整暂停时间的大小,我们可以看到main线程和t线程执行的先后顺序。

    要特别注意:直接调用Thread实例的run()方法是无效的:

    1. public class Main {
    2. public static void main(String[] args) {
    3. Thread t = new MyThread();
    4. t.run();
    5. }
    6. }
    7. class MyThread extends Thread {
    8. public void run() {
    9. System.out.println("hello");
    10. }
    11. }

    直接调用run()方法,相当于调用了一个普通的 Java 方法,当前线程并没有任何改变,也不会启动新线程。上述代码实际上是在main()方法内部又调用了run()方法,打印hello语句是在main线程中执行的,没有任何新线程被创建。

    必须调用Thread实例的start()方法才能启动新线程,如果我们查看Thread类的源代码,会看到start()方法内部调用了一个private native void start0()方法,native修饰符表示这个方法是由 JVM 虚拟机内部的 C 代码实现的,不是由 Java 代码实现的。

    可以对线程设定优先级,设定优先级的方法是:

    1. Thread.setPriority(int n)

    优先级高的线程被操作系统调度的优先级较高,操作系统对高优先级线程可能调度更频繁,但我们决不能通过设置优先级来确保高优先级的线程一定会先执行。

    小结

    Java 用Thread对象表示一个线程,通过调用start()启动一个新线程;

    一个线程对象只能调用一次start()方法;

    线程的执行代码写在run()方法中;

    线程调度由操作系统决定,程序本身无法决定调度顺序;

    Thread.sleep()可以把当前线程暂停一段时间。

    线程的状态

    原文地址https://www.liaoxuefeng.com/wiki/1252599548343744/1306580742045730

    在 Java 程序中,一个线程对象只能调用一次start()方法启动新线程,并在新线程中执行run()方法。一旦run()方法执行完毕,线程就结束了。因此,Java 线程的状态有以下几种:

    New:新创建的线程,尚未执行;
    Runnable:运行中的线程,正在执行run()方法的 Java 代码;
    Blocked:运行中的线程,因为某些操作被阻塞而挂起;
    Waiting:运行中的线程,因为某些操作在等待中;
    Timed Waiting:运行中的线程,因为执行sleep()方法正在计时等待;
    Terminated:线程已终止,因为run()方法执行完毕。

    用一个状态转移图表示如下:

    1. ┌─────────────┐
    2. New
    3. └─────────────┘
    4. ┌─────────────┐ ┌─────────────┐
    5. ││ Runnable Blocked ││
    6. └─────────────┘ └─────────────┘
    7. │┌─────────────┐ ┌─────────────┐│
    8. Waiting Timed Waiting
    9. │└─────────────┘ └─────────────┘│
    10. ┌─────────────┐
    11. Terminated
    12. └─────────────┘

    当线程启动后,它可以在RunnableBlockedWaitingTimed Waiting这几个状态之间切换,直到最后变成Terminated状态,线程终止。

    线程终止的原因有:

    线程正常终止:run()方法执行到return语句返回;
    线程意外终止:run()方法因为未捕获的异常导致线程终止;
    对某个线程的Thread实例调用stop()方法强制终止(强烈不推荐使用)。

    一个线程还可以等待另一个线程直到其运行结束。例如,main线程在启动t线程后,可以通过t.join()等待t线程结束后再继续运行:

    1. public class Main {
    2. public static void main(String[] args) throws InterruptedException {
    3. Thread t = new Thread(() -> {
    4. System.out.println("hello");
    5. });
    6. System.out.println("start");
    7. t.start();
    8. t.join();
    9. System.out.println("end");
    10. }
    11. }

    main线程对线程对象t调用join()方法时,主线程将等待变量t表示的线程运行结束,即join就是指等待该线程结束,然后才继续往下执行自身线程。所以,上述代码打印顺序可以肯定是main线程先打印startt线程再打印hellomain线程最后再打印end

    如果t线程已经结束,对实例t调用join()会立刻返回。此外,join(long)的重载方法也可以指定一个等待时间,超过等待时间后就不再继续等待。

    小结
    Java线程对象Thread的状态包括:New、Runnable、Blocked、Waiting、Timed Waiting和Terminated;
    通过对另一个线程对象调用join()方法可以等待其执行结束;
    可以指定等待时间,超过等待时间线程仍然没有结束就不再等待;
    对已经运行结束的线程调用join()方法会立刻返回。

    Java的join()方法
    join()是Thread类的一个方法。根据jdk文档的定义:
    public final void join()throws InterruptedException: Waits for this thread to die.
    join()方法的作用,是等待这个线程结束;但显然,这样的定义并不清晰。个人认为”Java 7 Concurrency Cookbook”的定义较为清晰:

    join() method suspends the execution of the calling thread until the object called finishes its execution.

    也就是说,t.join()方法阻塞调用此方法的线程(calling thread),直到线程t完成,此线程再继续;通常用于在main()主线程内,等待其它线程完成再结束main()主线程。例如:

    1. public class TestJoin {
    2. public static void main(String[] args) {
    3. System.out.println("Main线程开始执行...");
    4. MyThread threadA = new MyThread("线程A");
    5. MyThread threadB = new MyThread("线程B");
    6. threadA.start();
    7. threadB.start();
    8. System.out.println("Main线程执行结束...");
    9. }
    10. }
    11. class MyThread extends Thread{
    12. private String name;
    13. MyThread(String name){
    14. this.name = name;
    15. }
    16. @Override
    17. public void run() {
    18. System.out.printf("线程 %s执行开始...\n",name);
    19. for (int i =0;i<3;i++){
    20. System.out.printf("线程 %s执行:%s\n",name,i);
    21. }
    22. System.out.printf("线程 %s执行结束...\n",name);
    23. }
    24. }

    执行结果:

    Main线程开始执行… Main线程执行结束… 线程 线程B执行开始… 线程 线程A执行开始… 线程 线程A执行:0 线程 线程A执行:1 线程 线程A执行:2 线程 线程A执行结束… 线程 线程B执行:0 线程 线程B执行:1 线程 线程B执行:2 线程 线程B执行结束…

    可以看出主线程main比其它两个线程先结束。

    使用join()方法进行测试:

    1. public class TestJoin {
    2. public static void main(String[] args) {
    3. System.out.println("Main线程开始执行...");
    4. MyThread threadA = new MyThread("线程A");
    5. MyThread threadB = new MyThread("线程B");
    6. threadA.start();
    7. threadB.start();
    8. try {
    9. threadA.join();
    10. threadB.join();
    11. } catch (InterruptedException e) {
    12. e.printStackTrace();
    13. }
    14. System.out.println("Main线程执行结束...");
    15. }
    16. }

    执行结果:

    Main线程开始执行… 线程 线程A执行开始… 线程 线程B执行开始… 线程 线程B执行:0 线程 线程B执行:1 线程 线程B执行:2 线程 线程B执行结束… 线程 线程A执行:0 线程 线程A执行:1 线程 线程A执行:2 线程 线程A执行结束… Main线程执行结束…

    可以发现,main线程,在线程A和线程B执行结束后才结束。

    最后来深入了解一下join()的源码:

    1. public final synchronized void join(long millis) throws InterruptedException {
    2. long base = System.currentTimeMillis();
    3. long now = 0;
    4. if (millis < 0) {
    5. throw new IllegalArgumentException("timeout value is negative");
    6. }
    7. if (millis == 0) {
    8. while (isAlive()) {
    9. wait(0);
    10. }
    11. } else {
    12. while (isAlive()) {
    13. long delay = millis - now;
    14. if (delay <= 0) {
    15. break;
    16. }
    17. wait(delay);
    18. now = System.currentTimeMillis() - base;
    19. }
    20. }
    21. }

    可以看出,Join方法实现是通过wait(小提示:Object 提供的方法)。 当main线程调用t.join时候,main线程会获得线程对象t的锁(wait 意味着拿到该对象的锁),调用该对象的wait(等待时间),直到该对象唤醒main线程 ,比如退出后。这就意味着main 线程调用t.join时,必须能够拿到线程t对象的锁。

    参考:https://www.cnblogs.com/techyc/p/3286678.html