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

在这里插入图片描述

② 实现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();

    }
}

在这里插入图片描述

③ 实现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);
    }
}

在这里插入图片描述

④ 线程池创建线程

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

在这里插入图片描述

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

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

测试不加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();
        }

    }
}

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就会报错,运行如下:
在这里插入图片描述

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

PRINT!

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值