• Java开发之多线程包含代码调试【面试篇 持续更新】


    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


    提示:以下是本篇文章正文内容,下面案例可供参考

    一、多线程知识分布

    在这里插入图片描述

    二、线程的基础知识

    1. 线程与进程的区别

    程序由指令数据组成,但这些指令要运行,数据要读写,就必须将指令加载至CPU,数据加载至内存。在指令运行过程中还需要用到磁盘、网络等设备。进程就是用来加载指令、管理内存、管理IO的。

    当一个程序被运行,从磁盘加载这个程序的代码至内存,这时就开启了一个进程。

    在这里插入图片描述

    一个线程就是一个指令流,将指令流中的一条条指令以一定的顺序交给CPU执行
    一个进程之内可以分为一到多个线程。

    在这里插入图片描述

    二者对比

    1. 进程是正在运行程序的实例(比如浏览器、txt文档、ppt等),进程中包含了线程,每个线程执行不同的任务;
    2. 不同的进程使用不同的内存空间,在当前进程下的所有线程可以共享内存空间;(比如打开txt和idea是占用不同的内存空间)
    3. 线程更轻量,线程上下文切换成本一般上要比进程上下文切换低(上下文切换指的是从一个线程切换到另一个线程);

    2. 并行和并发有什么区别

    ① 单核CPU

    1. 单个CPU线程实际上还是串行执行的
    2. 操作系统中有一个组件叫做任务调度器,将cpu的时间片(windows下时间片最小约为15毫秒)分给不同的程序使用,只是由于cpu在线程间(时间片很短)的切换非常快,人类感觉是同时运行的。
    3. 微观是串行,宏观并行
    4. 一般会将这种线程轮流使用CPU的做法称为并发(concurrent)
      在这里插入图片描述

    ② 多核CPU

    每个核(core)都可以调度运行线程,这时候线程可以是并行的。
    在这里插入图片描述

    ③ 总结(现在都是多核CPU,在多核CPU下)

    1. 并发(concurrent)是同一时间应对(dealing with)多件事情的能力,多个线程轮流使用一个或多个
    2. 并行(parallel)是同一时间动手做(doing)多件事情的能力(4核CPU同时执行4个线程)
    3. 举例:
      在这里插入图片描述

    3. 创建线程的方式有哪些

    共有四种方式可以创建线程,分别是:

    1. 继承Thread类
    2. 实现runnable接口
    3. 实现Callable接口
    4. 线程池创建线程

    ① 继承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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    在这里插入图片描述

    ② 实现runnable接口

    public class MyRunnable implements Runnable{
        @Override
        public void run() {
            System.out.println("MyThread  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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    在这里插入图片描述

    ③ 实现Callable接口

    public class MyCallable implements Callable<String> {
        @Override
        public String call() throws Exception {
            System.out.println(Thread.currentThread().getName());
            return "ok";
        }
    
        public static void main(String[] args) throws ExecutionException, InterruptedException {
            // 创建MyCallable对象
            MyCallable mc = new MyCallable();
            // 创建FutureTask
            FutureTask<String> ft = new FutureTask<String>(mc);
            // 创建Thread对象
            Thread t1 = new Thread(ft);
            Thread t2 = new Thread(ft);
            // 调用start方法启动线程
            t1.start();
            // 调用ft的get方法获取执行结果
            String res = ft.get();
            // 输出
            System.out.println(res);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    在这里插入图片描述

    ④ 线程池创建线程

    public class MyExecutors implements Runnable{
    
        @Override
        public void run() {
            System.out.println("MyThread  run  ...");
        }
    
        public static void main(String[] args) {
            // 创建固定大小的线程池对象
            ExecutorService threadPool = Executors.newFixedThreadPool(3);
            // 提交任务,执行run
            threadPool.submit(new MyExecutors());
    
            // 关闭线程池
            threadPool.shutdown();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    在这里插入图片描述

    ⑤ runable和callable有什么区别

    1. Runnable接口run方法没有返回值
    2. Callable接口call方法有返回值,是个泛型,和Future、FutureTask配合可以用来获取异步执行的结果
    3. Callable接口的call()方法允许抛出异常,而Runnable接口的run()方法的异常只能在内部消化,不能继续上抛

    ⑥ run()和start()有什么区别

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

    在这里插入图片描述

    ⑦ 总结

    1. 创建线程的方式有哪些?
      继承Thread类
      实现runnable接口
      实现callable接口
      线程池创建线程(项目中使用方式)
    2. runnable和callable有什么区别
      Runnable接口run方法没有返回值
      Callable接口call方法有返回值,需要FutureTask获取结果
      Callable接口的call()方法允许抛出异常,而Runnable接口的run()方法的异常只能在内部消化,不能继续上抛
    3. run()和start()有什么区别?
      start():用来启动线程,通过该线程调用run方法执行run方法中所定义的逻辑代码。start方法只能被调用一次。
      run():封装了要被线程执行的代码,可以被调用多次。

    4. 线程包含哪些状态,状态直接如何变化的

    线程的状态可以参考JDK中的Thread类中的枚举State
    在这里插入图片描述
    在idea中打开Thread类找到State
    在这里插入图片描述

    ① 状态的变化过程

    在这里插入图片描述

    ② 总结

    1. 线程包括哪些状态
      新建(NEW)
      可运行(RUNNABLE)
      阻塞(BLOCKED)
      等待( WAITING )
      时间等待(TIMED_WALTING)
      终止(TERMINATED)
    2. 线程状态之间是如何变化的
      a. 创建线程对象是新建状态
      b. 调用了start()方法转变为可执行状态
      c. 线程获取到了CPU的执行权,执行结束是终止状态
      d. 在可执行状态的过程中,如果没有获取CPU的执行权,可能会切换其他状态

    如果没有获取锁(synchronized或lock)进入阻塞状态,获得锁再切换为可执行状态
    如果线程调用了wait()方法进入等待状态,其他线程调用notify()唤醒后可切换为可执行状态
    如果线程调用了sleep(50)方法,进入计时等待状态,到时间后可切换为可执行状态

    在这里插入图片描述

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

    ① join():等待线程运行结束

    阻塞调用此方法的线程进入timed_waiting直到线程t执行完成后,此线程再继续执行

    public class ThreadDemo01 {
        public static void main(String[] args) {
            // 创建线程:Lambda表达式定义了线程的执行内容
            Thread t1 = new Thread(()->{
                System.out.println("t1");
            });
    
            Thread t2 = new Thread(()->{
                try {
                    t1.join(); // 加入线程t1,只有t1线程执行完毕之后,再次执行该线程
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println("t2");
            });
    
            Thread t3 = new Thread(()->{
                try {
                    t2.join(); // 加入线程t1,只有t1线程执行完毕之后,再次执行该线程
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println("t3");
            });
    
            // 启动线程
            t3.start();
            t2.start();
            t1.start();
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    运行结果:
    在这里插入图片描述

    测试不加join的结果
    在这里插入图片描述

    6. 有什么区别notify()和notifyAll()

    1. notifyAll:唤醒所有wait的线程
    2. notify:只随机唤醒一个wait线程

    演示Java中多线程编程中的等待和唤醒机制,代码如下:

    public class NotifyThread {
    
        static Object lock = new Object();
    
        public static void main(String[] args) throws InterruptedException {
            // 创建t1线程对象,并通过构造函数的参数传递了一个Runnable对象(Lambda表达式),还给线程指定了一个名称 "t1"
            Thread t1 = new Thread(() -> {
                synchronized (lock) {  // 加锁同步块,使用 lock 对象作为锁。只有一个线程可以进入这个同步块,其他线程必须等待直到锁被释放。
                    System.out.println(Thread.currentThread().getName() + "...线程等待...");
                    try {
                        lock.wait(); // 等待操作,它让当前线程进入等待状态,并释放 lock 锁。线程将一直等待,直到另一个线程调用相同 lock 对象的 notify() 或 notifyAll() 方法,唤醒它。
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    System.out.println(Thread.currentThread().getName() + "...线程被唤醒了...");
                }
            }, "t1");
    
            Thread t2 = new Thread(() -> {
                synchronized (lock) {
                    System.out.println(Thread.currentThread().getName() + "...线程等待...");
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    System.out.println(Thread.currentThread().getName() + "...线程被唤醒了...");
                }
            }, "t2");
    
            t1.start();
            t2.start();
    
            Thread.sleep(2000);
    
            synchronized (lock) {
                lock.notify();
            }
    
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    notify() 运行结果:
    在这里插入图片描述

    notifyAll() 运行结果:
    在这里插入图片描述

    7. java中wait方法和sleep方法有什么不同

    ① 异同点

    共同点:

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

    不同点:

    1. 方法归属不同
      sleep(long)是 Thread的静态方法;
      而wait(), wait(long)都是Object的成员方法,每个对象都有;
    2. 醒来时机不同
      执行sleep(long)和wait(long)的线程都会在等待相应毫秒后醒来;
      wait(long)和 wait()还可以被notify唤醒,wait()如果不唤醒就一直等下去;
      它们都可以被打断唤醒;
    3. 锁特性不同(重点)
      wait方法 的调用必须先获取 wait对象的锁,而sleep 则无此限制;
      wait方法执行后会释放对象锁,允许其它线程获得该对象锁(我放弃cpu,但你们还可以用);
      而sleep如果在synchronized 代码块中执行,并不会释放对象锁(我放弃cpu,你们也用不了);

    ② 代码演示

    wait方法 的调用必须先获取 wait对象的锁,配合synchronized使用,运行情况如下:
    在这里插入图片描述
    如果去掉了synchronized就会报错,运行如下:
    在这里插入图片描述

  • 相关阅读:
    【DTEmpower案例操作教程】智能数据挖掘
    【信息论与编码基础】第1章 绪论
    Springboot集成shiro框架:
    软件测试方法 -- 等价类边界值
    SpringCloud-04-Feign
    DB2 HADR 配置 centos 7配置 DB2 HADR 版本 11.1,【亲测可用】全网最细
    gif表情制作一键轻松搞定,教你自制gif表情
    分类预测 | Matlab实现PSO-GRU-Attention粒子群算法优化门控循环单元融合注意力机制多特征分类预测
    软件架构风格-BS三层架构
    Spring Cloud Gateway整合Nacos实现服务路由及集群负载均衡
  • 原文地址:https://blog.csdn.net/qq_48592827/article/details/132870125