《Java 多线程编程核心技术》笔记——第1章 Java 多线程技能


本章主要介绍 Thread 类中的核心方法,Thread 类的核心方法较多,读者应该着重掌握如下关键技术点:

  • 线程的启动
  • 如何使线程暂停
  • 如何使线程停止
  • 线程的优先级
  • 线程安全相关的问题

1.1 进程和多线程的概念及线程的优点

  • 进程是受操作系统管理的基本运行单元

  • 线程是在进程中独立运行的子任务

    比如:QQ.exe 这个进程运行时就有很多子任务(好友视频,下载文件,发送表情)在同时运行,其中每一项任务都可以理解成是线程在工作

  • 多线程的优点:可以最大限度的利用 CPU 的空闲时间来处理其它的任务,提高 CPU 的利用率

    比如:一边让操作系统处理正在由打印机打印的数据,一边使用 Word 编辑文档,而 CPU 在这些任务间不停地切换,由于切换速度非常快,给使用者的感受就是这些任务似乎在同时运行。

以下是单任务运行环境的模型图:

在这里插入图片描述

  • 在单任务运行环境中,任务 1 和任务 2 是两个完全独立,互不相关的任务,任务 1 是在等待远程服务器返回数据,这时 CPU 一直处于等待状态,而任务 2 必须在任务 1 运行结束后才可以运行,虽然任务 2 执行时间仅有 1 秒,但却有 10 秒的等待时间,系统运行效率大幅降低
  • 单任务运行环境的特点就是排队执行,也就是同步
  • 单任务运行环境的缺点,即 CPU 利用率大幅降低

以下是多任务运行环境的模型图:

在这里插入图片描述

  • 在多任务运行环境中,CPU 完全可以在任务 1 和 任务 2 之间来回切换,使任务 2 不必等到 10 秒再运行,系统的运行效率大大得到提升
  • 使用多线程就是在使用异步

1.2 使用多线程

一个进程正在运行时,至少会有一个线程正在运行,比如调用 main 方法的线程就是这样的:

public class Test {
    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getName());
    }
}

运行结果

main

分析:这说明一个叫 main 的线程正在执行 main() 方法中的代码

1.2.1 继承 Thread 类

实现多线程编程的方式主要有两种,一种是继承 Thread 类,一种是实现 Runnable 接口

接下来我们看一下继承 Thread 类实现多线程编程:

  1. 创建一个自定义的线程类

    public class MyThread extends Thread {
        @Override
        public void run() {
            super.run();
            System.out.println("MyThread");
        }
    }
    
    
  2. 测试类

    public class MyThreadTest {
        public static void main(String[] args) {
            MyThread myThread = new MyThread();
            myThread.start();
            System.out.println("运行结束");
        }
    }
    
    

    运行结果

    运行结束
    MyThread
    

分析:MyThread 类的 run 方法执行的时间比较晚,这也说明使用多线程技术时,代码的运行结果与代码的执行顺序或调用顺序是无关的。线程是一个子任务,CPU 以随机的方式来调用线程中的 run 方法。

