前置:线程与进程的区别 | 东的博客 (dongtech.eu.org)

一、创建线程的四种方式

参考回答:

共有四种方式可以创建线程,分别是:继承Thread类、实现runnable接口、实现Callable接口、线程池创建线程

详细创建方式参考下面代码:

继承Thread类

 public class MyThread extends Thread {
 ​
     @Override
     public void run() {
         System.out.println("MyThread...run...");
     }
 ​
     
     public static void main(String[] args) {
 ​
         // 创建MyThread对象
         MyThread t1 = new MyThread() ;
         MyThread t2 = new MyThread() ;
 ​
         // 调用start方法启动线程
         t1.start();
         t2.start();
 ​
     }
     
 }

实现runnable接口

 public class MyRunnable implements Runnable{
 ​
     @Override
     public void run() {
         System.out.println("MyRunnable...run...");
     }
 ​
     public static void main(String[] args) {
 ​
         // 创建MyRunnable对象
         MyRunnable mr = new MyRunnable() ;
 ​
         // 创建Thread对象
         Thread t1 = new Thread(mr) ;
         Thread t2 = new Thread(mr) ;
 ​
         // 调用start方法启动线程
         t1.start();
         t2.start();
 ​
     }
 ​
 }

实现Callable接口

 public class MyCallable implements Callable<String> {
 ​
     @Override
     public String call() throws Exception {
         System.out.println("MyCallable...call...");
         return "OK";
     }
 ​
     public static void main(String[] args) throws ExecutionException, InterruptedException {
 ​
         // 创建MyCallable对象
         MyCallable mc = new MyCallable() ;
 ​
         // 创建F
         FutureTask<String> ft = new FutureTask<String>(mc) ;
 ​
         // 创建Thread对象
         Thread t1 = new Thread(ft) ;
         Thread t2 = new Thread(ft) ;
 ​
         // 调用start方法启动线程
         t1.start();
 ​
         // 调用ft的get方法获取执行结果
         String result = ft.get();
 ​
         // 输出
         System.out.println(result);
 ​
     }
 ​
 }

线程池创建线程

 public class MyExecutors implements Runnable{
 ​
     @Override
     public void run() {
         System.out.println("MyRunnable...run...");
     }
 ​
     public static void main(String[] args) {
 ​
         // 创建线程池对象
         ExecutorService threadPool = Executors.newFixedThreadPool(3);
         threadPool.submit(new MyExecutors()) ;
 ​
         // 关闭线程池
         threadPool.shutdown();
 ​
     }
 ​
 }

二、runnable 和 callable 的区别

  1. Runnable 接口run方法没有返回值;Callable接口call方法有返回值,是个泛型,和Future、FutureTask配合可以用来获取异步执行的结果

  2. Callalbe接口支持返回执行结果,需要调用FutureTask.get()得到,此方法会阻塞主进程的继续往下执行,如果不调用不会阻塞。

  3. Callable接口的call()方法允许抛出异常;而Runnable接口的run()方法的异常只能在内部消化,不能继续上抛

三、线程的 run()和 start()的区别

start(): 用来启动线程,通过该线程调用run方法执行run方法中所定义的逻辑代码。start方法只能被调用一次。

run(): 封装了要被线程执行的代码,可以被调用多次。

四、线程包括哪些状态,状态之间是如何变化的

线程的状态可以参考JDK中的Thread类中的枚举State

public enum State {
        /**
         * 尚未启动的线程的线程状态
         */
        NEW,

        /**
         * 可运行线程的线程状态。处于可运行状态的线程正在 Java 虚拟机中执行,但它可能正在等待来自		 
         * 操作系统的其他资源,例如处理器。
         */
        RUNNABLE,

        /**
         * 线程阻塞等待监视器锁的线程状态。处于阻塞状态的线程正在等待监视器锁进入同步块/方法或在调          
         * 用Object.wait后重新进入同步块/方法。
         */
        BLOCKED,

        /**
         * 等待线程的线程状态。由于调用以下方法之一,线程处于等待状态:
		 * Object.wait没有超时
         * 没有超时的Thread.join
         * LockSupport.park
         * 处于等待状态的线程正在等待另一个线程执行特定操作。
         * 例如,一个对对象调用Object.wait()的线程正在等待另一个线程对该对象调用Object.notify()			
         * 或Object.notifyAll() 。已调用Thread.join()的线程正在等待指定线程终止。
         */
        WAITING,

