【JUC面试篇】Java并发编程高频八股——线程与多线程

目录

1. 什么是进程和线程?有什么区别和联系?

2. Java的线程和操作系统的线程有什么区别?

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

4. 如何启动和停止线程?

5. Java线程的状态模型(有哪些状态)?

6. 调用 interrupt 是如何让线程抛出异常的?

7. 什么是线程上下文切换?

8. sleep 和 wait的区别是什么?

9. sleep会释放cpu吗?

10. 可以直接调用 Thread 类的 run 方法吗?

11. blocked和waiting有什么区别?

12. wait 状态下的线程如何进行恢复到 running 状态?

13. notify 和 notifyAll 的区别?

14. 并发和并行的区别?

15. 同步和异步的区别?

16. 单核 CPU 支持 Java 多线程吗?

17. 单核 CPU 上运行多个线程效率一定会高吗?

18. 不同的线程之间如何通信?

19. 线程间通信方式有哪些?

20. 使用多线程要注意哪些问题?

21. 什么是线程死锁?


1. 什么是进程和线程?有什么区别和联系?

  • 进程:是操作系统进行资源分配和调度的基本单位。每个进程拥有独立的内存空间。进程间相互隔离,一个进程崩溃通常不影响其他进程。

  • 线程:是进程内的执行单元是CPU调度的最小单位。同一进程的多个线程共享进程的内存空间和资源(如堆、全局变量),但每个线程有独立的栈和程序计数器

        线程是进程划分成的更小的运行单位。线程和进程最大的不同在于基本上各进程是独立的,而各线程则不一定,因为同一进程中的线程极有可能会相互影响。线程执行开销小,但不利于资源的管理和保护;而进程正相反。

2. Java的线程和操作系统的线程有什么区别?

现在的 Java 线程的本质其实就是操作系统的线程

Java 线程采用的是一对一的线程模型,也就是一个 Java 线程对应一个系统内核线程。

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

1. 继承Thread类

这是最直接的一种方式,用户自定义类继承java.lang.Thread类,重写其run()方法,run()方法中定义了线程执行的具体任务。创建该类的实例后,通过调用start()方法启动线程。

class MyThread extends Thread {@Overridepublic void run() {// 线程执行的代码}
}public static void main(String[] args) {MyThread t = new MyThread();t.start();
}

采用继承Thread类方式

  • 优点:编写简单,如果需要访问当前线程,无需使用Thread.currentThread()方法,直接使用this,即可获得当前线程
  • 缺点:因为线程类已经继承了Thread类,所以不能再继承其他的父类

2. 实现Runnable接口

如果一个类已经继承了其他类,就不能再继承Thread类,此时可以实现java.lang.Runnable接口。实现 Runnable接口需要重写run()方法,然后将此Runnable对象作为参数传递给 Thread类的构造器,创建Thread对象后调用其start()方法启动线程。

class MyRunnable implements Runnable {@Overridepublic void run() {// 线程执行的代码}
}public static void main(String[] args) {Thread t = new Thread(new MyRunnable());t.start();
}

采用实现 Runnable接口方式:

  • 优点:线程类只是实现了Runable接口,还可以继承其他的类。在这种方式下,可以多个线程共享同一个目标对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。
  • 缺点:编程稍微复杂,如果需要访问当前线程,必须使用Thread.currentThread( )方法。

 

3. 实现Callable接口与FutureTask

java.util.concurrent.Callable接口类似于Runnable,但Callablecall()方法可以有返回值并且可以抛出异常。要执行Callable任务,需将它包装进一个FutureTask,因为Thread类的构造器只接受Runnable参数,而FutureTask实现了Runnable接口。

class MyCallable implements Callable<Integer> {@Overridepublic Integer call() throws Exception {// 线程执行的代码,这里返回一个整型结果return 1;}
}public static void main(String[] args) {MyCallable task = new MyCallable();FutureTask<Integer> futureTask = new FutureTask<>(task);Thread t = new Thread(futureTask);t.start();try {Integer result = futureTask.get(); // 获取线程执行结果System.out.println("Result: " + result);} catch (InterruptedException | ExecutionException e) {e.printStackTrace();}
}

采用实现Callable接口方式:

  • 缺点:编程稍微复杂,如果需要访问当前线程,必须调用Thread.currentThread()方法。
  • 优点:线程只是实现Runnable或实现Callable接口,还可以继承其他类。这种方式下,多个线程可以共享一个 target 对象,非常适合多线程处理同一份资源的情形。

 

4. 使用线程池(Executor框架)

从Java 5开始引入的java.util.concurrent.ExecutorService和相关类提供了线程池的支持,这是一种更高效的线程管理方式,避免了频繁创建和销毁线程的开销。可以通过Executors类的静态方法创建不同类型的线程池。

class Task implements Runnable {@Overridepublic void run() {// 线程执行的代码}
}public static void main(String[] args) {ExecutorService executor = Executors.newFixedThreadPool(10); // 创建固定大小的线程池for (int i = 0; i < 100; i++) {executor.submit(new Task()); // 提交任务到线程池执行}executor.shutdown(); // 关闭线程池
}

采用线程池方式:

  • 缺点:线程池增加了程序的复杂度,特别是当涉及线程池参数调整和故障排查时。错误的配置可能导致死锁、资源耗尽等问题,这些问题的诊断和修复可能较为复杂。
  • 优点:线程池可以重用预先创建的线程,避免了线程创建和销毁的开销,显著提高了程序的性能。对于需要快速响应的并发请求,线程池可以迅速提供线程来处理任务,减少等待时间。并且,线程池能够有效控制运行的线程数量,防止因创建过多线程导致的系统资源耗尽(如内存溢出)。通过合理配置线程池大小,可以最大化CPU利用率和系统吞吐量。

4. 如何启动和停止线程?

启动线程通过 Thread 类的 start()方法:

// 创建两个线程,用 start 启动线程
MyThread myThread1 = new MyThread();
MyThread myThread2 = new MyThread();
myThread1.start();
myThread2.start();

停止线程的方法:

  • 异常法停止:线程调用interrupt()方法后,在线程的run方法中判断当前对象的interrupted()状态,如果是中断状态则抛出异常,达到中断线程的效果。
  • 在沉睡中停止:先将线程sleep,然后调用interrupt标记中断状态,interrupt会将阻塞状态的线程中断。会抛出中断异常,达到停止线程的效果
  • stop()暴力停止:线程调用stop()方法会被暴力停止,方法已弃用,该方法会有不好的后果:强制让线程停止有可能使一些请理性的工作得不到完成。
  • 使用return停止线程:调用interrupt标记为中断状态后,在run方法中判断当前线程状态,如果为中断状态则return,能达到停止线程的效果。

5. Java线程的状态模型(有哪些状态)?

Java 线程在运行的生命周期中的指定时刻只可能处于下面 6 种不同状态的其中一个状态:

线程状态解释
NEW尚未启动的线程状态,即线程创建,还未调用start方法
RUNNABLE就绪状态(调用start,等待调度)+正在运行
BLOCKED等待监视器锁时,陷入阻塞状态
WAITING等待状态的线程正在等待另一线程执行特定的操作(如notify)
TIMED_WAITING具有指定等待时间的等待状态
TERMINATED线程完成执行,终止状态

可以调用线程 Thread中的getState()方法获取当前线程的状态。

详细解释

NEW

NEW 是线程的初始状态。当通过new Thread()创建线程对象后,但尚未调用start()方法时,线程处于此状态。此时,线程尚未分配系统资源,仅作为Java对象存在。需要强调的是,调用start()方法会触发线程进入可运行状态。

RUNNABLE

RUNNABLE 表示线程已启动并准备好执行。调用start()方法后,线程进入此状态。它可能正在运行(占用CPU)或就绪等待CPU调度(例如,在操作系统的就绪队列中)。关键点在于,此状态涵盖了线程的实际执行和就绪等待,不区分两者。如果线程在等待I/O操作(如网络请求),它仍被视为RUNNABLE,因为底层操作系统可能处理了阻塞。

BLOCKED

BLOCKED 发生在线程尝试获取一个监视器锁(monitor lock)时,但该锁已被其他线程持有。典型场景是进入synchronized块或方法时遇到锁竞争。核心概念是线程在等待锁释放期间被阻塞,无法执行。一旦锁可用,线程会自动转换回RUNNABLE状态。注意,这与等待状态不同,BLOCKED是特定于锁的竞争。

WAITING

WAITING 是线程无限期等待另一个线程执行特定操作的状态。通过调用无参的Object.wait()Thread.join()LockSupport.park()方法进入此状态。关键特征是线程不会主动唤醒,必须依赖其他线程的通知(如notify()notifyAll())才能恢复。这常用于线程间协调,但需小心避免死锁。

TIMED_WAITING

TIMED_WAITING 类似于WAITING,但线程在指定时间内等待。通过调用带超时参数的方法进入,如Thread.sleep(long millis)Object.wait(long timeout)Thread.join(long millis)核心区别在于,线程会在超时后自动返回RUNNABLE状态,无需外部通知。这提供了更可控的等待机制,减少无限等待的风险。

TERMINATED

TERMINATED 是线程的终止状态。当线程的run()方法正常执行完毕或抛出未捕获异常时,进入此状态。关键点是线程资源已被释放,无法再重启(调用start()会抛出异常)。此时,线程对象可能仍被引用,但已无执行能力。

6. 调用 interrupt 是如何让线程抛出异常的?

每个线程都有一个与之关联的布尔属性来表示其中断状态,中断状态的初始值为false,当一个线程被其它线程调用 Thread.interrupt() 方法中断时,会根据实际情况做出响应。

  • 如果该线程正在执行低级别 的可中断方法(如 Thread.sleep()Thread.join() 或 Object.wait()),则会解除阻塞并抛出 InterruptedException 异常。
  • 否则 Thread.interrupt() 仅设置线程的中断状态,在该被中断的线程中稍后可通过轮询中断状态来决定是否要停止当前正在执行的任务。

7. 什么是线程上下文切换?

线程在执行过程中会有自己的运行条件和状态(也称上下文),比如上文所说到过的程序计数器,栈信息等。当出现如下情况的时候,线程会从占用 CPU 状态中退出。

  • 主动让出 CPU,比如调用了 sleep(), wait() 等。
  • 时间片用完,因为操作系统要防止一个线程或者进程长时间占用 CPU 导致其他线程或者进程饿死。
  • 调用了阻塞类型的系统中断,比如请求 IO,线程被阻塞。
  • 被终止或结束运行

这其中前三种都会发生线程切换,线程切换意味着需要保存当前线程的上下文,留待线程下次占用 CPU 的时候恢复现场。并加载下一个将要占用 CPU 的线程上下文。这就是所谓的 上下文切换

上下文切换是现代操作系统的基本功能,因其每次需要保存信息恢复信息,这将会占用 CPU,内存等系统资源进行处理,也就意味着效率会有一定损耗,如果频繁切换就会造成整体效率低下。

8. sleep 和 wait的区别是什么?

特性sleep()wait()
所属类Thread类(静态方法)Object类(实例方法)
锁释放
使用前提任意位置调用必须在同步块内(持有锁)
唤醒机制超时自动恢复需 notify()/notifyAll() 或超时
设计用途暂停线程执行,不涉及锁协作线程间协调,释放锁让其他线程工作
  • 所属分类的不同:sleep是 Thread类的静态方法,可以在任何地方直接通过 Thread.sleep()调用,无需依赖对象实例。wait是 Object类的实例方法,这意味着必须通过对象实例来调用。
  • 锁释放的情况: Thread.sleep()在调用时,线程会暂停执行指定的时间,但不会释放持有的对象锁。也就是说,在 sleep期间,其他线程无法获得该线程持有的锁。 Object.wait():调用该方法时,线程会释放持有的对象锁,进入等待状态,直到其他线程调用相同对象的 notify()或 notifyAll()方法唤醒它。
  • 使用条件:sleep可在任意位置调用,无需事先获取锁。wait必须在同步块或同步方法内调用(即线程需持有该对象的锁),否则抛出 IllegalMonitorStateException。
  • 唤醒机制:sleep休眠时间结束后,线程自动恢复到就绪状态,等待CPU调度。wait需要其他线程调用相同对象的 notify()或 notifyAll()方法才能被唤醒。notify()会随机唤醒一个在该对象上等待的线程,而 notifyAll()会唤醒所有在该对象上等待的线程。

9. sleep会释放cpu吗?

是的,调用 Thread.sleep() 时,线程会释放 CPU,但不会释放持有的锁。

当线程调用 sleep() 后,会主动让出 CPU 时间片,进入 TIMED_WAITING 状态。此时操作系统会触发调度,将 CPU 分配给其他处于就绪状态的线程。这样其他线程(无论是需要同一锁的线程还是不相关线程)便有机会执行。

sleep() 不会释放线程已持有的任何锁(如 synchronized 同步代码块或方法中获取的锁)。因此,如果有其他线程试图获取同一把锁,它们仍会被阻塞,直到原线程退出同步代码块。

10. 可以直接调用 Thread 类的 run 方法吗?

new 一个 Thread,线程进入了新建状态。调用 start()方法,会启动一个线程并使线程进入了就绪状态,当分配到时间片后就可以开始运行了。 start() 会执行线程的相应准备工作,然后自动执行 run() 方法的内容,这是真正的多线程工作。 但是,直接执行 run() 方法,会把 run() 方法当成一个 main 线程下的普通方法去执行,并不会在某个线程中执行它,所以这并不是多线程工作。

总结:调用 start() 方法方可启动线程并使线程进入就绪状态,直接执行 run() 方法的话不会以多线程的方式执行。

11. blocked和waiting有什么区别?

区别如下:

  • 触发条件:线程进入 BLOCKED 状态通常是因为试图获取一个对象的锁 (monitor lock) ,但该锁已经被另一个线程持有。这通常发生在尝试进入 synchronized 块或方法时,如果锁已被占用,则线程将被阻塞直到锁可用。线程进入 WAITING 状态是因为它正在等待另一个线程执行某些操作,例如调用 Object.wait()方法、 Thread.join()方法或 LockSupport.park()方法。在这种状态下,线程将不会消耗 CPU 资源,并且不会参与锁的竞争。

唤醒机制

当一个线程被阻塞等待锁时,一旦锁被释放,线程将有机会重新尝试获取锁。如果锁此时未被其他线程获取,那么线程可以从 BLOCKED 状态变为 RUNNABLE 状态。线程在 WAITING 状态中需要被显式唤醒。例如,如果线程调用了 Object.wait(),那么它必须等待另一个线程调用同一对象上的 Object.notify()Object.notifyAll() 方法才能被唤醒。

所以,BLOCKEDWAITING 两个状态最大的区别有两个:

  • BLOCKED 是锁竞争失败后被被动触发的状态,WAITING 是人为的主动触发的状态
  • BLCKED 的唤醒是自动触发的,而 WAITING 状态是必须要通过特定的方法来主动唤醒

12. wait 状态下的线程如何进行恢复到 running 状态?

线程从等待(WAIT)状态恢复到运行(RUNNING)状态的核心机制是通过外部事件触发或资源可用性变化,比如等待的线程被其他线程对象唤醒,notify() 和 notifyAll()。

synchronized (lock) {// 线程进入等待状态,释放锁lock.wait();
}// 其他线程调用以下代码唤醒等待线程
synchronized (lock) {lock.notify();     // 唤醒单个线程// lock.notifyAll(); // 唤醒所有等待线程
}

13. notify 和 notifyAll 的区别?

同样是唤醒等待的线程,同样最多只有一个线程能获得锁,同样不能控制哪个线程获得锁。

区别在于:

  • notify:唤醒一个线程,其他线程依然处于wait的等待唤醒状态,如果被唤醒的线程结束时没调用notify,其他线程就永远没人去唤醒,只能等待超时,或者被中断。

  • notifyAll:所有线程退出wait 的状态,开始竞争锁,但只有一个线程能抢到,这个线程执行完后,其他线程又会有一个幸运儿脱颖而出得到锁。

notify在源码的注释中说到notify选择唤醒的线程是任意的,但是依赖于具体实现的jvm。

14. 并发和并行的区别?

  • 并发:两个及两个以上的作业在同一 时间段 内执行。
  • 并行:两个及两个以上的作业在同一 时刻 执行。

最关键的点是:是否是 同时 执行。

15. 同步和异步的区别?

  • 同步:任务按顺序依次执行,必须等待当前操作完成才能继续下一个操作。
  • 异步:任务发起后无需等待完成,程序可继续执行其他操作。

16. 单核 CPU 支持 Java 多线程吗?

单核 CPU 是支持 Java 多线程的。操作系统通过时间片轮转的方式,将 CPU 的时间分配给不同的线程。尽管单核 CPU 一次只能执行一个任务,但通过快速在多个线程之间切换,可以让用户感觉多个任务是同时进行的。

Java 使用的线程调度是抢占式的。也就是说,JVM 本身不负责线程的调度,而是将线程的调度委托给操作系统。操作系统通常会基于线程优先级和时间片来调度线程的执行,高优先级的线程通常获得 CPU 时间片的机会更多。

17. 单核 CPU 上运行多个线程效率一定会高吗?

单核 CPU 同时运行多个线程的效率是否会高,取决于线程的类型和任务的性质。一般来说,有两种类型的线程:

  1. CPU 密集型:CPU 密集型的线程主要进行计算和逻辑处理,需要占用大量的 CPU 资源。
  2. IO 密集型:IO 密集型的线程主要进行输入输出操作,如读写文件、网络通信等,需要等待 IO 设备的响应,而不占用太多的 CPU 资源。

在单核 CPU 上,同一时刻只能有一个线程在运行,其他线程需要等待 CPU 的时间片分配。如果线程是 CPU 密集型的,那么多个线程同时运行会导致频繁的线程切换,增加了系统的开销,降低了效率。如果线程是 IO 密集型的,那么多个线程同时运行可以利用 CPU 在等待 IO 时的空闲时间,提高了效率。

因此,对于单核 CPU 来说,如果任务是 CPU 密集型的,那么开很多线程会影响效率;如果任务是 IO 密集型的,那么开很多线程会提高效率。当然,这里的“很多”也要适度,不能超过系统能够承受的上限。

18. 不同的线程之间如何通信?

 共享变量是最基本的线程间通信方式。多个线程可以访问和修改同一个共享变量,从而实现信息的传递为了保证线程安全,通常需要使用 synchronized 关键字或 volatile 关键字。

class SharedVariableExample {// 使用 volatile 关键字保证变量的可见性private static volatile boolean flag = false;public static void main(String[] args) {// 生产者线程Thread producer = new Thread(() -> {try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}// 修改共享变量flag = true;System.out.println("Producer: Flag is set to true.");});// 消费者线程Thread consumer = new Thread(() -> {while (!flag) {// 等待共享变量被修改}System.out.println("Consumer: Flag is now true.");});producer.start();consumer.start();}
}

代码解释

  • volatile 关键字确保了 flag 变量在多个线程之间的可见性,即一个线程修改了 flag 的值,其他线程能立即看到。
  • 生产者线程在睡眠 2 秒后将 flag 设置为 true,消费者线程在 flag 为 false 时一直等待,直到 flag 变为 true 才继续执行。

 

 Object 类中的 wait()notify() 和 notifyAll() 方法可以用于线程间的协作。wait() 方法使当前线程进入等待状态,notify() 方法唤醒在此对象监视器上等待的单个线程,notifyAll() 方法唤醒在此对象监视器上等待的所有线程。

class WaitNotifyExample {private static final Object lock = new Object();public static void main(String[] args) {// 生产者线程Thread producer = new Thread(() -> {synchronized (lock) {try {System.out.println("Producer: Producing...");Thread.sleep(2000);System.out.println("Producer: Production finished. Notifying consumer.");// 唤醒等待的线程lock.notify();} catch (InterruptedException e) {e.printStackTrace();}}});// 消费者线程Thread consumer = new Thread(() -> {synchronized (lock) {try {System.out.println("Consumer: Waiting for production to finish.");// 进入等待状态lock.wait();System.out.println("Consumer: Production finished. Consuming...");} catch (InterruptedException e) {e.printStackTrace();}}});consumer.start();producer.start();}
}

代码解释:

  • lock 是一个用于同步的对象,生产者和消费者线程都需要获取该对象的锁才能执行相应的操作。
  • 消费者线程调用 lock.wait() 方法进入等待状态,释放锁;生产者线程执行完生产任务后调用 lock.notify() 方法唤醒等待的消费者线程。

 

java.util.concurrent.locks 包中的 LockCondition 接口提供了比 synchronized 更灵活的线程间通信方式。 Condition接口的await()方法类似于wait()方法,signal()方法类似于notify()方法,signalAll()方法类似于notifyAll()方法。

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;class LockConditionExample {private static final Lock lock = new ReentrantLock();private static final Condition condition = lock.newCondition();public static void main(String[] args) {// 生产者线程Thread producer = new Thread(() -> {lock.lock();try {System.out.println("Producer: Producing...");Thread.sleep(2000);System.out.println("Producer: Production finished. Notifying consumer.");// 唤醒等待的线程condition.signal();} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}});// 消费者线程Thread consumer = new Thread(() -> {lock.lock();try {System.out.println("Consumer: Waiting for production to finish.");// 进入等待状态condition.await();System.out.println("Consumer: Production finished. Consuming...");} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}});consumer.start();producer.start();}
}

代码解释:

  • ReentrantLock 是 Lock 接口的一个实现类,condition 是通过 lock.newCondition() 方法创建的。
  • 消费者线程调用 condition.await() 方法进入等待状态,生产者线程执行完生产任务后调用 condition.signal() 方法唤醒等待的消费者线程。

 

java.util.concurrent 包中的 BlockingQueue 接口提供了线程安全的队列操作,当队列满时,插入元素的线程会被阻塞;当队列为空时,获取元素的线程会被阻塞。

队列自动处理线程阻塞与唤醒(如队列空时阻塞消费者,满时阻塞生产者)。

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;class BlockingQueueExample {private static final BlockingQueue<Integer> queue = new LinkedBlockingQueue<>(1);public static void main(String[] args) {// 生产者线程Thread producer = new Thread(() -> {try {System.out.println("Producer: Producing...");queue.put(1);System.out.println("Producer: Production finished.");} catch (InterruptedException e) {e.printStackTrace();}});// 消费者线程Thread consumer = new Thread(() -> {try {System.out.println("Consumer: Waiting for production to finish.");int item = queue.take();System.out.println("Consumer: Consumed item: " + item);} catch (InterruptedException e) {e.printStackTrace();}});consumer.start();producer.start();}
}

代码解释:

  • LinkedBlockingQueue 是 BlockingQueue 接口的一个实现类,容量为 1。
  • 生产者线程调用 queue.put(1) 方法将元素插入队列,如果队列已满,线程会被阻塞;消费者线程调用 queue.take() 方法从队列中取出元素,如果队列为空,线程会被阻塞。

19. 线程间通信方式有哪些?

1. 共享内存(隐式通信)

线程通过共享变量(如对象成员变量、静态变量)隐式通信。
关键点:必须使用 synchronized 或 volatile 保证可见性与原子性
示例:volatile 确保变量修改后立即对其他线程可见,synchronized 通过锁机制实现原子操作和内存屏障。

2. wait()/notify()(显式同步)

基于 Object 内置锁的经典通信方式:

  • wait():释放锁并阻塞线程,需在 synchronized 块内调用

  • notify()/notifyAll():唤醒同一锁上等待的线程(随机一个或全部)。

synchronized(lock) {while(条件不满足) { // 必须用循环检查条件lock.wait();    // 释放锁,线程阻塞}// 执行业务逻辑lock.notifyAll();   // 唤醒其他线程
}

3. Lock 与 Condition(显式锁)

使用 java.util.concurrent.locks 包下的更灵活机制:

  • Lock 替代 synchronized,提供 lock()/unlock() 显式锁控制。

  • Condition 替代 wait()/notify(),支持多个等待队列

Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();lock.lock();
try {while(条件不满足) {condition.await(); // 释放锁并等待}condition.signalAll(); // 唤醒等待线程
} finally {lock.unlock();
}

4. 阻塞队列(BlockingQueue

生产者-消费者模型的首选方案:

  • 队列自动处理线程阻塞与唤醒(如队列空时阻塞消费者,满时阻塞生产者)。

  • 常用实现类:ArrayBlockingQueueLinkedBlockingQueue

BlockingQueue<String> queue = new ArrayBlockingQueue<>(10);// 生产者
queue.put("data"); // 队列满时自动阻塞// 消费者
String data = queue.take(); // 队列空时自动阻塞

5. 同步工具类(CountDownLatch/CyclicBarrier

控制线程执行顺序的高级工具:

  • CountDownLatch:线程等待指定次数的事件完成(一次性)。

CountDownLatch latch = new CountDownLatch(3);
// 工作线程
latch.countDown(); // 事件完成,计数器减1
// 主线程
latch.await();    // 阻塞直到计数器归零
  • CyclicBarrier:线程互相等待到达屏障点(可重复使用)。
CyclicBarrier barrier = new CyclicBarrier(3);
// 工作线程
barrier.await(); // 阻塞直到所有线程到达

20. 使用多线程要注意哪些问题?

要保证多线程的程序是安全,不要出现数据竞争造成的数据混乱的问题。

Java的线程安全在三个方面体现∶

  • 原子性:提供互斥访问, 同一时刻只能有一个线程对数据进行操作,在Java中使用了atomic包(这个包提供了一些支持原子操作的类,这些类可以在多线程环境下保证操作的原子性)和synchronized关键字来确保原子性;
  • 可见性:一个线程对主内存的修改可以及时地被其他线程看到, 在Java中使用了synchronized和volatile这两个关键字确保可见性;
  • 有序性:一个线程观察其他线程中的指令执行顺序, 由于指令重排序,该观察结果一般杂乱无序,在Java中使用了happens - before原则来确保有序性。

21. 什么是线程死锁?

线程死锁描述的是这样一种情况:多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。

如下图所示,线程 A 持有资源 2,线程 B 持有资源 1,他们同时都想申请对方的资源,所以这两个线程就会互相等待而进入死锁状态。

产生死锁的四个必要条件:

  1. 互斥条件:该资源任意一个时刻只由一个线程占用。
  2. 请求与保持条件:一个线程因请求资源而阻塞时,对已获得的资源保持不放。
  3. 不剥夺条件:线程已获得的资源在未使用完之前不能被其他线程强行剥夺,只有自己使用完毕后才释放资源。
  4. 循环等待条件:若干线程之间形成一种头尾相接的循环等待资源关系。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.pswp.cn/diannao/86709.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

LSTM-SVM多变量时序预测(Matlab完整源码和数据)

LSTM-SVM多变量时序预测&#xff08;Matlab完整源码和数据&#xff09; 目录 LSTM-SVM多变量时序预测&#xff08;Matlab完整源码和数据&#xff09;效果一览基本介绍程序设计参考资料 效果一览 基本介绍 代码主要功能 该代码实现了一个LSTM-SVM多变量时序预测模型&#xff0c…

ES6——数组扩展之Set数组

在ES6&#xff08;ECMAScript 2015&#xff09;中&#xff0c;JavaScript的Set对象提供了一种存储任何值唯一性的方式&#xff0c;类似于数组但又不需要索引访问。这对于需要确保元素唯一性的场景非常有用。Set对象本身并不直接提供数组那样的方法来操作数据&#xff08;例如ma…

日志收集工具-logstash

提示&#xff1a;Windows 环境下 安装部署 logstash 采集日志文件 文章目录 一、下载二、解压部署三、常用插件四、常用配置 Logstash 服务器数据处理管道&#xff0c;能够从多个来源采集数据&#xff0c;转换数据&#xff0c;然后将数据发送到您最喜欢的存储库中。Logstash 没…

6个月Python学习计划 Day 21 - Python 学习前三周回顾总结

✅ 第一周&#xff1a;基础入门与流程控制&#xff08;Day 1 - 7&#xff09; “打地基”的一周&#xff0c;我们走完了从变量、输入输出、判断、循环到第一个小型系统的完整链路。 &#x1f4d8; 学习重点&#xff1a; Python 基础语法&#xff1a;变量类型、字符串格式化、注…

Spring Boot SQL数据库功能详解

Spring Boot自动配置与数据源管理 数据源自动配置机制 当在Spring Boot项目中添加数据库驱动依赖&#xff08;如org.postgresql:postgresql&#xff09;后&#xff0c;应用启动时自动配置系统会尝试创建DataSource实现。开发者只需提供基础连接信息&#xff1a; 数据库URL格…

java每日精进 6.11【消息队列】

1.内存级Spring_Event 1.1 控制器层&#xff1a;StringTextController /*** 字符串文本管理控制器* 提供通过消息队列异步获取文本信息的接口*/ RestController RequestMapping("/api/string-text") public class StringTextController {Resourceprivate StringTex…

【凌智视觉模块】rv1106 部署 ppocrv4 检测模型 rknn 推理

PP-OCRv4 文本框检测 1. 模型介绍 如有需要可以前往我们的仓库进行查看 凌智视觉模块 PP-OCRv4在PP-OCRv3的基础上进一步升级。整体的框架图保持了与PP-OCRv3相同的pipeline&#xff0c;针对检测模型和识别模型进行了数据、网络结构、训练策略等多个模块的优化。 从算法改…

uniapp Vue2 获取电量的独家方法:绕过官方插件限制

在使用 uniapp 进行跨平台应用开发时&#xff0c;获取设备电量信息是一个常见的需求。然而&#xff0c;uniapp 官方提供的uni.getBatteryInfo方法存在一定的局限性&#xff0c;它不仅需要下载插件&#xff0c;而且目前仅支持 Vue3&#xff0c;这让使用 Vue2 进行开发的开发者陷…

Go语言中的if else控制语句

if else是Go语言中最基础也最常用的条件控制语句&#xff0c;用于根据条件执行不同的代码块。下面我将详细介绍Go语言中if else的各种用法和特性。 1. 基本语法 1.1. 最简单的if语句 if 条件表达式 {// 条件为true时执行的代码 } 示例&#xff1a; if x > 10 {fmt.Prin…

[Spring]-AOP

AOP场景 AOP: Aspect Oriented Programming (面向切面编程) OOP: Object Oriented Programming (面向对象编程) 场景设计 设计: 编写一个计算器接口和实现类&#xff0c;提供加减乘除四则运算 需求: 在加减乘除运算的时候需要记录操作日志(运算前参数、运算后结果)实现方案:…

Web3 借贷与清算机制全解析:链上金融的运行逻辑

Web3 借贷与清算机制全解析&#xff1a;链上金融的运行逻辑 超额抵押借款 例如&#xff0c;借款人用ETH为抵押借入DAI&#xff1b;借款人的ETH的价值一定是要超过DAI的价值&#xff1b;借款人可以任意自由的使用自己借出的DAI 稳定币 第一步&#xff1a;借款人需要去提供一定…

RK3588开发笔记-GNSS-RTK模块调试

目录 前言 一、什么是GNSS/RTK 二、硬件连接 三、内核配置 四、模块调试 五、ntripclient使用 总结 前言 在RK3588平台上集成高精度定位功能是许多工业级应用的需求。本文记录了我调试GNSS-RTK模块的全过程,包含硬件连接、驱动移植、数据解析和精度优化等关键环节,希望对…

Vue.js $emit的介绍和简单使用

前言 在 Vue.js 开发中&#xff0c;组件化是核心思想之一。但组件间的通信是一个重要课题&#xff0c;特别是子组件向父组件传递数据的场景。Vue 提供了多种通信方式&#xff0c;而$emit正是实现子→父通信的关键方法。本文将深入解析$emit的原理、使用场景及最佳实践。 一、$e…

【Linux 学习计划】-- 简易版shell编写

目录 思路 创建自己的命令行 获取用户命令 分割命令 检查是否是内建命令 cd命令实现 进程程序替换执行程序 总代码 结语 思路 int main() {while (1){// 1. 自己的命令行PrintCommandLine();// 2. 获取用户命令char command[SIZE];int n GetUserCommand(command, si…

一个完整的日志收集方案:Elasticsearch + Logstash + Kibana+Filebeat (二)

&#x1f4c4; 本地 Windows 部署 Logstash 连接本地 Elasticsearch 指南 ✅ 目标 在本地 Windows 上安装并运行 Logstash配置 Logstash 将数据发送至本地 Elasticsearch测试数据采集与 ES 存储流程 &#x1f9f0; 前提条件 软件版本要求安装说明Java17Oracle JDK 下载 或 O…

Java使用Selenium反爬虫优化方案

当我们爬取大站的时候&#xff0c;就得需要对抗反爬虫机制的场景&#xff0c;因为项目要求使用Java和Selenium。Selenium通常用于模拟用户操作&#xff0c;但效率较低&#xff0c;所以需要我们结合其他技术来实现高效。 在 Java 中使用 Selenium 进行高效反爬虫对抗时&#xff…

状态管理方案对比与决策

1. 状态管理的基本概念 现代前端应用随着功能复杂度提升&#xff0c;状态管理已成为架构设计的核心挑战。状态管理本质上解决的是数据的存储、变更追踪和响应式更新问题&#xff0c;以确保UI与底层数据保持同步。 核心挑战: 状态共享与组件通信可预测的状态变更性能优化与重…

Fetch与Axios:区别、联系、优缺点及使用差异

Fetch与Axios&#xff1a;区别、联系、优缺点及使用差异 文章目录 Fetch与Axios&#xff1a;区别、联系、优缺点及使用差异一、联系二、区别1. 浏览器支持与兼容性2. 响应处理3. 请求拦截和响应拦截4. 错误处理 三、优缺点1. Fetch API优点缺点 2. Axios优点缺点 四、使用上的差…

【Docker】快速入门与项目部署实战

我们在部署一个项目时&#xff0c;会出现一系列问题比如&#xff1a; 命令太多了&#xff0c;记不住软件安装包名字复杂&#xff0c;不知道去哪里找安装和部署步骤复杂&#xff0c;容易出错 其实上述问题不仅仅是新手&#xff0c;即便是运维在安装、部署的时候一样会觉得麻烦…

Java面试题尚硅谷版第1季

1、写出如下代码运行结果 1.1、 使用局部变量表和操作数栈解题 1.2、使用前置和后置递增解题 2、写一个单例模式 2.1、考察知识点 2.2、单例模式实现 3、类加载和初始化顺序 package classload;public class Father {private int i test();private static int j method();st…