多线程的知识点

1. 基础概念

1.1. 什么是线程?线程和进程的区别是什么?

线程:线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中

进程:整个程序是一个进程,一个进程可以包含多个线程,这些线程可以并发执行,共享进程的资源

1.2. Java中如何创建线程?有几种方式?

三种方式:

1.2.1. 继承Thread类的方式:
  1. 继承Thread类
  2. 重写run方法
  3. 创建线程对象
  4. 调用start()方法启动。
1.2.2. 通过实现Runable来创建线程
  1. 定义一个线程任务类MyRunnable实现Runnable接口,重写run()方法
  2. 创建MyRunnable对象
  3. 把MyRunnable任务对象交给Thread线程对象处理。
  4. 调用线程对象的start()方法启动线程
1.2.3. 通过FutureTask来创建线程
  1. 创建任务对象
    1. 定义一个类实现Callable接口,重写call方法,封装要做的事情,和要返回的数据。
    2. 把Callable类型的对象封装成FutureTask(管理多线程的返回结果)。
  1. 把FutureTask对象交给Thread对象。
  2. 调用Thread对象的start方法启动线程。
  3. 线程执行完毕后、通过FutureTask对象的的get方法去获取线程任务执行的结果

1.3. 三种创建方式对比:

1.4. 什么是守护线程(Daemon Thread)?如何设置守护线程?

守护线程:当核心线程都结束之后,守护线程也会陆续关闭。

守护线程:当非守护线程结束后,守护线程就没有继续执行下去的必要了,就会陆续关闭,不管执行完没有,比如垃圾回收、性能监控

设置守护线程:在 Java 中,可以通过Thread类的setDaemon(true)方法将一个线程设置为守护线

1.5. 什么是线程的生命周期?线程有哪些状态?

  • 线程的生命周期:线程的生命周期指的是线程从创建到销毁的整个过程,期间会经历不同的状态变化。
  • 线程的六种状态:

1.6. start()方法和run()方法的区别是什么?

start()方法用于启动一个新的线程,它会在新的线程中调用run()方法,实现多线程并发执行

run()方法只是一个普通的方法,直接调用run()方法并不会启动新的线程,而是在当前线程中顺序执行run()方法中的代码

2. 线程同步与锁

2.1. 什么是线程安全?如何实现线程安全?

线程安全就是在多线程环境下,程序的执行结果和单线程环境下的执行结果一致,不会出现数据不一致或其他异常情况。

1.加锁:如synchronized关键字,保证同一时刻只有一个线程可以访问共享资源

2.使用不可变对象,例如String类

2.2. synchronized关键字的作用是什么?如何使用?

  • 作用synchronized关键字用于实现线程同步,它可以保证在同一时刻只有一个线程能够访问被synchronized修饰的代码块或方法,从而避免多个线程同时访问共享资源导致的数据不一致问题。
  • 使用:synchronized(锁对象)

2.3. synchronized修饰静态方法和实例方法的区别是什么?

锁的对象不同:修饰实例方法时,锁的对象是当前实例对象

修饰静态方法时,锁的对象是当前类的Class对象

2.4. 什么是死锁?如何避免死锁?

死锁:死锁是指两个或多个线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去

破坏互斥条件:无法被破坏,因为锁就是通过互斥来解决线程安全问题

破坏请求与保持条件:一次申请所有需要的资源

破坏不可剥夺条件:如果线程已占用部分资源,且他还要申请其他资源,如果申请不到,就主动释放它自己占有的资源

破坏循环等待条件:按照顺序申请资源,释放资源则逆序

2.5. 什么是volatile关键字?它的作用是什么?

  • volatile 关键字volatile用于修饰变量,保证变量的可见性和禁止指令重排序。

保证可见性:当一个变量被声明为volatile时,它会保证对该变量的写操作会立即刷新到主内存中,而读操作会从主内存中读取最新的值。这样可以确保多个线程对该变量的操作是可见的,避免出现数据不一致的问题。

禁止指令重排序:编译器和处理器为了提高性能,可能会对指令进行重排序。volatile关键字可以禁止这种重排序,保证代码的执行顺序和编写顺序一致。

2.6. volatilesynchronized的区别是什么?

每个线程都会有自己的工作空间,每个线程只能访问各自的工作空间,而一些共享变量会被加载到每个线程的工作空间中。

  • 作用范围不同
    • volatile只能修饰变量。
    • synchronized关键字可以修饰方法和代码块
  • 同步机制不同
    • volatile是一种轻量级的同步机制,它不会阻塞线程
    • synchronized是一种重量级的同步机制,它会阻塞线程
  • 使用场景不同
    • volatile适用于一个变量被多个线程读取,而只有一个线程写入的场景,例如状态标志位。
    • synchronized适用于多个线程对共享资源进行读写操作的场景,需要保证数据的一致性。

2.7. 什么是ReentrantLock?它与synchronized的区别是什么?

ReentrantLock是一个可重入锁,它提供了与synchronized关键字类似的同步功能,但更加灵活。

