Day288.多线程-面试题汇总 -Juc

Posted 阿昌喜欢吃黄桃

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Day288.多线程-面试题汇总 -Juc相关的知识,希望对你有一定的参考价值。

多线程高频面试题汇总

  • 有多少种实现线程的方法?

本质是一种,方式有两种实现Runnable接口&继承Thread类

包装的方式有许多,lambta、线程池


  • 实现Runnable接口 和 继承Thread类 哪种方式更好?为什么

实现Runnable接口的方式更好;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Q8LxA9Di-1622819653540)(C:/Users/PePe/AppData/Roaming/Typora/typora-user-images/image-20210604213127067.png)]


  • 一个线程两次调用start()方法会怎么样?为什么

会报错,因为start()执行完毕后,线程的生命周期会进入死亡状态,当再次执行,start()方法运行开始他会对线程状态进行检测,那此时这个线程已经处于死亡状态,那就进入不了再启动状态


  • 既然start()会调用run()方法,那为什么不直接调用start(),而不是直接调用run()方法

因为start()启动了线程,让现场进入了new的生命周期,而run()方法只是普普通通的一个方法


  • 如何正确停止线程

使用interrupt,实现三方配合,一个发起中断,一个检测中断,然后响应中断


  • 如何处理不可中断的阻塞

可使用每个类所提供专门的停止线程方法来处理中断等方式


  • 线程有几种状态?生命周期是什么?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bqKEtzDA-1622819653542)(C:/Users/PePe/AppData/Roaming/Typora/typora-user-images/image-20210604214856498.png)]


  • 如何用wait()方法实现两个线程交替打印
/******
 @author 阿昌
 @create 2021-05-23 20:05
  *******
  *  两个线程交替打印 0 - 100 的奇偶数,用wait和notify
 */
public class WaitNotifyPrintOddEven {

    private static int count = 0;
    private static final Object lock = new Object();

    //主函数
    public static void main(String[] args) {
        TurningRounder turningRounder = new TurningRounder();
        Thread even = new Thread(turningRounder);
        Thread odd = new Thread(turningRounder);

        even.start();
        odd.start();
    }

    static class TurningRounder implements Runnable {
        @Override
        public void run() {
            while (count <= 100) {
                synchronized (lock) {
                    System.out.println(Thread.currentThread().getName() + "," + count++);
                    lock.notify();
                    if (count <= 100) {
                        try {
                            lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    }
}

  • 为什么要使用生产者和消费者模式

保证生产消费平衡,通过一个异步的队列来实现


  • 什么是生产者消费者模式

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2SZEFIqf-1622819653543)(C:/Users/PePe/AppData/Roaming/Typora/typora-user-images/image-20210604215607948.png)]

生产者生成往queue队列中放,queue队列满了生产阻塞;

消费者消费queue队列,queue队列没了消费阻塞;

生产者生成会向消费者通知消费;

消费者消费会向生成者通知生成

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-T6YohrLE-1622819653546)(C:/Users/PePe/AppData/Roaming/Typora/typora-user-images/image-20210604215617852.png)]


  • 手写代码,通过wait/notify实现生产者消费者模式
/******
 @author 阿昌
 @create 2021-05-22 20:58
 *******
 *  用wait/notify来实现 生产者消费者模式
 */
public class ProducerConsumerModel {
    //主函数
    public static void main(String[] args) {
        EventStorage eventStorage = new EventStorage();
        Consumer1 consumer = new Consumer1(eventStorage);
        Producer1 producer = new Producer1(eventStorage);

        Thread threadConsumer = new Thread(consumer);
        Thread threadProducer = new Thread(producer);

        threadConsumer.start();
        threadProducer.start();
    }
}

//生产者
class Producer1 implements Runnable{
    private EventStorage storage;

    public Producer1(EventStorage storage) {
        this.storage = storage;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            storage.put();
        }
    }
}

//消费者
class Consumer1 implements Runnable{
    private EventStorage storage;

    public Consumer1(EventStorage storage) {
        this.storage = storage;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            storage.take();
        }
    }
}

//队列
class EventStorage{
    private int maxSize;
    private LinkedList storage;

    public EventStorage(){
        this.maxSize=10;
        storage = new LinkedList<>();
    }