        /**
         * 具有指定等待时间的等待线程的线程状态。由于以指定的正等待时间调用以下方法之一,线程处于定          
         * 时等待状态:
		 * Thread.sleep
		 * Object.wait超时
		 * Thread.join超时
		 * LockSupport.parkNanos
		 * LockSupport.parkUntil
         * </ul>
         */
        TIMED_WAITING,

        /**
         * 已终止线程的线程状态。线程已完成执行
         */
        TERMINATED;
    }

状态之间的转换

分别是

  • 新建 NEW

    • 当一个线程对象被创建,但还未调用 start 方法时处于新建状态

    • 此时未与操作系统底层线程关联

  • 可运行 RUNNABLE

    • 调用了 start 方法,就会由新建进入可运行

    • 此时与底层线程关联,由操作系统调度执行

  • 终结 TERMINATED

    • 线程内代码已经执行完毕,由可运行进入终结

    • 此时会取消与底层线程关联

  • 阻塞 BLOCKED

    • 当获取锁失败后,由可运行进入 Monitor 的阻塞队列阻塞,此时不占用 cpu 时间

    • 当持锁线程释放锁时,会按照一定规则唤醒阻塞队列中的阻塞线程,唤醒后的线程进入可运行状态

  • 等待 WAITING

    • 当获取锁成功后,但由于条件不满足,调用了 wait() 方法,此时从可运行状态释放锁进入 Monitor 等待集合等待,同样不占用 cpu 时间

    • 当其它持锁线程调用 notify() 或 notifyAll() 方法,会按照一定规则唤醒等待集合中的等待线程,恢复为可运行状态

  • 有时限等待 TIME_WAITING

    • 当获取锁成功后,但由于条件不满足,调用了 wait(long) 方法,此时从可运行状态释放锁进入 Monitor 等待集合进行有时限等待,同样不占用 cpu 时间

    • 当其它持锁线程调用 notify() 或 notifyAll() 方法,会按照一定规则唤醒等待集合中的有时限等待线程,恢复为可运行状态,并重新去竞争锁

    • 如果等待超时,也会从有时限等待状态恢复为可运行状态,并重新去竞争锁

    • 还有一种情况是调用 sleep(long) 方法也会从可运行状态进入有时限等待状态,但与 Monitor 无关,不需要主动唤醒,超时时间到自然恢复为可运行状态

五、新建 T1、T2、T3 三个线程,如何保证它们按顺序执行?

在多线程中有多种方法让线程按特定顺序执行,你可以用线程类的join()方法在一个线程中启动另一个线程,另外一个线程完成该线程继续执行。

代码举例:

为了确保三个线程的顺序你应该先启动最后一个(T3调用T2,T2调用T1),这样T1就会先完成而T3最后完成

 public class JoinTest {
 ​
     public static void main(String[] args) {
 ​
         // 创建线程对象
         Thread t1 = new Thread(() -> {
             System.out.println("t1");
         }) ;
 ​
         Thread t2 = new Thread(() -> {
             try {
                 t1.join();                          // 加入线程t1,只有t1线程执行完毕以后,再次执行该线程
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
             System.out.println("t2");
         }) ;
 ​
 ​
         Thread t3 = new Thread(() -> {
             try {
                 t2.join();                              // 加入线程t2,只有t2线程执行完毕以后,再次执行该线程
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
             System.out.println("t3");
         }) ;
 ​
         // 启动线程
         t1.start();
         t2.start();
         t3.start();
 ​
     }
 ​
 }

六、notify()和 notifyAll()有什么区别?

notifyAll:唤醒所有wait的线程

notify:只随机唤醒一个 wait 线程

 package com.itheima.basic;
 ​
 public class WaitNotify {
 ​
     static boolean flag = false;
     static Object lock = new Object();
 ​
     public static void main(String[] args) {
 ​
         Thread t1 = new Thread(() -> {
             synchronized (lock){
                 while (!flag){
                     System.out.println(Thread.currentThread().getName()+"...wating...");
                     try {
                         lock.wait();
                     } catch (InterruptedException e) {
                         e.printStackTrace();
                     }
                 }
                 System.out.println(Thread.currentThread().getName()+"...flag is true");
             }
         });
 ​
         Thread t2 = new Thread(() -> {
             synchronized (lock){
                 while (!flag){
                     System.out.println(Thread.currentThread().getName()+"...wating...");
                     try {
                         lock.wait();
                     } catch (InterruptedException e) {
                         e.printStackTrace();
                     }
                 }
                 System.out.println(Thread.currentThread().getName()+"...flag is true");
             }
         });
 ​
         Thread t3 = new Thread(() -> {
             synchronized (lock) {
                 System.out.println(Thread.currentThread().getName() + " hold lock");
                 lock.notifyAll();
                 flag = true;
                 try {
                     Thread.sleep(2000);
                 } catch (InterruptedException e) {
                     e.printStackTrace();
                 }
             }
         });
         t1.start();
         t2.start();
         t3.start();
     }
 }

 结果:

 Thread-0...wating...

 Thread-1...wating...

 Thread-2 hold lock

 Thread-1...flag is true

 Thread-0...flag is true

七、在 java 中 wait 和 sleep 方法的不同?

参考回答:

共同点

  • wait() ,wait(long) 和 sleep(long) 的效果都是让当前线程暂时放弃 CPU 的使用权,进入阻塞状态

不同点

  • 方法归属不同

    • sleep(long) 是 Thread 的静态方法

    • 而 wait(),wait(long) 都是 Object 的成员方法,每个对象都有

  • 醒来时机不同

    • 执行 sleep(long) 和 wait(long) 的线程都会在等待相应毫秒后醒来

    • wait(long) 和 wait() 还可以被 notify 唤醒,wait() 如果不唤醒就一直等下去

    • 它们都可以被打断唤醒

  • 锁特性不同(重点)

    • wait 方法的调用必须先获取 wait 对象的锁,而 sleep 则无此限制

    • wait 方法执行后会释放对象锁,允许其它线程获得该对象锁(我放弃 cpu,但你们还可以用)

    • 而 sleep 如果在 synchronized 代码块中执行,并不会释放对象锁(我放弃 cpu,你们也用不了)

代码示例:

 public class WaitSleepCase {
 ​
     static final Object LOCK = new Object();
 ​
     public static void main(String[] args) throws InterruptedException {
         sleeping();
     }
 ​
     private static void illegalWait() throws InterruptedException {
         LOCK.wait();
     }
 ​
     private static void waiting() throws InterruptedException {
         Thread t1 = new Thread(() -> {
             synchronized (LOCK) {
                 try {
                     get("t").debug("waiting...");
                     LOCK.wait(5000L);
                 } catch (InterruptedException e) {
                     get("t").debug("interrupted...");
                     e.printStackTrace();
                 }
             }
         }, "t1");
         t1.start();
 ​
         Thread.sleep(100);
         synchronized (LOCK) {
             main.debug("other...");
         }
 ​
     }
 ​
     private static void sleeping() throws InterruptedException {
         Thread t1 = new Thread(() -> {
             synchronized (LOCK) {
                 try {
                     get("t").debug("sleeping...");
                     Thread.sleep(5000L);
                 } catch (InterruptedException e) {
                     get("t").debug("interrupted...");
                     e.printStackTrace();
                 }
             }
         }, "t1");
         t1.start();
 ​
         Thread.sleep(100);
         synchronized (LOCK) {
             main.debug("other...");
         }
     }
 }

八、如何停止一个正在运行的线程?

参考回答:

有三种方式可以停止线程

  • 使用退出标志,使线程正常退出,也就是当run方法完成后线程终止

  • 使用stop方法强行终止(不推荐,方法已作废)

  • 使用interrupt方法中断线程

代码参考如下:

使用退出标志,使线程正常退出

 public class MyInterrupt1 extends Thread {
 ​
     volatile boolean flag = false ;     // 线程执行的退出标记
 ​
     @Override
     public void run() {
         while(!flag) {
             System.out.println("MyThread...run...");
             try {
                 Thread.sleep(3000);
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
         }
     }
 ​
     public static void main(String[] args) throws InterruptedException {
 ​
         // 创建MyThread对象
         MyInterrupt1 t1 = new MyInterrupt1() ;
         t1.start();
 ​
         // 主线程休眠6秒
         Thread.sleep(6000);
 ​
         // 更改标记为true
         t1.flag = true ;
 ​
     }
 }

使用stop方法强行终止

 public class MyInterrupt2 extends Thread {
 ​
     volatile boolean flag = false ;     // 线程执行的退出标记
 ​
     @Override
     public void run() {
         while(!flag) {
             System.out.println("MyThread...run...");
             try {
                 Thread.sleep(3000);
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
         }
     }
 ​
     public static void main(String[] args) throws InterruptedException {
 ​
         // 创建MyThread对象
         MyInterrupt2 t1 = new MyInterrupt2() ;
         t1.start();
 ​
         // 主线程休眠2秒
         Thread.sleep(6000);
 ​
         // 调用stop方法
         t1.stop();
 ​
     }
 }

使用interrupt方法中断线程

 package com.itheima.basic;
 ​
 public class MyInterrupt3 {
 ​
     public static void main(String[] args) throws InterruptedException {
 ​
         //1.打断阻塞的线程
         /*Thread t1 = new Thread(()->{
             System.out.println("t1 正在运行...");
             try {
                 Thread.sleep(5000);
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
         }, "t1");
         t1.start();
         Thread.sleep(500);
         t1.interrupt();
         System.out.println(t1.isInterrupted());*/
 ​
 ​
         //2.打断正常的线程
         Thread t2 = new Thread(()->{
             while(true) {
                 Thread current = Thread.currentThread();
                 boolean interrupted = current.isInterrupted();
                 if(interrupted) {
                     System.out.println("打断状态:"+interrupted);
                     break;
                 }
             }
         }, "t2");
         t2.start();
         Thread.sleep(500);
 //        t2.interrupt();
 ​
     }
 }


九、守护线程

1.概况

在Java中,线程分为两种主要类型:用户线程和守护线程(Daemon Thread)。

  1. 用户线程(User Thread):

    • 用户线程是程序的主要执行线程,当用户线程存在时,JVM 不会退出。主线程是一个典型的用户线程。

  2. 守护线程(Daemon Thread):

    • 守护线程是一种在后台提供服务的线程,它的存在并不阻止程序的终止。当所有的用户线程结束时,守护线程会被自动终止,即它的生命周期会随着用户线程的结束而结束。

    • 守护线程通常被用于执行一些辅助性质的任务,例如垃圾回收、内存管理等。JVM 中的垃圾回收器就是一个典型的守护线程。

2.用处

在Java中,通过setDaemon(boolean on)方法可以将一个线程设置为守护线程。例如:

A和B之间有一条网络连接,可以用守护线程来进行发送心跳,一旦A和B连接断开,非守护线程就结束了,守护线程(也就是心跳没有必要再发送了)也刚好断开。

public static void main(String[] args) {

    Thread t = new Thread(() -> {
        Thread innerThread = new Thread(() -> {
            try {
                while (true) {
                    System.out.println("Do some thing for health check.");
                    Thread.sleep(1_000);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

      //  innerThread.setDaemon(true);
        innerThread.start();

        try {
            Thread.sleep(1_000);
            System.out.println("T thread finish done.");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    });
    //t.setDaemon(true);
    t.start();
}

/*
设置该线程为守护线程必须在启动它之前。如果t.start()之后,再t.setDaemon(true);
会抛出IllegalThreadStateException
*/

输出结果:

Do some thing for health check.
Do some thing for health check.
T thread finish done. //此时main线程已经结束,但是由于innerThread还在发送心跳,应用不会关闭
Do some thing for health check.
Do some thing for health check.
Do some thing for health check.
Do some thing for health check.

3.补充

当JVM启动后,实际有多个线程,但是至少有一个非守护线程(比如main线程)

  • Finalizer:GC守护线程

  • RMI:Java自带的远程方法调用(秋招面试,有个面试官问过)

  • Monitor :是一个守护线程,负责监听一些操作,也在main线程组中

  • 其它:我用的是IDEA,其它的应该是IDEA的线程,比如鼠标监听啥的。