1. 进程和线程的异同?
相同点:生命周期相似,都包含就绪、运行、终止等状态
不同点:
1. 起源不同,先有进程,由于CPU的提升以及性能要求的提高,才有了线程;
2. 内存共享机制不同:进程与进程之间通常不能共享数据,线程与线程之间不需要任何处理就能直接共享数据;
3. 拥有资源数量不同,线程共享的属性:进程ID、进程公有数据等;线程私有的属性:线程ID、线程堆栈,进程是线程的容器,一个进程至少要包含一个线程‘
4. 开销不同,进程的启动和终止都远大于线程
2. 并发和并行的异同?
相同点:并行一定是并发
不同点:
1. 并发是利用单核CPU,多个任务在重叠的时间内运行和完成,因为CPU运行的很快,只是看起来像同时运行,实际上是串行的;
2. 并行是利用多核CPU,多个任务在同一时刻启动、运行和终止,是真正的同时运行。
3. 高并发是不是就意味着多线程?有什么反例?
1. 高并发不意味着多线程,高并发是大量请求导致的状态,多线程只是解决高并发状态的一种解决方案
2. 反例:数据库请求是高并发状态时,可以加入Redis缓存层来解决高并发状态,而Redis是单线程的
3. 反例:可以使用消息中间件来解决高并发,比如RabbitMQ
4. 多线程可以提高程序执行效率,你知不知道有哪些弊端?
1. 性能问题:上下文切换、保存CPU缓存带来的损耗
2. 线程安全问题:数据安全(i++)、死锁、活锁、饥饿
5. 什么是同步阻塞、异步非阻塞?
同步阻塞:调用者发起请求,在结果返回之前一直等待,被调用者不会主动告诉结果
异步非阻塞:调用者发起请求,在结果返回之前做别的事,被调用者主动告诉结果
6. 有多少种实现线程的方法?
1. 从代码写法的角度,有实现Runnable接口、继承Thread类、线程池、FutureTask和Callable等;
2. 本质上是实现Runnable接口、继承Thread类,其中实现Runnable接口更好,有三点优势;
3. 看原理,实现Runnable接口、继承Thread类本质都是一样的,都是执行run(),一种是执行Runnable对象的run(),另一种是重写Thread类的run()。
7. 实现Runnable接口和继承Thread类哪种方式更好?
1. 解耦的角度,创建线程和任务内容应该解耦;
2. 新建线程的损耗,Thread类负责创建线程,实现Runnable接口的方法,我们可以反复的用同一个线程,不用再去新建线程,比如线程池就这么做的;
3. 扩展性,Java不支持多继承,继承Thread类就无法继承其他类,限制了扩展性。
8. 一个线程两次调用start()会出现什么情况?为什么?
1. 启动线程时会检查线程状态,如果不等于0,即未启动状态,会报线程状态异常;
2. 关于线程状态:New、Runnable、Waiting、TimeWaiting、Terminated,创建线程没执行start()时是New状态,执行后是Runnable状态;
9. 如何停止线程?
1. 正确方法:用interrupt来请求,并与子线程配合停止,保证数据安全,把停止权交给子线程
2. 错误方法:stop/suspend已废弃,volatile的boolean不能在阻塞状态下响应中断
10. 线程有哪几种状态?生命周期是什么?
11. 手写生产者消费者模式
public class ProducerAndConsumer {
public static void main(String[] args) throws InterruptedException {
Storage storage = new Storage();
Thread producer = new Thread(new Producer(storage));
Thread consumer = new Thread(new Consumer(storage));
producer.start();
consumer.start();
}
}
class Storage {
private int MAX_SIZE = 100;
private List<Object> objects = new ArrayList<>();
public void take() throws InterruptedException {
synchronized (this) {
while (objects.size() == 0) {
wait();
}
objects.remove(0);
System.out.println("消费者消费了1个产品,还剩下" + objects.size() + "个产品");
notify();
}
}
public void put() throws InterruptedException {
synchronized (this) {
while (objects.size() == MAX_SIZE) {
wait();
}
objects.add(new Object());
System.out.println("生产者生产了1个产品,还剩下" + objects.size() + "个产品");
notify();
}
}
}
class Producer implements Runnable {
private Storage storage;
public Producer(Storage storage) {
this.storage = storage;
}
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
try {
storage.put();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Consumer implements Runnable {
private Storage storage;
public Consumer(Storage storage) {
this.storage = storage;
}
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
try {
storage.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
12. 用程序实现两个线程交替打印0~100的奇偶数
/**
* 使用wait/notify实现
*/
public class EvenOddTwo implements Runnable {
private int MAX_SIZE = 100;
private int count = 0;
private final Object lock = new Object();
@Override
public void run() {
while (count < MAX_SIZE) {
synchronized (lock) {
System.out.println(Thread.currentThread().getName() + ":" + count++);
lock.notify();
if (count < MAX_SIZE) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
public static void main(String[] args) {
EvenOddTwo evenOddTwo = new EvenOddTwo();
Thread t1 = new Thread(evenOddTwo, "偶线程");
Thread t2 = new Thread(evenOddTwo, "奇线程");
t1.start();
t2.start();
}
}
/**
* 只使用synchronized实现
*/
public class EvenOddOne {
private static final int MAX_VALUE = 100;
private static int count = 0;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
while (count <= MAX_VALUE) {
synchronized (EvenOddOne.class) {
if (count % 2 != 0) {
System.out.println(Thread.currentThread().getName() + ":" + count++);
}
}
}
}
}, "奇线程");
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
while (count <= MAX_VALUE) {
synchronized (EvenOddOne.class) {
if (count % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ":" + count++);
}
}
}
}
}, "偶线程");
t1.start();
t2.start();
}
}
13. wait方法要在同步代码块里面使用?而sleep不需要?
- 因为wait方法和notify方法要配合使用的,同步代码块的作用是在同一时刻只有一个线程执行这段代码,如果不在同步代码块里使用,有可能出现所有的线程都先执行了notify方法,再来执行wait方法,导致线程永久等待;
- sleep方法只是针对当前线程的,不需要与其他线程配合,所以不需要;
14. 为什么wait,notify,notifyAll被定义在Object类里面,而sleep定义在Thread类里?
- wait,notify,notifyAll是锁级别的操作,而锁是属于某一个对象的,而并不是线程中;
- 如果定义在线程中,如果某个线程持有多个锁,如果调用Thread.wait(),就没法实现指定释放某个对象的锁的逻辑,就不够灵活;
15. wait方法是属于Object对象的,那调用Thread.wait会怎么样?
Thread十分退出,线程退出的时候,会自动进行notify,会影响wait和notify来配合
16. notifyAll之后所有的线程都会再次抢夺锁,如果某线程抢夺失败怎么办?
会陷入到等待的状态,即BLOCKED状态,等待线程调度,知道拿到这把锁
17. 用suspend和resume来阻塞线程可以吗?为什么?
不可以,突然的停止线程会导致脏数据导致线程安全问题,不推荐。推荐wait/notify
18. wait/notify、sleep方法的异同?
相同点:
- 阻塞
- 响应中断
不同点:
- wait/notify必须在同步方法中进行,sleep不需要
- sleep方法必须设定超时时间,wait/notify不需要
- sleep属于Thread类,wait/notify属于Object类
- wait/notify释放锁,sleep不释放锁
19. join期间,线程处于哪种线程状态?
答:Waiting
20. 守护线程和普通线程的区别?我们是否需要设置线程为守护线程?
答:普通线程由我们创建的,用于执行逻辑,守护线程服务于普通线程,守护线程不影响JVM的退出,而所有普通线程都结束,JVM自动退出。不应该设置成守护线程。JVM发现没有用户线程了,JVM会退出,而我们的线程就会强行终止,造成数据不一致。
21. 如何处理线程异常?
- 在每个线程中用try/catch处理
- 实现UncaughtExceptionHandler接口进行全局处理