    //生成产品
    public synchronized void put(){
        //如果满了
        while (storage.size()==maxSize){
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        //如果没满
        storage.add(new Date());
        System.out.println("生产者生产了产品,仓库里有了"+storage.size()+"个产品");
        notify();
    }

    //消费产品
    public synchronized void take(){
        //如果空了
        while (storage.size()<=0){
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //如果没空
        System.out.println("消费者消费了产品,拿到了"+storage.poll()+",现在仓库还剩下"+storage.size());
        notify();
    }
}

  • 为什么wait必须在同步代码块中使用

防止在wait之后没有notify唤醒


  • 为什么线程通信的wait、notify、notifyAll被定义在object类中,而sleep定义在thread类中

因为在java中每个对象都是一把锁,虽然thread类中也有wait等,但是是不推荐的


  • wait方法属于object,那调用wait会怎么样

thread类在运行结束之后自动调用notify,会扰乱我们的逻辑


  • 如何选择用notify 还是notifyAll

优先选中notifyAll,除非要求每次只唤醒一个线程,才去使用notify


  • notifyAll后所有线程都会再次抢锁,如果某个线程抢夺失败了会怎么样

不会发送错误,而是等待下一次释放锁后再去抢夺


  • 用suspend和resume来阻塞线程可以吗?为什么?

不推荐,由于安全问题,方法已经过时


  • wait/notify、sleep的异同

wait属于object类,sleep属于thread类;

wait释放锁,sleep不释放锁;


  • 在join期间,线程处于什么线程状态

处于waiting状态,他底层就是调用了wait方法


  • yield和sleep的区别

  • 守护线程和普通线程的区别

区别在于JVM,守护线程服务普通线程,当普通线程数量为0,那么JVM就会被关闭;守护线程不会影响到jvm的结束


  • 我们需要给线程设置为守护线程吗

不需要,因为java自带的守护线程足够我们使用


  • 为什么程序设计不依赖于线程优先级

因为不同的操作系统对于线程优先级采用的策略都不一样,所以不推荐依赖线程优先级


  • 讲一讲Java异常体系

他们继承于Throwable,分为error 和exception;exception下面还分为runtimeexception和非runtimeexception


  • 实际工作中,如何全局处理异常

写一个全局异常处理器,来捕获异常


  • run方法是否可以抛出异常?如果抛出异常?线程状态会怎么样?

不可以抛出异常,如果抛出,线程就会终止


  • 一共有几类线程安全问题

3类


  • 那些场景需要额外注意线程安全问题?

资源共享…


  • 为什么多线程会带来性能问题

因为切换线程会带来上下文切换,用内存保存上下文环境和加载上下文环境


  • Java代码是如何转化,最终被CPU执行

先生成.java文件,然后javac生成字节码文件.class,然后通过jvm读取加载.class文件,生成机器代码,被cpu执行解读


  • 单例模式的作用和使用场景

  • 单例模式的多种写法、单列和高并发的关系

  • 单例各种写法的使用场景

  • 饿汉式的缺点

消耗资源,一上来就加载


  • 懒汉试的缺点

可能造成线程安全问题


  • 为什么要用double-check,不用就不安全吗?

  • 为什么双重检查模式要使用volatile

因为构造方法生成对象的过程包含3步骤,不是原子性的,所以会出现可见性和重排序问题


  • 最好的单例模式实现是什么?

枚举实现


  • 讲一讲什么是java内存模型

  • 什么是happens-before

表达可见性问题


  • Happens-before的规则

sychronized和lock锁的原则,等等等


  • 讲讲volatile关键字

  • volatile的使用场景

使用于做触发器;赋值场景


  • volatile的作用

直接将线程内存刷入主内存;保证该变量具有可见性和防重排序问题


  • volatile和sychronized的异同

volatile是轻量级是sychronized的等。。。。使用的不用场景


  • 什么会发生内存可见性问题?

因为cpu是多层缓存结构


  • 你知道主内存和本地内存吗?他们是什么

不同的线程拥有自己的本地内存,他们共用一个主内存做为交互通道


  • 主内存和本地内存的关系

本地内存之间的交互必须通过主内存;

每一个线程都有自己的本地内存;


  • 什么是原子操作

一系列操作要呢全都完成,要呢就完全都不完成


  • java中的原子操作有哪些?

除long和double以为的基本数据类型操作。。。。等


  • long和double的原子行你了解吗

他们都是64位,在32位的jvm虚拟机中,就需要写入两次,因此就会出现可见性和重排序问题,不是原子操作的原因;一次32位,但是64就不存在这个问题


  • 写一个必然死锁的列子
/******
 @author 阿昌
 @create 2021-05-31 21:16
  *******
  *      必定发生死锁的情况
 */
public class MustDeadLock implements Runnable {
    int flag = 1;//标记位
    static Object lock1 = new Object();
    static Object lock2 = new Object();

    public static void main(String[] args) {
        MustDeadLock r1 = new MustDeadLock();
        MustDeadLock r2 = new MustDeadLock();
        r1.flag=1;
        r2.flag=0;
        Thread thread1 = new Thread(r1);
        Thread thread2 = new Thread(r2);

        thread1.start();
        thread2.start();
    }

    @Override
    public void run() {
        System.out.println("flag= " + flag);
        if (flag == 1) {
            synchronized (lock1){
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (lock2){
                    System.out.println(flag);
                }
            }
        }
        if (flag == 0) {
            synchronized (lock2){
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (lock1){
                    System.out.println(flag);
                }
            }

        }
    }

}

  • 生产中什么场景会发生死锁

一个方法获取到多个锁对象


  • 发生死锁必然满足的条件是什么

互斥原则;请求与保持条件;不剥夺条件;循环等待条件


  • 如何定位死锁的位置

jstack命令;ThreadMXBean


  • 如何解决死锁

修复死锁策略;检测与恢复策略;鸵鸟策略


  • 实际开发中如何避免死锁

设置超时时间;

多使用并发类;

尽量降低锁的使用粒度;

如果能使用同步代码块,就不使用同步方法;

给你的线程起个有意义的名字;

避免锁嵌套;

分配资源前先看能不能收回;

不要几个功能用同一把锁


  • 什么是活跃性问题?活锁、饥饿和死锁有什么区别

让程序一直活跃的运行;

活锁:指的是程序还在消耗资源,但是无法进行推进下去

饥饿:一线程长时间没有获取到资源

以上是关于Day288.多线程-面试题汇总 -Juc的主要内容,如果未能解决你的问题,请参考以下文章

Java经典面试题汇总多线程

java多线程面试题汇总

Java面试题汇总

抢先准备,40个 Java 多线程面试题及答案大汇总!

2021年大厂面试题汇总:JVM+Redis+多线程+Spring全家桶

iOS多线程面试题汇总与解析