55. 解释一下 Java 中的等待/通知机制。
Java中的等待/通知机制是线程间通信的一种方式,它通过Object
类的三个方法来实现:wait()
, wait(long timeout)
, 和 notify()
, 以及 notifyAll()
。
wait()
: 当一个线程调用一个共享对象的wait()
方法时,该线程会进入等待状态,并暂时释放它对共享对象的锁定。这允许其他线程访问该对象。wait(long timeout)
: 与wait()
类似,但它允许设置一个超时时间,如果在指定时间内没有被通知,线程将自动醒来。notify()
: 当一个线程调用共享对象的notify()
方法时,它会唤醒在该对象上等待的一个线程(如果有多个线程在等待,具体唤醒哪一个是不确定的)。notifyAll()
: 唤醒在该对象上等待的所有线程。
以下是使用等待/通知机制的一个简单示例:
public class WaitNotifyExample {public synchronized void method1() {try {System.out.println("Thread " + Thread.currentThread().getName() + " is waiting.");wait(); // 释放锁System.out.println("Thread " + Thread.currentThread().getName() + " is resumed.");} catch (InterruptedException e) {e.printStackTrace();}}public synchronized void method2() {System.out.println("Thread " + Thread.currentThread().getName() + " is going to notify.");notify(); // 唤醒在同一个对象上等待的线程}
}public class Main {public static void main(String[] args) {WaitNotifyExample example = new WaitNotifyExample();Thread t1 = new Thread(() -> example.method1(), "Thread-1");Thread t2 = new Thread(() -> example.method2(), "Thread-2");t1.start();t2.start();// 确保线程t1先执行try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }// 通知t1继续执行synchronized (example) {example.notify();}}
}
注意:
wait()
和notify()
方法必须包含在一个同步块或方法中,因为它们需要操作锁。- 当线程调用
wait()
时,它不会竞争锁,直到它被唤醒。 - 使用
notify()
而不是notifyAll()
可能会导致线程饥饿,因为某些线程可能永远不会被唤醒。
这种机制主要用于生产者-消费者模式,信号量模式等并发场景中,以实现线程之间的协调和通信。
56. Java 中的线程池是如何工作的?
Java 中的线程池是一种基于池化思想管理线程的机制,它主要解决了两个问题:一是减少创建和销毁线程所需的时间,二是提高系统响应速度。
线程池的工作原理如下:
- 创建线程池:线程池通过
Executor
框架来创建和管理,常见的创建方式有ThreadPoolExecutor
和Executors
工厂类。 - 提交任务:当一个任务需要执行时,它被提交给线程池。
- 任务队列:线程池内部维护一个队列(工作队列),用于存储待执行的任务。
- 如果线程池中有空闲线程,线程池将选取一个线程执行任务。
- 如果线程池中所有线程都在工作,新提交的任务将被放入队列中等待。
- 线程复用:线程池中的线程执行完任务后不会被销毁,而是回到线程池中等待下一个任务。
- 线程管理:线程池负责管理线程的生命周期,如创建、销毁、挂起等。
以下是线程池中几个重要的参数:
- 核心线程数(Core Pool Size):线程池维护线程的最少数量。
- 最大线程数(Maximum Pool Size):线程池维护线程的最大数量。
- 工作队列(Work Queue):用于存储待执行任务的队列。
- 存活时间(KeepAliveTime):非核心线程的空闲存活时间。
- 线程工厂(ThreadFactory):用于创建新线程的工厂。
- 拒绝策略(RejectedExecutionHandler):当任务无法执行时的处理策略。
下面是一个使用ThreadPoolExecutor
创建线程池的示例:
import java.util.concurrent.*;public class ThreadPoolExample {public static void main(String[] args) {int corePoolSize = 2;int maxPoolSize = 4;long keepAliveTime = 10;TimeUnit unit = TimeUnit.SECONDS;BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>(2);ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize, maxPoolSize, keepAliveTime, unit, workQueue);// 提交任务for (int i = 0; i < 6; i++) {final int index = i;executor.execute(() -> {System.out.println("执行任务: " + index);try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}});}// 关闭线程池executor.shutdown();}
}
总的来说,线程池通过提前创建线程并复用,减少线程创建和销毁的开销,从而提高系统性能和响应速度。同时,它还支持多种灵活的配置和扩展,使得线程管理更加高效和可靠。
有帮助请点赞收藏呀~
领【150 道精选 Java 高频面试题】请 go 公众号:码路向前 。