多线程面试题知识点总结

  1. 并行和并发的概念
    并行说明的是两个程序在同一时刻一起执行
    并发的概念和并行略微有些差别 是在微观时刻上的一起实行是有执行的先后顺序的。
    对于单核机器来说只能发生并发,对于多核机器来说可以发生并发也可以发生并行。

  2. 进程和线程
    进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。 它是操作系统动态执行的基本单元。通俗的来说 就是我们电脑上安装的软件在运行时所创建的一个执行单元。
    线程,程序执行的最小单元,换句话说真正进行执行活动的是线程。进程里面管理的就是线程。进程可以包含一个或多个线程。

    注意:线程具有自己的私有空间,所有的线程共享一片内存空间,每个线程都有单独的栈内存用来存储本地的数据,同一进程中的其他线程无法访问。

  3. 线程的生命周期可能会经过的状态

得到处理器资源
yield或者失去处理器资源
阻塞 sleep
创建 New
就绪 Runnable
运行 Running
死亡

线程状态在源码里面有如下几种
NEW RUNNABLE BlOCKED WAITTING TIMED_WAITING TERMINATED
4. 线程安全问题
就拿售票来说,当有两个线程都来进行操作的时候,线程1和线程2 同时拿到了同一张票,然后再进行买票操作,最后会发现一张票卖给了两个人。这就是线程安全的问题。
总结 线程安全问题的原因
- 多个线程操作共享的数据(票)
- 操作共享数据的线程代码有多条
- 多个线程对共享的数据有写操作

  1. 线程同步解决线程安全问题
    同步的方法主要有三种

    • 同步代码块
    • 同步方法
    • 同步锁
  2. 线程死锁
    多线程以及多进程改善了系统资源的利用率并提高了系统的出口i能力,然而并发执行也带来了新的问题-死锁
    所谓死锁 是指多个线程因竞争资源而造成的一种僵局(互相等待),若无外力作用,这些进程都将无法向前推进。
    死锁产生的必要条件:互斥条件,不可剥夺条件,请求保持条件,循环等待条件;

  3. 线程通讯的方式

    • 通过对象唤醒

    • 通过Lock关键字生成的条件Condition的signall和await()进行通信

    • 使用countDownLatch进行线程间的通信;主要的api是countDownLatch.countDown()
      该方法进入压入栈的线程数并且减去原来的初始值。
      该线程能使一个线程等待其他线程完成各自的工作之后再执行。

    • Cyclicbarrier可以进行线程间的通信。该类和CountDownLatch非常相似,该类可以实现让一组线程等待至某个状态之后再同步执行。该类中的reset方法可以实现循环利用

    • Semaphore 用于控制线程对于某组资源的访问权限。 通过acquire和release进行资源管理

  4. wait和sleep的区别
    wait只能在上下文中调用wait(),否则就抛出illegalMonitorStateException 异常;sleep不需要再同步块中调用。
    作用对象 wait方法定义再Object类内,作用于对象本身;sleep方法定义再Thread类中 作用域当前线程。
    wait() 释放锁资源;sleep() 不释放锁资源
    唤醒条件 wait唤醒需要其他线程调用notify或者notifyAll;而sleep()超时或者调用interrupt()方法体。
    方法属性 wait()是实例方法 ; sleep()是静态方法

  5. Java中的volatile变量是什么
    可见性,是指线程间的可见性, 一个线程修改的状态对另一个线程是可见的,
    java虚拟机提供的轻量级的同步机制 1.保证了可见性, 2. 不保证原子性, 3 禁止指令重排

  6. JMM的可见性
    由于jvm运行程序的实体是线程,而每个线程创建时jvm都会为其创建一个工作内存,工作内存是每个线程的私有数据区域,而***java内存模型中规定所有变量都存储在主内存,主内存是共享内存区域,所有线程都可以访问, 但线程对变量的操作必须在工作内存中进行,首先要将变量从主内存拷贝到自己的工作内存空间,然后对变量进行操作,操作完成后再将变量写会主内存***,线程间的通信必须通过主内存来完成.

  7. 一个线程运行时发生异常会怎样。
    如果该异常被捕获,则程序继续运行
    如果异常没有被捕获,该线程将会停止执行。

  8. interrupted 和isInterrupted方法的区别
    interrupted() 和isInterrupted()的主要区别是前者会将中断状态清除 而后者不会。Java多线程的中断机制是用内部标识来实现的,调用Thread.interrupt() 来中断一个线程就会设置中断标识为true,当调用Thread.interrupted() 来检查中断状态时,中断状态会被清零,而非静态方法isInterrupted() 用来查询其他线程的中断状态,且不会改变中断状态标识。简单的来说就是任何抛出InterruptedException异常的方法都会将中断状态清零。

  9. 怎样检测一个线程是否拥有锁
    在java.lang.Thread中有一个方法 叫holdsLock(),它返回true,如果当且仅当当前线程拥有某个具体对象的锁。

  10. Java线程池中的submit()和execute()方法有什么区别
    对返回值的处理不同。
    execute() 方法不关心返回值
    submit方法有返回值,Future
    对异常的处理不同
    execute方法不会抛出异常
    submit方法不会抛出异常。除非调用future.get()方法

  11. 如果在提交任务时,线程池队列已满,这时会发生甚末。
    如果使用的是无界队列LinkedBlockingQueue,也就是无界队列,没关系,继续添加任务到阻塞队列中等待执行,因为linkedBlockingQueue可以近乎认为是无穷大的队列,可以无限存放任务。

    如果使用的是有界队列,ArrayBlockingQueue,任务首先会被添加到ArrayBlockingQueue中,ArrayBlockingQueue满了,会根据maximumPoolSize 的值来增加线程数量,如果增加了线程数量还是处理不过来,ArrayBlockingQueue继续满,那么则会使用拒绝策略RejectedExecutionHandler处理满了的任务。

    ThreadPoolExecutor(int corePoolSize,//线程池维护线程的最少数量
    int maximumPoolSize,//线程池维护线程的最大数量
    long keepAliveTime,//线程池维护线程所允许的空间时间。当线程池的数量超过corePoolSize时,多余的空闲线程的存活时间
    TimeUnit unit,//线程池维护线程所允许的空闲时间的单位
    BolckingQueue queue,//线程池所使用的缓冲队列
    RejectedExecutionHandler handler);//线程池对拒绝任务的处理策略
    

    handler有四个选择
    策略1 ThreadPoolExecutor.AbortPolicy() 丢弃任务并抛出RejectExecutionException异常。

    策略2 ThreadPoolEcecutor.DiscardPolicy 丢弃任务,但是不抛出异常,如果线程队列已满,则后续提交的任务都会被丢弃,且是静默丢弃

    策略3 ThreadPoolEcecutor.DiscardOldestPolicy 丢弃队列最前面的任务,然后重新提交被拒绝的任务。

    策略4 CallerRunsPolicy 如果任务被拒绝了,则由调用线程直接执行此任务。
    在这里插入图片描述

  12. 什么是cas
    比较并交换,假设由三个操作数,内存值V 旧的预期值,要修改的值B,当且仅当预期值A和内存值V相同时,才会将内存值修改为B并且返回 false。当然cas 一定要volatile变量配合,这样才能保证每次拿到的变量时主内存中最新的那个值。
    即现在由两个线程对共享变量num=10进行加1操作,线程1和线程2同时拿到num=10的值,假设线程1 先执行,那么此时变量值变为11,而线程2来执行时发现,内存值11和旧的预期值10不同就返回false,重新执行。

  13. synchroized 和 Lock的区别
    1. Synchroized是内置的Java的关键字,Lock是一个java类
    2. Synchroized无法判断获取锁的状态,Lock可以判断是否获取到了锁 tryLock 来判断是否获取到了锁。
    3. synchroized会自动释放锁,但是lock必须手动释放锁。
    4. synchroized 可重入锁,不可以中断,非公平锁,即谁先抢到谁就占用。Lock 可重入锁,可以判断锁,非公平锁,可以自己设置,通过在ReentrantLock(true) 设置为公平锁,模式是非公平锁。

  14. 生产者消费者的虚假唤醒问题
    下面看一个例子
    我使用的是ArrayBlockingQueue来作为仓库存储面包类,生产者开了一个 可以生产20次,消费者开了两个每个线程可以消费10次,理论上来说运行时应该没问题,但是下面的运行结果就出了问题, 生产者只生产了10次,两个消费者各消费了5次,就一直处于阻塞状态。
    在这里插入图片描述
    这种现象就是虚假唤醒,那么出现虚假唤醒的原因是什么
    网上的截图
    在这里插入图片描述
    (1)product <= 0时,两个消费者先后根据if条件判断为“缺货”,都进入wait状态;

