线程的基础知识

线程和进程的区别?

  • 进程是正在运行程序的实例,进程中包含了线程,每个线程执行不同的任务
  • 不同的进程使用不同的内存空间,在当前进程下的所有线程可以共享内存空间
  • 线程更轻量,线程上下文切换成本一般上要比进程上下文切换低(上下文切换指的是从一个线程切换到另一个线程)

并行和并发有什么区别?

  • 并发:“同一段时间内”处理多个任务 —— 交替推进(看起来同时)。
  • 并行:**“同一时刻”处理多个任务 —— **真正同时执行(同时发生)。

记忆:并发 = 轮流做并行 = 一起做

回答:

现在都是多核CPU,在多核CPU下

并发是多个任务在同一时间段内交替执行,多个线程轮流使用一个或多个CPU,强调任务切换与协调;

并行是多个任务在同一时刻同时执行,依赖多核/多处理器资源(4核CPU同时执行4个线程)。

创建线程的四种方式

回答:在java中一共有四种常见的创建方式,分别是:继承Thread类、实现runnable接口、实现Callable接口、线程池创建线程。通常情况下,我们项目中都会采用线程池的方式创建线程。

  1. 继承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();
 
     }
 }
  1. 实现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();
 
     }
 
 }
  1. 实现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);
 
     }
 
 }
  1. 线程池创建线程(项目常用)
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里面定义了6中线程的状态分别是:新建、可运行、终结、阻塞、等待和有时限等待六种。
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;
     }

状态之间是如何变化的:

在这里插入图片描述

回答:

在这里插入图片描述

详细解释:

  • 新建
    • 当一个线程对象被创建,但还未调用 start 方法时处于新建状态
    • 此时未与操作系统底层线程关联
  • 可运行
    • 调用了 start 方法,就会由新建进入可运行
    • 此时与底层线程关联,由操作系统调度执行
  • 终结
    • 线程内代码已经执行完毕,由可运行进入终结
    • 此时会取消与底层线程关联
  • 阻塞
    • 当获取锁失败后,由可运行进入 Monitor 的阻塞队列阻塞,此时不占用 cpu 时间
    • 当持锁线程释放锁时,会按照一定规则唤醒阻塞队列中的阻塞线程,唤醒后的线程进入可运行状态
  • 等待
    • 当获取锁成功后,但由于条件不满足,调用了 wait() 方法,此时从可运行状态释放锁进入 Monitor 等待集合等待,同样不占用 cpu 时间
    • 当其它持锁线程调用 notify() 或 notifyAll() 方法,会按照一定规则唤醒等待集合中的等待线程,恢复为可运行状态
  • 有时限等待
    • 当获取锁成功后,但由于条件不满足,调用了 wait(long) 方法,此时从可运行状态释放锁进入 Monitor 等待集合进行有时限等待,同样不占用 cpu 时间
    • 当其它持锁线程调用 notify() 或 notifyAll() 方法,会按照一定规则唤醒等待集合中的有时限等待线程,恢复为可运行状态,并重新去竞争锁
    • 如果等待超时,也会从有时限等待状态恢复为可运行状态,并重新去竞争锁
    • 还有一种情况是调用 sleep(long) 方法也会从可运行状态进入有时限等待状态,但与 Monitor 无关,不需要主动唤醒,超时时间到自然恢复为可运行状态

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

可以用线程类的join()方法在一个线程中启动另一个线程,另外一个线程完成该线程继续执行。

例如:

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();
 
     }
 
 }

回答:

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

使用join方法,T3调用T2,T2调用T1,这样就能确保T1就会先完成而T3最后完成。

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

notifyAll:唤醒所有wait的线程。

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

在 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方法中断线程
  1. 使用退出标志,使线程正常退出
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 ;

    }
}
  1. 使用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();

    }
}
  1. 使用interrupt方法中断线程
  • 打断当前阻塞的线程(sleep,wait,join)的线程,线程会抛出InterruptedException异常。
public class MyInterrupt3 {

    public static void main(String[] args) throws InterruptedException {

        //1.打断阻塞的线程
        Thread t1 = new Thread(()->{
            System.out.println("t1 正在运行...");
            try {
                Thread.sleep(5000);//阻塞线程,wait,join也是一样
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "t1");
        t1.start();
        Thread.sleep(500); 
        t1.interrupt();
       System.out.println(t1.isInterrupted());
    }
}

在这里插入图片描述

  • 打断正常的线程,可以根据打断状态来标记是否退出线程。
public class MyInterrupt3 {

    public static void main(String[] args) throws InterruptedException {

    //2.打断正常的线程
    Thread t2 = new Thread(()->{
        while(true) {
            Thread current = Thread.currentThread();
            boolean interrupted = current.isInterrupted(); //默认是false
            if(interrupted) {
                System.out.println("打断状态:"+interrupted);
                break;
            }
        }
    }, "t2");
    t2.start();
    Thread.sleep(500);
    t2.interrupt(); //一调用就改为true
}
}

在这里插入图片描述

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