面试题:线程基础5问
Posted 格子衫111
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了面试题:线程基础5问相关的知识,希望对你有一定的参考价值。
一:为何说只有 1 种实现线程的方法?
1.创建线程,本质上就一种,通过new Thread()实现;
但实现线程执行内容有两种方式,也是我们最常见的,1)继承Thread类;2)实现Runnable接口。
其他方式,例如线程池,Timer只是对new Thread()的封装。
2.实现Runnable接口相比继承Thread类的好处:
1)结构上分工更明确,线程本身属性和任务逻辑解耦;
2)某些情况下性能更好,直接把任务交给线程池执行,无需再次new Thread();
3)可拓展性更好,实现接口可以多个,而继承只能单继承。
二:如何正确停止线程?为什么 volatile 标记位的停止方法是错误的?
1.正确停止线程的方法:
thread.interrupt(),通知线程中断;
2.线程内逻辑需配合响应中断:
1)正常执行循环中使用 Thread.currentThread().isInterrupted()判断中断标识;
2)若含有sleep()等Waiting操作,会唤醒线程,抛出interruptedException,抛出后中断标识会重置。对于中断异常,要么正确处理,重新设置中断标识;要么在方法上声明抛出异常以便调用方处理。
3.为什么用 volatile 标记位的停止方法是错误的
例如 生产-消费模式,含有阻塞put操作时,volatile 标记变量改变也无法唤醒阻塞中的生产者线程。
4.stop()、suspend() 和 resume()是已过期方法,
有很大安全风险,它们强行停止线程,有可能造成线程持有的锁或资源没有释放。
三:线程是如何在 6 种状态之间转换的?
在 Java 中线程的生命周期中一共有 6 种状态。
- New(新创建)
- Runnable(可运行)= running || ready
- Blocked(被阻塞)
- Waiting(等待)
- Timed Waiting(计时等待)
- Terminated(被终止)
如果想要确定线程当前的状态,可以通过 getState() 方法,并且线程在任何时刻只可能处于 1 种状态。
Java 中的 Runable 状态对应操作系统线程状态中的两种状态,分别是 Running 和 Ready,也就是说,Java 中处于 Runnable 状态的线程有可能正在执行,也有可能没有正在执行,正在等待被分配 CPU 资源。
四:wait/notify/notifyAll 方法的使用注意事项?
1. 为什么 wait 方法必须在 synchronized 保护的同步代码中使用?
1)拿生产者消费者举例,如果消费者未加 synchronized 保护,while 判断和 wait 方法无法构成原子操作,可能生产者先notify,消费者再wait,后续如无生产者再notify,消费者便有可能陷入无穷无尽的等待。
2)程序需采用while循环来应对“虚假唤醒”(非notify/notifyAll,中断或超时的唤醒),即便被虚假唤醒了,也会再次检查while里面的条件。
2. 为什么 wait/notify/notifyAll 被定义在 Object 类中,而 sleep 定义在 Thread 类中?
1)wait/notify/notifyAll 也都是锁级别的操作,它们的锁属于对象,所以把它们定义在 Object 类中是最合适,因为 Object 类是所有对象的父类。
2)因为可能在Thread中持有多个对象锁,做复杂的业务操作,如果定义wait定义在Thread中,那么是不满足的。
3. wait/notify 和 sleep 方法的异同?
相同点:
1)都可以使线程阻塞;
2)都可以响应interrupt 中断,并抛出InterruptedException 异常
不同点:
1)wait必须写在 synchronized 保护的代码中,sleep不用;
2)在同步代码中,执行sleep方法时不会释放monitor锁,执行wait方法时会自动释放monitor锁;
3)sleep方法需要设置到期时间,到期自动恢复,wait方法不能设置时间,不会主动恢复,除非被中断或者唤醒;
4)wait/notify 是属于Object类的,sleep是属于Thread类的;
五:有哪几种实现生产者消费者模式的方法?
1.如何用 BlockingQueue 实现生产者消费者模式?
public static void main(String[] args) {
BlockingQueue<Object> queue = new ArrayBlockingQueue<>(10);
Runnable producer = () -> {
while (true) {
queue.put(new Object());
}
};
new Thread(producer).start();
new Thread(producer).start();
Runnable consumer = () -> {
while (true) {
queue.take();
}
};
new Thread(consumer).start();
new Thread(consumer).start();
}
-
首先,创建了一个 ArrayBlockingQueue 类型的 BlockingQueue,命名为 queue 并将它的容量设置为 10;
-
其次,创建一个简单的生产者(继承Runnablel),while(true) 循环体中的queue.put() 负责往队列添加数据,同时创建两个生产者线程并启动;
-
然后,创建一个简单的消费者(继承Runnablel),while(true) 循环体中的 queue.take() 负责消费数据,同时创建两个消费者线程并启动。
2.如何用 Condition 实现生产者消费者模式?
public class MyBlockingQueueForCondition {
private Queue queue;
private int max = 16;
private ReentrantLock lock = new ReentrantLock();
private Condition notEmpty = lock.newCondition();
private Condition notFull = lock.newCondition();
public MyBlockingQueueForCondition(int size) {
this.max = size;
queue = new LinkedList();
}
public void put(Object o) throws InterruptedException {
lock.lock();
try {
while (queue.size() == max) {
notFull.await();
}
queue.add(o);
notEmpty.signalAll();
} finally {
lock.unlock();
}
}
public Object take() throws InterruptedException {
lock.lock();
try {
while (queue.size() == 0) {
notEmpty.await();
}
Object item = queue.remove();
notFull.signalAll();
return item;
} finally {
lock.unlock();
}
}
}
自行实现一个简易版的 BlockingQueue:
1)首先,定义了一个队列(Queue )变量 queue 并设置最大容量(ma变量)为 16;
2)其次,定义了一个 ReentrantLock 类型的 Lock 锁,并在 Lock 锁的基础上创建两个 Condition,一个是 notEmpty,另一个是 notFull,分别代表队列没有空和没有满的条件;
3)最后,声明了 put 和 take 这两个核心方法。
put:使用lock() 方法加锁,队列满了,使用notFull.await()进行等待,未满, 生产后,使用notEmpty.signalAll()通知,finally,使用unlock() 释放锁;
take:使用lock() 方法加锁,队列为0,使用notEmpty.await()进行等待,大于0, 消费后,使用notFull.signalAll()通知,finally,使用unlock() 释放锁。
3.如何用 wait/notify 实现生产者消费者模式?
class MyBlockingQueue {
private int maxSize;
private LinkedList<Object> storage;
public MyBlockingQueue(int size) {
this.maxSize = size;
storage = new LinkedList<>();
}
public synchronized void put() throws InterruptedException {
while (storage.size() == maxSize) {
wait();
}
storage.add(new Object());
notifyAll();
}
public synchronized void take() throws InterruptedException {
while (storage.size() == 0) {
wait();
}
System.out.println(storage.remove());
notifyAll();
}
}
实现原理和Condition 类似 , 自行实现简易版BlockingQueue:
用 LinkedList 装数据,核心在于put和 take 两个方法的编写,
put:list 满了,使用wait()进行等待,未满, 生产后,使用 notifyAll() 通知;
take:list 为0,使用wait()进行等待,大于0, 消费后,使用 notifyAll() 通知。
思考: 为何多线程的代码大部分都用while而不用if?
消费者举例:
如果是if,执行 wait() 时会自动释放
monitor锁,可能导致两个线程同时进来一起休眠。当被生产者唤醒时,两个线程一起被唤醒。一个抢到锁,正常消费,消费完毕,另外个线程又拿到锁,再往下执行(去消费),but,东西都被第1个线程消费完了,本身是不满足条件的,还执行个鬼啊。所以,换成while的话,它会继续去走一遍判断,然后再次休眠,避免抛出NoSuchElementException 异常。
以上是关于面试题:线程基础5问的主要内容,如果未能解决你的问题,请参考以下文章