(2)另外一个生产者生产了产品之后,通过this.notifyAll();的调用,唤醒了所有的消费线程。使得他们共同执行–produce的操作;也就是说,
–product; // 被执行了两次
​ 但是如果你采用while,他会判别你的锁条件标志还没有被修改,那么会再一次的进入到 wait 的状态中。

上述我们可以理解 线程唤醒后的执行顺序,wait后保存现场,并释放对象锁,当再唤醒的时候,打开现场继续执行。所以要使用while。

  1. CountDownLatch 和CyclicBarrier之间的区别

    1. CountDownLatch准确的来说就是起到计数的功能 即资源减少到0时,才会继续向下执行
     public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(10);
        int i =0;
        for(i=0;i<10;i++){
            new Thread(()->{
                countDownLatch.countDown();
                System.out.println("go out"+countDownLatch.getCount());
            }).start();
        }
        countDownLatch.await();//等待计数器为零
        System.out.println("我在等待结束");
    }
    

    在这里插入图片描述

    1. CyclicBarrier 是控制资源到达某个之后,再进行执行,我们可以控制在资源到达某个之之后执行某个线程。而且最重要的是它可以循环利用。通过reset接口重新利用栅栏。
  2. 如何理解读写锁 ReadWriteLock
    多个线程在进行读操作时不会互斥
    但是多个线程读,其中一个线程写 会互斥,以及多个线程写会互斥。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值