synchronized是隐式锁:当进入同步代码块或方法时自动获取锁,退出时自动释放锁。

ReentrantLock是显式锁:需要手动调用lock()方法获取锁,调用unlock()方法释放锁,通常需要在finally块中释放锁,以确保锁一定会被释放。

ReentrantLock提供了更多的灵活性,例如可以实现公平锁(ReentrantLock(true)),可以尝试获取锁

2.8. 什么是wait()notify()notifyAll()方法?它们的作用是什么?

wait () 方法:用于使当前线程进入等待状态,当前线程会释放对象的锁。

notify () 方法:用于唤醒在此对象监视器上等待的单个线程。如果有多个线程在等待,则随机选择一个线程唤醒。

notifyAll () 方法notifyAll()方法用于唤醒在此对象监视器上等待的所有线程。

2.9. 为什么wait()notify()方法必须在synchronized块中调用?

因为wait()方法在调用时会释放对象的锁,而notify()notifyAll()方法在调用时需要获取对象的锁。如果不在synchronized块中调用,就无法保证线程安全,可能会出现竞态条件

2.10. 什么是线程间的通信?如何实现?

线程间的通信:线程间的通信是指多个线程之间通过某种机制进行信息交换和协调

使用wait()notify()notifyAll()方法

3. 3. 线程池

3.1. 什么是线程池?为什么要使用线程池?

一个装线程的池子,池子中的线程可以复用, 当任务执行完,线程不会销毁,而是归还给池子中。

1、可以降低资源的消耗

2、提高响应速度

3、便于线程管理

3.2. Java中如何创建线程池?有哪些常见的线程池实现?

创建方式:

在 Java 中,可以使用 Executors 工具类或者直接使用 ThreadPoolExecutor 类来创建线程池。

常见线程池实现:

  • FixedThreadPool:固定大小的线程池,线程数量固定。
  • CachedThreadPool:可缓存的线程池,线程数量不固定,会根据任务数量动态创建和销毁线程。当有新任务提交时,如果线程池中有空闲线程,则执行任务;如果没有空闲线程,则创建新线程执行任务。当线程空闲时间超过一定时间(默认 60 秒),会被销毁。
  • SingleThreadExecutor:单线程的线程池,线程池中只有一个线程,所有任务按照顺序依次执行。
  • ScheduledThreadPool:定时任务线程池

3.3. Executors类提供了哪些创建线程池的方法?

  • newFixedThreadPool(int nThreads):创建一个固定大小的线程池
  • newCachedThreadPool():创建一个可缓存的线程池。
  • newSingleThreadExecutor():创建一个单线程的线程池。
  • newScheduledThreadPool(int corePoolSize):创建一个定时任务线程池

3.4. ThreadPoolExecutor的核心参数有哪些?它们的作用是什么?

6大核心参数:

核心线程数量:线程池会一直保持这些线程的存活,即使它们处于空闲状态

最大线程的个数:当任务数量超过核心线程数量且队列已满时,线程池会创建新线程,直到线程数量达到最大线程数量。

临时线程存活时间:当线程空闲时间超过该时间时,会被销毁

存活时间的单位:

任务队列:用于存储等待执行的任务

线程工厂:用于创建线程

拒绝策略:当任务队列已满且线程数量达到最大线程数量时,新提交的任务会触发拒绝策略。

3.5. 什么是线程池的饱和策略(拒绝策略)?有哪些常见的拒绝策略?

是指当任务队列已满且线程数量达到最大线程数量时,新提交的任务的处理方式

常见拒绝策略:

  • AbortPolicy:默认的拒绝策略,直接抛出 RejectedExecutionException 异常,阻止系统正常运行。
  • CallerRunsPolicy:由调用线程(提交任务的线程)来执行该任务。
  • DiscardPolicy:直接丢弃新提交的任务,不做任何处理。
  • DiscardOldestPolicy:丢弃任务队列中最老的任务,然后尝试重新提交新任务。

3.6. 如何关闭线程池?shutdown()shutdownNow()的区别是什么?

关闭线程池的方法

可以使用 shutdown() 或 shutdownNow() 方法关闭线程池。

区别:

  • shutdown():该方法会平滑地关闭线程池,不再接受新任务,但会继续执行队列中已有的任务,直到所有任务执行完毕。调用该方法后,线程池的状态会变为 SHUTDOWN
  • shutdownNow():该方法会立即关闭线程池,尝试停止所有正在执行的任务,并返回队列中未执行的任务列表。调用该方法后,线程池的状态会变为 STOP

3.7. 如何监控线程池的状态?

可以通过 ThreadPoolExecutor 提供的一些方法来监控线程池的状态:

  • getTaskCount():返回线程池已接收到的任务总数。
  • getCompletedTaskCount():返回线程池已完成的任务数量。
  • getActiveCount():返回线程池中正在执行任务的线程数量。
  • getPoolSize():返回线程池当前的线程数量。
  • getQueue().size():返回任务队列中等待执行的任务数量。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值