下面演示线程调用的随机性:

  1. 创建一个自定义的线程类

    public class MyThread2 extends Thread {
        @Override
        public void run() {
            super.run();
            for (int i = 0; i < 10; i++) {
                int time = (int) (Math.random() * 1000);
                try {
                    Thread.sleep(time);
                    System.out.println("run="+Thread.currentThread().getName());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    
  2. 测试类

    public class MyThread2Test {
        public static void main(String[] args) {
            MyThread2 myThread2 = new MyThread2();
            myThread2.setName("myThread2");
            myThread2.start();
            for (int i = 0; i < 10; i++) {
                int time = (int) (Math.random() * 1000);
                try {
                    Thread.sleep(time);
                    System.out.println("main=" + Thread.currentThread().getName());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    

    运行结果

    run=myThread2
    main=main
    run=myThread2
    run=myThread2
    main=main
    run=myThread2
    run=myThread2
    main=main
    run=myThread2
    main=main
    run=myThread2
    run=myThread2
    main=main
    run=myThread2
    run=myThread2
    main=main
    main=main
    main=main
    main=main
    main=main
    

分析:为了展示出线程的调用具有随机特性,所以使用随机数的形式来使线程挂起,从而表现出 CPU 执行哪个线程具有不确定性

注意:Thread 类中的 start() 方法的作用是通知 ”线程规划器“ 此线程已经准备就绪,等待调用线程对象的 run() 方法,具有异步效果。如果调用 thread.run() 就是同步了,那么此线程对象并不交给 ”线程规划器“ 来进行处理,而是由 main 主线程来调用 run() 方法,也就是说要等到 run() 方法执行完才可以执行后面的代码

下面演示 start() 方法的执行顺序不代表线程的启动顺序:

  1. 创建一个自定义的线程类

    public class MyThread3 extends Thread {
        private int i;
    
        public MyThread3(int i) {
            this.i = i;
        }
    
        @Override
        public void run() {
            System.out.println(i);
            super.run();
        }
    }
    
    
  2. 测试类

    public class MyThread3Test {
        public static void main(String[] args) {
            List<MyThread3> myThread3List = new ArrayList<>();
            for (int i = 0; i < 10; i++) {
                myThread3List.add(new MyThread3(i));
            }
            for (int i = 0; i < 10; i++) {
                myThread3List.get(i).start();
            }
        }
    }
    

    运行结果

    0
    2
    3
    1
    4
    6
    9
    5
    7
    8
    

1.2.2 实现 Runnable 接口

如果想要创建的线程类已经有一个父类了,这是就不能再继承自 Thread 类了,因为 Java 不支持多继承,所以就需要实现 Runnable 接口来应对这样的情况了

Thread 类有两个构造函数 Thread(Runnable target) 和 Thread(Runnable target, String name),说明构造函数支持传入一个 Runnable 接口的对象。

下面看一下实现 Runnable 接口来实现多线程编程:

  1. 创建一个自定义的线程类

    public class MyThread4 implements Runnable{
        @Override
        public void run() {
            System.out.println("MyThread4");
        }
    }
    
    
  2. 测试类

    public class MyThread4Test {
        public static void main(String[] args) {
            MyThread4 myThread4 = new MyThread4();
            Thread thread = new Thread(myThread4);
            thread.start();
            System.out.println("运行结束");
        }
    }
    
    

    运行结果

    运行结束
    MyThread4
    

另外要说明的是,Thread 类也实现了 Runnable 接口

public class Thread implements Runnable {

这也就意味着 Thread(Runnable target) 不光可以传入 Runnable 对象,还可以传入一个 Thread 类的对象,这样做完全可以将一个 Thread 对象中的 run() 方法交给其它的线程进行调用

1.2.3 实例变量与线程安全

自定义线程类中的实例变量针对其他线程可以有共享和不共享之分

  1. 不共享的情况

    在这里插入图片描述

    下面通过一个示例来看一下数据不共享的情况:

    1. 创建一个自定义的线程类

      public class MyThread5 extends Thread {
          private int count = 5;
      
          public MyThread5(String name) {
              super();
              this.setName(name);
          }
      
          @Override
          public void run() {
              super.run();
              while (count > 0) {
                  count--;
                  System.out.println("由" + this.currentThread().getName() + "计算,count = " + count);
              }
          }
      }
      
      
    2. 测试类

      public class MyThread5Test {
          public static void main(String[] args) {
              MyThread5 a = new MyThread5("A");
              MyThread5 b = new MyThread5("B");
              MyThread5 c = new MyThread5("C");
              a.start();
              b.start();
              c.start();
      
          }
      }
      
      

      运行结果

      由B计算,count = 4
      由A计算,count = 4
      由A计算,count = 3
      由B计算,count = 3
      由B计算,count = 2
      由A计算,count = 2
      由B计算,count = 1
      由C计算,count = 4
      由B计算,count = 0
      由A计算,count = 1
      由A计算,count = 0
      由C计算,count = 3
      由C计算,count = 2
      由C计算,count = 1
      由C计算,count = 0
      

    分析:一共创建了 3 个线程,每个线程都有各自的 count 变量,自己减少自己的 count 变量的值,这样情况就是变量不共享

  2. 共享数据的情况

    在这里插入图片描述

    共享数据的情况就是多个线程可以访问同一个变量,比如在实现投票功能的软件时,多个线程可以同时处理同一个人的票数

    下面通过一个示例来看一下数据共享的情况:

    1. 创建一个自定义的线程类

      public class MyThread6 extends Thread {
          private int count = 5;
      
          @Override
          public void run() {
              super.run();
              count--;
              System.out.println("由" + this.currentThread().getName() + "计算,count = " + count);
          }
      }
      
      
    2. 测试类

      public class MyThread6Test {
          public static void main(String[] args) {
              MyThread6 myThread6 = new MyThread6();
      
              Thread a = new Thread(myThread6, "A");
              Thread b = new Thread(myThread6, "B");
              Thread c = new Thread(myThread6, "C");
              Thread d = new Thread(myThread6, "D");
              Thread e = new Thread(myThread6, "E");
      
              a.start();
              b.start();
              c.start();
              d.start();
              e.start();
          }
      }
      
      

      运行结果

      由B计算,count = 3
      由A计算,count = 3
      由C计算,count = 2
      由D计算,count = 1
      由E计算,count = 0
      

    分析:线程 A 和线程 B 打印出的 count 值都是 3,说明 A 和 B 同时对 count 进行了处理,这就产生了 ”非线程安全“ 问题。“非线程安全” 主要是指多个线程对同一个对象中的同一个实例变量进行操作时,会出现值被更改、值不同步的情况,进而影响到程序的执行流程。

    但我们想要得到的打印结果是依次递减的,该怎么解决非线程安全问题呢? 这时就需要使多个线程之间进行同步,也就是用按顺序排队的方式进行减 1 操作。更改后的代码如下:

    1. 创建一个自定义的线程类

      public class MyThread7 extends Thread {
          private int count = 5;
      
          @Override
          synchronized public void run() {
              super.run();
              count--;
              System.out.println("由" + this.currentThread().getName() + "计算,count = " + count);
          }
      }
      
      
    2. 测试类

      public class MyThread7Test {
          public static void main(String[] args) {
              MyThread7 myThread7 = new MyThread7();
      
              Thread a = new Thread(myThread7, "A");
              Thread b = new Thread(myThread7, "B");
              Thread c = new Thread(myThread7, "C");
              Thread d = new Thread(myThread7, "D");
              Thread e = new Thread(myThread7, "E");
      
              a.start();
              b.start();
              c.start();
              d.start();
              e.start();
          }
      }
      
      

      运行结果

      由A计算,count = 4
      由B计算,count = 3
      由C计算,count = 2
      由D计算,count = 1
      由E计算,count = 0
      

    分析:在 run() 方法前加入 synchronized 关键字,使多个线程在执行 run() 方法时,以排队的方式进行处理。 当一个线程调用 run() 方法前,先判断 run() 方法有没有被上锁,如果上锁,说明有其他线程正在调用 run() 方法,必须等其他线程对 run() 方法调用结束后才可以执行 run() 方法。这样也就实现了排队调用 run() 方法的目的了。

    synchronized 可以在任意对象及方法上加锁,而加锁的这段代码称为 ”互斥区“ 或 ”临界区“。

1.2.4 留意 i-- 与 System.out.println() 的异常

System.out.println() 方法内部是同步的

public void println(String x) {
    synchronized (this) {
        print(x);
        newLine();
    }
}

System.out.println() 与 i-- 联合使用时还是会产生 ”非线程安全问题“,下面通过一个示例来看一下:

  1. 创建一个自定义的线程类

    public class MyThread8 extends Thread {
        private int i = 5;
    
        @Override
        public void run() {
            super.run();
            System.out.println("i = " + (i--) + "   threadName = " + this.currentThread().getName());
        }
    }
    
    
  2. 测试类

    public class MyThread8Test {
        public static void main(String[] args) {
            MyThread8 myThread8 = new MyThread8();
            Thread t1 = new Thread(myThread8);
            Thread t2 = new Thread(myThread8);
            Thread t3 = new Thread(myThread8);
            Thread t4 = new Thread(myThread8);
            Thread t5 = new Thread(myThread8);
            t1.start();
            t2.start();
            t3.start();
            t4.start();
            t5.start();
        }
    }
    
    

    运行结果

    i = 5   threadName = Thread-2
    i = 3   threadName = Thread-4
    i = 2   threadName = Thread-5
    i = 4   threadName = Thread-3
    i = 5   threadName = Thread-1
    

分析:虽然 System.out.println() 方法内部是同步的,但 i-- 操作却是在进入 println() 之前发生的,所以还是会产生 ”非线程安全问题“。为了防止 ”非线程安全问题“ 的发生,还是应该继续使用同步方法

1.3 currentThread() 方法

currentThread() 方法可返回代码块正在被哪个线程调用的信息

下面通过一个示例进行说明:

  1. 创建一个自定义的线程类

    public class MyThread9 extends Thread {
        public MyThread9() {
            System.out.println("构造方法的打印:" + Thread.currentThread().getName());
        }
        
        @Override
        public void run() {
            super.run();
            System.out.println("run 方法的打印:" + Thread.currentThread().getName());
        }
    }
    
    
  2. 测试类

    public class MyThread9Test {
        public static void main(String[] args) {
            MyThread9 myThread9 = new MyThread9();
            myThread9.start();
        }
    }
    
    

    运行结果

    构造方法的打印:main
    run 方法的打印:Thread-0
    

分析:MyThread 类的构造函数是被 main 线程调用的,而 run 方法是被名称为 Thread-0 的线程调用的

下面再看一个比较复杂的实例:

  1. 创建一个自定义的线程类

    public class MyThread10 extends Thread {
        public MyThread10() {
            System.out.println("MyThread10---begin");
            // Thread.currentThread().getName() 指的是调用 MyThread10 这个线程类的线程的名称
            System.out.println("Thread.currentThread().getName():" + Thread.currentThread().getName());
            // this.getName() 指的是 MyThread10 这个线程类的名称
            System.out.println("this.getName():" + this.getName());
            System.out.println("MyThread10---end");
        }
    
        @Override
        public void run() {
            super.run();
            System.out.println("run---begin");
            System.out.println("Thread.currentThread().getName():" + Thread.currentThread().getName());
            System.out.println("this.getName():" + this.getName());
            System.out.println("run---end");
        }
    }
    
    
  2. 测试类

    public class MyThread10Test {
        public static void main(String[] args) {
            MyThread10 myThread10 = new MyThread10();
            System.out.println("---------");
            Thread thread = new Thread(myThread10,"A");
            thread.start();
        }
    }
    
    

    运行结果

    MyThread10---begin
    Thread.currentThread().getName():main
    this.getName():Thread-0
    MyThread10---end
    ---------
    run---begin
    Thread.currentThread().getName():A
    this.getName():Thread-0
    run---end
    

1.4 isAlive() 方法

isAlive() 方法的功能是判断当前的线程是否处于活动状态,活动状态就是线程已经启动且尚未终止

下面通过一个示例进行说明:

  1. 创建一个自定义的线程类

    public class MyThread11 extends Thread {
        @Override
        public void run() {
            super.run();
            System.out.println("run = " + this.isAlive());
        }
    }
    
    
  2. 测试类

    public class MyThread11Test {
        public static void main(String[] args) throws InterruptedException {
            MyThread11 myThread11 = new MyThread11();
            System.out.println("begin = " + myThread11.isAlive());
            myThread11.start();
            Thread.sleep(1000);
            System.out.println("end = " + myThread11.isAlive());
        }
    }
    
    

    运行结果

    begin = false
    run = true
    end = false
    

分析:begin = false 是因为 myThread11 线程还未启动,end = false 是因为 myThread11 线程已经终止

在使用 isAlive() 方法时,如果将线程对象以构造参数的方式传递给 Thread 对象进行 start() 启动,运行的结果和前面的示例是有差异的。造成差异的主要原因还是来自于 Thread.currentThread() 和 this 的差异,下面通过一个示例进行说明:

  1. 创建一个自定义的线程类

    public class MyThread12 extends Thread {
        public MyThread12() {
            System.out.println("MyThread12---begin");
            // Thread.currentThread().getName() 指的是调用 MyThread10 这个线程类的线程的名称
            System.out.println("Thread.currentThread().getName():" + Thread.currentThread().getName());
            // Thread.currentThread().isAlive() 指的是调用 MyThread10 这个线程类的线程是否处于活动状态
            System.out.println("Thread.currentThread().isAlive():" + Thread.currentThread().isAlive());
            // this.getName() 指的是 MyThread10 这个线程类的名称
            System.out.println("this.getName():" + this.getName());
            // this.isAlive() 指的是 MyThread10 这个线程类是否处于活动状态
            System.out.println("this.isAlive():" + this.isAlive());
            System.out.println("MyThread12---end");
        }
    
        @Override
        public void run() {
            super.run();
            System.out.println("run---begin");
            System.out.println("Thread.currentThread().getName():" + Thread.currentThread().getName());
            System.out.println("Thread.currentThread().isAlive():" + Thread.currentThread().isAlive());
            System.out.println("this.getName():" + this.getName());
            System.out.println("this.isAlive():" + this.isAlive());
            System.out.println("run---end");
        }
    }
    
    
  2. 测试类

    public class MyThread12Test {
        public static void main(String[] args) {
            MyThread12 myThread12 = new MyThread12();
            System.out.println("---------");
            Thread thread = new Thread(myThread12, "A");
            thread.start();
        }
    }
    
    

    运行结果

    MyThread12---begin
    Thread.currentThread().getName():main
    Thread.currentThread().isAlive():true
    this.getName():Thread-0
    this.isAlive():false
    MyThread12---end
    ---------
    run---begin
    Thread.currentThread().getName():A
    Thread.currentThread().isAlive():true
    this.getName():Thread-0
    this.isAlive():false
    run---end
    
    

1.5 sleep() 方法

sleep() 方法的作用是在指定的毫秒数内让当前 ”正在执行的线程” 休眠(暂停执行)。这个 ”正在执行的线程” 是指 this.currentThread() 返回的线程。

下面通过一个示例来说明:

  1. 创建一个自定义的线程类

    public class MyThread13 extends Thread {
        @Override
        public void run() {
            super.run();
            try {
                System.out.println("run threadName = " + this.currentThread().getName() + " begin time = " + System.currentTimeMillis());
                Thread.sleep(2000);
                System.out.println("run threadName = " + this.currentThread().getName() + " end time = " + System.currentTimeMillis());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    
  2. 测试类

    public class MyThread13Test {
        public static void main(String[] args) {
            System.out.println("begin time = " + System.currentTimeMillis());
            MyThread13 myThread13 = new MyThread13();
            myThread13.run();
            System.out.println("end time = " + System.currentTimeMillis());
        }
    }
    
    

    运行结果

    begin time = 1573470507836
    run threadName = main begin time = 1573470507837
    run threadName = main end time = 1573470509838
    end time = 1573470509838
    

分析:上面测试类中是直接调用 myThread13.run() 方法,也就是由 main 线程来调用 run() 方法,从运行结果可以看到在 run() 方法中 main 线程暂停了 2000 毫秒

接下来看看使用 start() 方法启动 myThread13 线程:

  1. 测试类

    public class MyThread13_2 {
        public static void main(String[] args) {
            System.out.println("begin time = " + System.currentTimeMillis());
            MyThread13 myThread13 = new MyThread13();
            myThread13.start();
            System.out.println("end time = " + System.currentTimeMillis());
        }
    }
    
    

    运行结果

    begin time = 1573470976618
    end time = 1573470976620
    run threadName = Thread-0 begin time = 1573470976621
    run threadName = Thread-0 end time = 1573470978621
    

分析:由于 main 线程和 myThread13 线程是异步执行的,所以首先执行的是 main 线程打印的 begin time 和 end time,而 myThread13 线程是随后运行的,从运行结果可以看到在 run() 方法中 myThread13 线程暂停了 2000 毫秒

1.6 getId() 方法

getId() 方法的作用是取得线程的唯一标识。

下面通过一个示例来说明:

  1. 创建一个测试类

     public class Test2 {
         public static void main(String[] args) {
             System.out.println(Thread.currentThread().getName());
             System.out.println(Thread.currentThread().getId());
         }
     }
     
    

    运行结果

    main
    1
    

分析:从运行结果来看,说明正在执行 main() 方法中代码的线程名称为 main,线程 id 值为 1

1.7 停止线程

停止一个线程意味着在线程处理完任务之前停掉正在做的操作,也就是放弃当前操作。

在 Java 中有以下三种方法可以终止正在运行的线程:

  1. 使用退出标志,使线程正常退出,也就是当 run() 方法完成后线程终止
  2. 使用 stop() 方法强行终止线程,已废弃,不推荐使用
  3. 使用 interrupt() 方法中断线程,经常使用

1.7.1 停止不了的线程

接下来会使用 interrupt() 方法,但这个方法不会真的停止线程,只是在当前线程中打了一个停止的标记

下面通过一个示例来说明:

  1. 创建一个自定义的线程类

    public class MyThread14 extends Thread {
        @Override
        public void run() {
            super.run();
            for (int i = 0; i < 500000; i++) {
                System.out.println(i);
            }
        }
    }
    
    
  2. 测试类

    public class MyThread14Test {
        public static void main(String[] args) {
            try {
                MyThread14 myThread14 = new MyThread14();
                myThread14.start();
                Thread.sleep(1000);
                myThread14.interrupt();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    

    运行结果

    ...
    499990
    499991
    499992
    499993
    499994
    499995
    499996
    499997
    499998
    499999
    

分析:从运行结果来看,调用 interrupt() 方法并没有停止线程,那该如何停止线程呢?

1.7.2 判断线程是否是停止状态

在介绍如何如何停止线程前,先来看一下如何判断线程的状态是否是停止的。

Thread 类中提供了两种方法

  1. this.interrupted():测试当前线程是否已经是中断状态,当前线程是指运行 this.interrupted() 方法的线程,执行后具有将状态标志清除为 false 的功能
  2. this.isInterrupted():测试线程 Thread 对象是否已经是中断状态,但不清除状态标志

下面通过一个示例来演示 interrupted() 方法的使用:

  1. 创建一个自定义的线程类

    public class MyThread15 extends Thread {
        @Override
        public void run() {
            super.run();
            for (int i = 0; i < 50000; i++) {
                System.out.println(i);
            }
        }
    }
    
    
  2. 测试类

    public class MyThread15Test {
        public static void main(String[] args) {
            try {
                MyThread15 myThread15 = new MyThread15();
                myThread15.start();
                // 让 main 线程休眠 0.5 秒,这样线程规划器会先执行 myThread15 线程
                Thread.sleep(500);
                myThread15.interrupt();
                System.out.println("是否停止1 = " + myThread15.interrupted());
                System.out.println("是否停止2 = " + myThread15.interrupted());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
        }
    }
    
    

    运行结果

    ...
    40292
    40293
    40294
    40295
    是否停止1 = false
    40296
    40297
    ...
    40803
    40804
    40805
    是否停止2 = false
    40806
    40807
    40808
    40809
    ...
    

分析:

我们在测试类中调用了以下代码,来停止 myThread15 线程:

myThread15.interrupt();

但在后面又使用了以下代码,来判断当前线程是否停止:

System.out.println("是否停止1 = " + myThread15.interrupted());
System.out.println("是否停止2 = " + myThread15.interrupted());

但打印结果说明当前线程并未停止,再看看 interrupted() 方法的解释:测试当前线程是否已经是中断状态,当前线程是指运行 this.interrupted() 方法的线程

这个当前线程是 main 线程,它从未中断过,所以打印结果是两个 false

接下来让 main 线程产生中断,再看看:

  1. 创建测试类

    public class Test3 {
        public static void main(String[] args) {
            Thread.currentThread().interrupt();
            System.out.println("是否停止1 = " + Thread.interrupted());
            System.out.println("是否停止2 = " + Thread.interrupted());
        }
    }
    
    

    运行结果

    是否停止1 = true
    是否停止2 = false
    

分析:第一次调用 interrupted() 判断当前线程已经是中断状态了,然后清除了状态标志,所以第二次调用 interrupted() 方法的返回值是 false

介绍完 interrupted() 方法,下面通过一个示例来演示 isInterrupted() 方法的使用:

  1. 创建测试类

    public class MyThread15Test_2 {
        public static void main(String[] args) {
            try {
                MyThread15 myThread15 = new MyThread15();
                myThread15.start();
                // 让 main 线程休眠 0.5 秒,这样线程规划器会先执行 myThread15 线程
                Thread.sleep(500);
                myThread15.interrupt();
                System.out.println("是否停止1 = " + myThread15.isInterrupted());
                System.out.println("是否停止2 = " + myThread15.isInterrupted());
            } catch (InterruptedException e) {
             e.printStackTrace();
            }
     }
    }
    
    

    运行结果

    ...
    48254
    48255
    48256
    48257
    是否停止1 = true
    是否停止2 = true
    48258
    48259
    48260
    ...
    

分析:从打印结果上来看,测试 myThread15 线程已经是中断状态了,而且 isInterrupted() 方法并不清除状态标志,所以打印了两个 true

1.7.3 能停止的线程——异常法

有了前面学习的知识,就可以在 for 语句中判断一下线程是否是中断状态了,如果是中断状态,则后面的代码不再运行即可。

下面通过一个示例来演示异常法停止线程:

  1. 创建一个自定义的线程类

    public class MyThread16 extends Thread {
        @Override
        public void run() {
            super.run();
            try {
                for (int i = 0; i < 50000; i++) {
                    // 如果是中断状态就不再执行后面的代码了
                    if (this.interrupted()) {
                        System.out.println("我要退出了...");
                        throw new InterruptedException();
                    }
                    System.out.println(i);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    
  2. 测试类

    public class MyThread16Test {
        public static void main(String[] args) {
            try {
                MyThread16 myThread16 = new MyThread16();
                myThread16.start();
                Thread.sleep(500);
                myThread16.interrupt();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    

    运行结果

    ...
    41332
    41333
    41334
    41335
    41336
    41337
    41338
    我要退出了...
    java.lang.InterruptedException
    	at MyThread16.run(MyThread16.java:9)
    
    

1.7.4 在沉睡中停止

如果线程在 sleep() 状态下停止线程,会是什么效果呢?

下面通过一个示例来演示:

  1. 创建一个自定义的线程类

    public class MyThread17 extends Thread {
        @Override
        public void run() {
            super.run();
            try {
                System.out.println("begin");
                Thread.sleep(100000);
                System.out.println("end");
            } catch (InterruptedException e) {
                System.out.println("线程沉睡中被中断,进入 catch !" + " this.interrupted() = "+this.interrupted());
                e.printStackTrace();
            }
        }
    }
    
    
  2. 测试类

    public class MyThread17Test {
        public static void main(String[] args) {
            try {
                MyThread17 myThread17 = new MyThread17();
                myThread17.start();
                Thread.sleep(500);
                myThread17.interrupt();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    

    运行结果

    begin
    线程沉睡中被中断,进入 catch ! this.interrupted() = false
    java.lang.InterruptedException: sleep interrupted
    	at java.lang.Thread.sleep(Native Method)
    	at MyThread17.run(MyThread17.java:7)
    
    

分析:从打印结果上来看,如果在 sleep 状态下停止某一线程,会进入 catch 语句,并清除停止状态值,使之变成 false

前面一个实验是先 sleep 然后再停止线程,接下来我们看看先停止线程再 sleep 会怎样,下面通过一个示例来演示:

  1. 创建一个自定义的线程类

    public class MyThread18 extends Thread {
        @Override
        public void run() {
            super.run();
            try {
                for (int i = 0; i < 50000; i++) {
                    System.out.println(i);
                }
                System.out.println("begin");
                Thread.sleep(100000);
                System.out.println("end");
            } catch (InterruptedException e) {
                System.out.println("线程先被中断,再遇到 sleep ,进入 catch !" + " this.interrupted() = " + this.interrupted());
                e.printStackTrace();
            }
        }
    }
    
    
  2. 测试类

    public class MyThread18Test {
        public static void main(String[] args) {
            MyThread18 myThread18 = new MyThread18();
            myThread18.start();
            myThread18.interrupt();
        }
    }
    
    

    运行结果

    49997
    49998
    49999
    begin
    线程先被中断,再遇到 sleep ,进入 catch ! this.interrupted() = false
    java.lang.InterruptedException: sleep interrupted
    	at java.lang.Thread.sleep(Native Method)
    	at MyThread18.run(MyThread18.java:10)
    
    

1.7.5 能停止的线程——暴力停止

使用 stop() 方法停止线程是非常暴力的,下面通过一个示例来演示:

  1. 创建一个自定义的线程类

    public class MyThread19 extends Thread {
        private int i = 0;
    
        @Override
        public void run() {
            super.run();
            try {
                while (true) {
                    i++;
                    System.out.println(i);
                    sleep(1000);
                }
            } catch (InterruptedException e) {
                System.out.println("进入了 catch() 中");
                e.printStackTrace();
            }
    
        }
    }
    
    
  2. 测试类

    public class MyThread19Test {
        public static void main(String[] args) {
            try {
                MyThread19 myThread19 = new MyThread19();
                myThread19.start();
                Thread.sleep(8000);
                myThread19.stop();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    

    运行结果

    1
    2
    3
    4
    5
    6
    7
    8
    

1.7.6 方法 stop() 与 java.lang.ThreadDeath 异常

调用 stop() 方法时会抛出 java.lang.ThreadDeath 异常,但在通常的情况下,此异常不需要显示地捕获。

下面通过一个示例来演示:

  1. 创建一个自定义的线程类

    public class MyThread20 extends Thread {
        @Override
        public void run() {
            super.run();
            try {
                this.stop();
            } catch (ThreadDeath e) {
                System.out.println("进入了 catch() 中");
                e.printStackTrace();
            }
        }
    }
    
    
  2. 测试类

    public class MyThread20Test {
        public static void main(String[] args) {
            MyThread20 myThread20 = new MyThread20();
            myThread20.start();
        }
    }
    
    

    运行结果

    进入了 catch() 中
    java.lang.ThreadDeath
    	at java.lang.Thread.stop(Thread.java:850)
    	at MyThread20.run(MyThread20.java:6)
    
    

1.7.7 释放锁的不良后果

stop() 方法已经被作废,原因如下:

  1. 如果强制让线程停止,则有可能使一些清洁性的工作得不到完成
  2. 对锁定的对象进行了 ”解锁“,导致数据得不到同步的处理,出现数据不一致的问题

下面通过一个示例来演示 stop() 方法造成数据不一致的示例:

  1. 创建一个对象,其中 setUsernameAndPassword 方法上锁

    public class SynchronizedObject {
        private String username = "a";
        private String password = "aa";
    
        public String getUsername() {
            return username;
        }
    
        public void setUsername(String username) {
            this.username = username;
        }
    
        public String getPassword() {
            return password;
        }
    
        public void setPassword(String password) {
            this.password = password;
        }
    
        synchronized public void setUsernameAndPassword(String username, String password) {
            try {
                this.username = username;
                Thread.sleep(10000);
                this.password = password;
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    
  2. 创建一个自定义的线程类

    public class MyThread21 extends Thread {
        private SynchronizedObject synchronizedObject;
    
        public MyThread21(SynchronizedObject synchronizedObject) {
            this.synchronizedObject = synchronizedObject;
        }
    
        @Override
        public void run() {
            super.run();
            synchronizedObject.setUsernameAndPassword("b","bb");
        }
    }
    
    
  3. 测试类

    public class MyThread21Test {
        public static void main(String[] args) {
            try {
                SynchronizedObject synchronizedObject = new SynchronizedObject();
                MyThread21 myThread21 = new MyThread21(synchronizedObject);
                myThread21.start();
                Thread.sleep(500);
                myThread21.stop();
                System.out.println(synchronizedObject.getUsername() + " " +synchronizedObject.getPassword());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    

    运行结果

    b aa
    

1.7.8 使用 return 停止线程

将方法 interrupt() 和 return 结合使用也能实现停止线程的效果,不过还是建议使用 ”抛异常“ 法来实现线程的停止,因为 catch 块可以对异常的信息进行相关的处理,而且使用异常流能更方便地控制程序的运行流程,不至于代码中出现很多个 return 造成污染

下面通过一个示例来演示使用 return 停止线程:

  1. 创建一个自定义的线程类

    public class MyThread22 extends Thread {
        private int i = 0;
        @Override
        public void run() {
            super.run();
            while (true) {
                if(this.isInterrupted()){
                    System.out.println("退出了");
                    return;
                }
                System.out.println(i);
                i++;
            }
        }
    }
    
    
  2. 测试类

    public class MyThread22Test {
        public static void main(String[] args) {
            try {
                MyThread22 myThread22 = new MyThread22();
                myThread22.start();
                Thread.sleep(500);
                myThread22.interrupt();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    

    运行结果

    ...
    16916
    16917
    16918
    16919
    退出了
    

1.8 暂停线程

暂停线程意味着此线程还可以恢复运行。在 Java 多线程中,可以使用 suspend() 方法暂停线程,使用 resume() 方法恢复线程的执行。这两个方法已经是过期作废的方法了

1.8.1 suspend 与 resume 方法的使用

下面通过一个示例来演示 suspend 与 resume 方法的使用:

  1. 创建一个自定义的线程类

    public class MyThread23 extends Thread {
        private int i = 0;
    
        public int getI() {
            return i;
        }
    
        public void setI(int i) {
            this.i = i;
        }
    
        @Override
        public void run() {
            super.run();
            while (true) {
                i++;
            }
        }
    }
    
    
  2. 测试类

    public class MyThread23Test {
        public static void main(String[] args) {
            try {
                // 创建线程
                MyThread23 myThread23 = new MyThread23();
                // 启动线程
                myThread23.start();
                Thread.sleep(500);
                // 暂停线程
                myThread23.suspend();
    
                // A 段
                System.out.println("A = " + System.currentTimeMillis() + " i = " + myThread23.getI());
                Thread.sleep(500);
                System.out.println("A = " + System.currentTimeMillis() + " i = " + myThread23.getI());
    
                // 恢复线程
                myThread23.resume();
                Thread.sleep(500);
                // 暂停线程
                myThread23.suspend();
    
                // B 段
                System.out.println("B = " + System.currentTimeMillis() + " i = " + myThread23.getI());
                Thread.sleep(500);
                System.out.println("B = " + System.currentTimeMillis() + " i = " + myThread23.getI());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    

    运行结果

    A = 1573647074155 i = 220277857
    A = 1573647074655 i = 220277857
    B = 1573647075156 i = 469660276
    B = 1573647075656 i = 469660276
    

分析:从打印结果上来看,线程的确被暂停了,而且还可以恢复成运行的状态

1.8.2 suspend 和 resume 方法的缺点——独占

在使用 suspend 和 resume 方法时,如果使用不当,极易造成公共的同步对象的独占,使得其他线程无法访问公共的同步对象。

下面通过一个示例来演示独占锁的情况:

  1. 创建一个自定义的线程类

    public class MyThread24 extends Thread {
        private int count = 5;
    
        @Override
        synchronized public void run() {
            super.run();
            count--;
            System.out.println("由" + this.currentThread().getName() + "计算,count = " + count);
            if (this.currentThread().getName() == "A") {
                // A 线程永远 suspend 了
                Thread.currentThread().suspend();
            }
        }
    }
    
    
  2. 测试类

    public class MyThread24Test {
        public static void main(String[] args) {
            try {
                MyThread24 myThread24 = new MyThread24();
                Thread a = new Thread(myThread24, "A");
                a.start();
                Thread.sleep(500);
                Thread b = new Thread(myThread24, "B");
                b.start();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
        }
    }
    
    

    运行结果

    由A计算,count = 4
    

分析:A 线程先在 run() 方法中被永远暂停了,然后 B 线程就不能进入 run() 方法了,因为 run() 方法被 A 线程独占了

下面通过一个示例来演示另一种独占锁的情况:

  1. 创建一个自定义的线程类

    public class MyThread25 extends Thread {
        private int i = 0;
        @Override
        public void run() {
            super.run();
            while (true) {
                i++;
    //            System.out.println(i);
            }
        }
    }
    
    
  2. 测试类

    public class MyThread25Test {
        public static void main(String[] args) {
            try {
                MyThread25 myThread25 = new MyThread25();
                myThread25.start();
                Thread.sleep(500);
                myThread25.suspend();
                System.out.println("main end");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    

    运行结果

    main end
    
  3. 接下来把 MyThread25 中的注释去掉,再次运行,运行结果如下

    ...
    41172
    41173
    41174
    41175
    41176
    
    

分析:第二次运行控制台没有打印 main end,出现这样的情况的原因是,当程序运行到 println() 方法内部时,同步锁未被释放。

之前讲过,System.out.println() 方法内部是同步的,源代码如下:

public void println(String x) {
    synchronized (this) {
        print(x);
        newLine();
    }
}

这导致 PrintStream 对象的 println() 方法一直呈暂停状态,并且锁未释放,所以 main() 方法中的代码 System.out.println(“main end”); 迟迟不能打印。

1.8.3 suspend 和 resume 方法的缺点——不同步

在使用 suspend 和 resume 方法时也容易出现因为线程的暂停而导致数据不同步的情况。

下面通过一个示例来演示数据不同步的情况:

  1. 创建一个自定义的实体类

    public class MyObject {
        private String username = "a";
        private String password = "aa";
    
        public void setUsernameAndPassword(String username, String password) {
            this.username = username;
            if (Thread.currentThread().getName() == "A"){
                // 线程暂停了
                Thread.currentThread().suspend();
            }
            this.password = password;
        }
    
        public void printUsernameAndPassword() {
            System.out.println(this.username + " " + this.password);
        }
    }
    
    
  2. 测试类

    public class MyObjectTest {
        public static void main(String[] args) {
            try {
                MyObject myObject = new MyObject();
                Thread thread1 = new Thread() {
                    @Override
                    public void run() {
                        super.run();
                        myObject.setUsernameAndPassword("b","bb");
                    }
                };
                thread1.setName("A");
                thread1.start();
                Thread.sleep(500);
                Thread thread2 = new Thread(){
                    @Override
                    public void run() {
                        super.run();
                        myObject.printUsernameAndPassword();
                    }
                };
                thread2.start();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    

    运行结果

    b aa
    

1.9 yield 方法

yield() 方法的作用是放弃当前的 CPU 资源,将它让给其它的任务去占用 CPU 执行时间。但放弃的时间不确定,有可能刚刚放弃,马上又获得 CPU 时间片。

下面通过一个示例来演示 yield 方法的使用:

  1. 创建一个自定义的线程类

    public class MyThread26 extends Thread {
        @Override
        public void run() {
            super.run();
            long beginTime = System.currentTimeMillis();
            int count = 0;
            for (int i = 0; i < 5000000; i++) {
    //            Thread.yield();
                count += i;
            }
            System.out.println(count);
            long endTime = System.currentTimeMillis();
            System.out.println("用时:" + (endTime - beginTime) + "毫秒");
        }
    }
    
    
  2. 测试类

    public class MyThread26Test {
        public static void main(String[] args) {
            MyThread26 myThread26 = new MyThread26();
            myThread26.start();
        }
    }
    
    

    运行结果

    1642668640
    用时:5毫秒
    
  3. 接下来把 MyThread26 中的注释取消,再次运行,运行结果如下:

    1642668640
    用时:4726毫秒
    

分析:显然,第二次运行,将 CPU 让给其他资源导致速度变慢了

1.10 线程的优先级

在操作系统中,线程可以划分优先级,优先级高的线程得到的 CPU 资源较多,也就是 CPU 优先执行优先级较高的线程对象中的任务。

设置线程优先级有助于帮 “线程规划器” 确定在下一次选择哪个线程来优先执行。

设置线程的优先级使用 setPriority() 方法,此方法源码如下:

public final void setPriority(int newPriority) {
    ThreadGroup g;
    checkAccess();
    if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
        throw new IllegalArgumentException();
    }
    if((g = getThreadGroup()) != null) {
        if (newPriority > g.getMaxPriority()) {
            newPriority = g.getMaxPriority();
        }
        setPriority0(priority = newPriority);
    }
}

在 Java 中,线程的优先级分为 1-10 这 10 个等级,如果小于 1 或大于 10,则 JDK 抛出异常 throw new IllegalArgumentException()

1.10.1 线程优先级的继承性

在 Java 中,线程的优先级具有继承性,比如 A 线程启动 B 线程,则 B 线程的优先级与 A 是一样的。

下面通过一个示例来演示:

  1. 创建两个自定义的线程类

    public class MyThread27 extends Thread {
        @Override
        public void run() {
            super.run();
            System.out.println("MyThread27 priority: " + this.getPriority());
            MyThread27_2 myThread27_2 = new MyThread27_2();
            myThread27_2.start();
        }
    }
    
    
    public class MyThread27_2 extends Thread {
        @Override
        public void run() {
            super.run();
            System.out.println("MyThread27_2 priority: " + this.getPriority());
        }
    }
    
    
  2. 测试类

    public class MyThread27Test {
        public static void main(String[] args) {
            System.out.println("main begin priority: "+Thread.currentThread().getPriority());
    //        Thread.currentThread().setPriority(6);
            System.out.println("main end priority: "+Thread.currentThread().getPriority());
            MyThread27 myThread27 = new MyThread27();
            myThread27.start();
        }
    }
    
    

    运行结果

    main begin priority: 5
    main end priority: 5
    MyThread27 priority: 5
    MyThread27_2 priority: 5
    
  3. 接下来把 MyThread27Test 中的注释取消,再次运行,运行结果如下:

    main begin priority: 5
    main end priority: 6
    MyThread27 priority: 6
    MyThread27_2 priority: 6
    
    

分析:显然,第二次运行优先级被更改后,依然被继承了

1.10.2 优先级具有规则性

线程的优先级具有一定的规则性,也就是 CPU 尽量将执行资源让给优先级比较高的线程。

下面通过一个示例来演示:

  1. 创建两个自定义的线程类

    public class MyThread28 extends Thread {
        @Override
        public void run() {
            super.run();
            long beginTime = System.currentTimeMillis();
            int count = 0;
            for (int i = 0; i < 100000000; i++) {
                count += i;
            }
            long endTime = System.currentTimeMillis();
            System.out.println("MyThread28 用时:" + (endTime - beginTime) + "毫秒");
        }
    }
    
    
    public class MyThread28_2 extends Thread {
        @Override
        public void run() {
            super.run();
            long beginTime = System.currentTimeMillis();
            int count = 0;
            for (int i = 0; i < 50000000; i++) {
                count += i;
            }
            long endTime = System.currentTimeMillis();
            System.out.println("MyThread28_2 用时:" + (endTime - beginTime) + "毫秒");
        }
    }
    
    
  2. 测试类

    public class MyThread28Test {
        public static void main(String[] args) {
            for (int i = 0; i < 5; i++) {
                MyThread28 myThread28 = new MyThread28();
                myThread28.setPriority(10);
                myThread28.start();
                MyThread28_2 myThread28_2 = new MyThread28_2();
                myThread28_2.setPriority(1);
                myThread28_2.start();
            }
        }
    }
    
    

    运行结果

    MyThread28 用时:44毫秒
    MyThread28 用时:45毫秒
    MyThread28 用时:47毫秒
    MyThread28 用时:39毫秒
    MyThread28_2 用时:26毫秒
    MyThread28_2 用时:31毫秒
    MyThread28 用时:39毫秒
    MyThread28_2 用时:58毫秒
    MyThread28_2 用时:22毫秒
    MyThread28_2 用时:22毫秒
    

分析:从打印结果上来看,高优先级的线程总是大部分先执行完,但不代表高优先级的线程全部先执行完

当线程优先级的等级差距很大时,谁先执行完和代码的调用顺序无关,为了验证这一结论,继续实验:

  1. 测试类

    public class MyThread28Test_2 {
        public static void main(String[] args) {
            for (int i = 0; i < 5; i++) {
                MyThread28 myThread28 = new MyThread28();
                myThread28.setPriority(1);
                myThread28.start();
                MyThread28_2 myThread28_2 = new MyThread28_2();
                myThread28_2.setPriority(10);
                myThread28_2.start();
            }
        }
    }
    
    
  2. 运行结果

    MyThread28_2 用时:27毫秒
    MyThread28_2 用时:26毫秒
    MyThread28_2 用时:28毫秒
    MyThread28_2 用时:20毫秒
    MyThread28_2 用时:6毫秒
    MyThread28 用时:63毫秒
    MyThread28 用时:96毫秒
    MyThread28 用时:106毫秒
    MyThread28 用时:116毫秒
    MyThread28 用时:116毫秒
    

分析:可以发现 MyThread28_2 先执行完,也就验证了线程的优先级与代码的执行顺序无关,出现这样的结果完全是因为 MyThread28_2 的优先级是最高的

1.10.3 优先级具有随机性

线程的优先级具有 “随机性”,也就是优先级较高的线程不一定每一次都先执行完。

下面通过一个示例来演示:

  1. 创建两个自定义的线程类

    public class MyThread29 extends Thread {
        @Override
        public void run() {
            super.run();
            long beginTime = System.currentTimeMillis();
            int count = 0;
            for (int i = 0; i < 100000000; i++) {
                count += i;
            }
            long endTime = System.currentTimeMillis();
            System.out.println("MyThread29 用时:" + (endTime - beginTime) + "毫秒");
        }
    }
    
    
    public class MyThread29_2 extends Thread{
        @Override
        public void run() {
            super.run();
            long beginTime = System.currentTimeMillis();
            int count = 0;
            for (int i = 0; i < 100000000; i++) {
                count += i;
            }
            long endTime = System.currentTimeMillis();
            System.out.println("MyThread29_2 用时:" + (endTime - beginTime) + "毫秒");
        }
    }
    
    
  2. 测试类

    public class MyThread29Test {
        public static void main(String[] args) {
            for (int i = 0; i < 5; i++) {
                MyThread29 myThread29 = new MyThread29();
                myThread29.setPriority(5);
                myThread29.start();
                MyThread29_2 myThread29_2 = new MyThread29_2();
                myThread29_2.setPriority(6);
                myThread29_2.start();
            }
    
        }
    }
    
    

    运行结果

    MyThread29_2 用时:63毫秒
    MyThread29 用时:80毫秒
    MyThread29_2 用时:81毫秒
    MyThread29 用时:90毫秒
    MyThread29 用时:67毫秒
    MyThread29_2 用时:70毫秒
    MyThread29_2 用时:78毫秒
    MyThread29 用时:68毫秒
    MyThread29 用时:74毫秒
    MyThread29_2 用时:64毫秒
    

分析:为了让结果体现 “随机性”,所以两个线程的优先级一个设置为 5,另一个设置为 6,让优先级接近一些。从打印结果上来看,MyThread29_2 线程的优先级更高,但 MyThread29_2 线程不一定每一次都先执行完。

1.10.4 看谁运行的快

优先级高的线程和优先级低的谁的运行速度更快,下面通过一个示例来演示:

  1. 创建两个自定义的线程类

    public class MyThread30 extends Thread {
        private long count = 0;
    
        public long getCount() {
            return count;
        }
    
        public void setCount(long count) {
            this.count = count;
        }
    
        @Override
        public void run() {
            super.run();
            while (true) {
                count++;
            }
        }
    }
    
    
    public class MyThread30_2 extends Thread {
        private long count = 0;
    
        public long getCount() {
            return count;
        }
    
        public void setCount(long count) {
            this.count = count;
        }
    
        @Override
        public void run() {
            super.run();
            while (true) {
                count++;
            }
        }
    }
    
    
  2. 测试类

    public class MyThread30Test {
        public static void main(String[] args) {
            try {
                MyThread30 myThread30 = new MyThread30();
                myThread30.setPriority(Thread.NORM_PRIORITY - 3);
                myThread30.start();
    
                MyThread30_2 myThread30_2 = new MyThread30_2();
                myThread30_2.setPriority(Thread.NORM_PRIORITY + 3);
                myThread30_2.start();
    
                Thread.sleep(20000);
                myThread30.stop();
                myThread30_2.stop();
    
                System.out.println("myThread30:" + myThread30.getCount());
                System.out.println("myThread30_2:" + myThread30_2.getCount());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
    
        }
    }
    
    

    运行结果

    myThread30:5682347365
    myThread30_2:7136576679
    

分析:从打印结果上来看,优先级高的线程运行速度更快

1.11 守护线程

在 Java 线程中有两种线程,一种是用户线程,另一种就是守护(Daemon)线程。

什么是守护线程呢? 守护线程是一种特殊的线程,它的特性有陪伴的含义,当进程中不存在非守护进程了,则守护线程自动销毁。典型的守护进程就是垃圾回收线程,当进程中没有非守护进程了,则垃圾回收线程也没有存在的必要了,自动销毁。

下面通过一个示例来演示守护线程:

  1. 创建一个自定义的线程类

    public class MyThread31 extends Thread {
        private int i = 0;
    
        @Override
        public void run() {
            super.run();
            try {
                while(true){
                    i++;
                    System.out.println(i);
                    Thread.sleep(1000);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    
  2. 测试类

    public class MyThread31Test {
        public static void main(String[] args) {
            try {
                MyThread31 myThread31 = new MyThread31();
                // 将一个用户线程设置为守护线程
                myThread31.setDaemon(true);
                myThread31.start();
                Thread.sleep(5000);
                System.out.println("守护线程退出了");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    

    运行结果

    1
    2
    3
    4
    5
    守护线程退出了
    
发布了64 篇原创文章 · 获赞 20 · 访问量 6477

猜你喜欢

转载自blog.csdn.net/bm1998/article/details/103071700