Java并发编程之常见面试题(一)

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方法的异同?

相同点:

  • 阻塞
  • 响应中断

不同点:

  1. wait/notify必须在同步方法中进行,sleep不需要
  2. sleep方法必须设定超时时间,wait/notify不需要
  3. sleep属于Thread类,wait/notify属于Object类
  4. wait/notify释放锁,sleep不释放锁

19. join期间,线程处于哪种线程状态?

答:Waiting

20. 守护线程和普通线程的区别?我们是否需要设置线程为守护线程?

答:普通线程由我们创建的,用于执行逻辑,守护线程服务于普通线程,守护线程不影响JVM的退出,而所有普通线程都结束,JVM自动退出。不应该设置成守护线程。JVM发现没有用户线程了,JVM会退出,而我们的线程就会强行终止,造成数据不一致。

21. 如何处理线程异常?

  1. 在每个线程中用try/catch处理
  2. 实现UncaughtExceptionHandler接口进行全局处理
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值