多线程面试题总结

1.并发编程的三要素:(也是带来线程安全所在)

原子性:原子是不可再分割的最小单元,原子性是指一个或多个操作要么全部执行成功,要么全部执行失败。
可见性:一个线程对共享变量的修改,另一个线程能看到(synchronized,volatile)
有序性:程序的执行顺序按照代码的先后顺序

线程安全的问题原因有:

 - 线程切换带来的原子性问题
 - 缓存导致的可见性问题
 - 编译优化带来的有序性问题

解决方案:

  • JDK Atomic开头的原子类、synchronized、LOCK,可以解决原子性问题
  • synchronized、volatile、LOCK,可以解决可见性问题
  • Happens-Before 规则可以解决有序性问题

2.线程与进程的区别

根本区别:进程是操作系统资源分配的基本单元,而线程是处理器任务调度的和执行的基本单位。
内存分配:同一进程的线程共享本进程的地址空间和资源,而进程之间的地址空间和资源是相互独立的。

线程分类

分为用户线程与守护线程
用户(User)线程:运行在前台,执行具体任务,如程序的主线程,连接网络的子线程都是用户线程。必须执行完才会结束。例如,main主线程。
守护(Daemon)线程:运行在后台,为其它前台线程服务,也可以说守护线程是JVM非守护线程的”佣人“,一旦所有用户线程都执行结束,守护线程会随着JVM一起结束运行。例如,垃圾回收线程。

3.形成死锁的四个必要条件

  • 互斥条件:线程(进程)对所分配的资源具有排它性,即一个资源只能被一个进程占用,直到该进程被释放。
  • 请求与保持条件:一个进程(线程)因请求被占有资源而发生堵塞时,对已获取的资源保持不放。
  • 不剥夺条件:线程(进程)已获取的资源在未使用完之前不能被其他线程强行剥夺,只有等自己使用完才释放资源。
  • 循环等待条件:当发生死锁时,所等待的线程(进程)必定形成一个环路,死循环造成永久堵塞。

4.如何避免死锁

只需破坏形参死锁的四个必要条件之一即可。

  • 破坏互斥条件:无法破坏,我们的🔒本身就是来个线程(进程)来产生互斥
  • 破坏请求与保持条件:一次申请所有资源
  • 破坏不剥夺条件:占有部分资源的线程尝试申请其它资源,如果申请不到,可以主动释放它占有的资源。
  • 破坏循环等待条件:按序来申请资源。

5.什么是上下文的切换

当前任务执行完,CPU时间片切换到另一个任务之前会保存自己的状态,以便下次再切换会这个任务时可以继续执行下去,任务从保存到再加载执行就是一次上下文切换。

6.创建线程的四种方式

  • 继承Thread类
  • 实现Runnable接口
  • 实现Callable接口
  • Executors工具类创建线程池

补充:Callable接口call方法有返回值,是个泛型,和Futrue和FutureTask配合用来获取异步执行结果。Callable用于产生结果,Future用于接收结果。需要调用FutureTask.get()得到,此方法会堵塞主线程继续往下执行,如果不调用就不会堵塞。Runable接口run方法只能抛出运行时的异常,且无法捕获处理;Callable接口call方法允许抛出异常,可以获取异常信息。

7.start()

start方法使线程进入就绪,会执行线程前的相应准备工作

8.阻塞

  • 等待阻塞:运行的线程执行wait()方法,JVM会有该线程放入等待队列
  • 同步阻塞:运行的线程在获取对象的同步锁时,若该同步被别的线程占用,则JVM会把该线程放入锁池中
  • 其他阻塞:运行的线程执行sleep或join方法,或者发出来I/O请求时,JVM会把该线程置为阻塞状态。当sleep状态超时,join等待线程终止或者超时,或者I/O处理完毕时,线程重新进入可运行状态。

9.线程调度算法

  • 分时调度就是让所有的线程轮流获得CPU的使用权,并且平均分配到各个线程占有CPU的时间片。
  • 抢占式调度:Java虚拟机采用抢占式调度模型,是指优先让线程池中优先级高的线程首先占用CPU,如果线程池中优先级相同,那么随机选择一个线程,使其占有CPU,处于这个状态的CPU会一直运行,优先级高的分的CPU的时间片相对会多一点。

10.sleep()和wait()有什么区别

两者都可以使线程进入等待状态

  • 类不同:sleep()是Thread下的静态方法,wait()是Object类下的方法
  • 是否释放锁:sleep()不释放锁,wait()释放锁
  • 用处不同:wait()常用于线程间的通信,sleep()常用于暂停执行。
  • 用法不同:wait()用完后,线程不会自动执行,必须调用notify()或notifyAll()方法才能执行,sleep()方法调用后,线程经过过一定时间会自动苏醒,wait(参数)也可以传参数使其苏醒。它们苏醒后还有所区别,因为wait()会释放锁,所以苏醒后没有获取到锁就进入堵塞状态,获取到锁就进入就绪状态,而sleep苏醒后之间进入就绪状态,但是如果cpu不空闲,则进入的是就绪状态的堵塞队列中。

11.Thread的sleep和Thread的yield

  1. yield作用:让出CPU的使用权,使当前线程从运行状态进入就绪状态,等待CPU的下次调度。
  2. Thread类的sleep()和yield()方法将在当前正在运行的线程上工作,所以其它处于等待状态的线程调用它们是没有意义的,所以设置为静态最合适。
  3. 线程调用sleep()方法进入堵塞状态,醒来后因为(没有释放锁)后直接进入了就绪状态,运行yield后也没有释放锁,于是进入了就绪状态。
  4. sleep()方法使用时需要处理InterruptException异常,而yield没有。
  5. sleep()执行后进入堵塞状态(计时等待),醒来后进入就绪状态(可能是堵塞队列),而yield是直接进入就绪状态。

12.保证多线程安全的方式

  1. 使用安全类,比如 java.util.concurrent 下的类,使用原子类AtomicInteger
  2. 使用自动锁,synchronized锁
  3. Lock lock = new ReentrantLock(),使用手动锁lock .lock(),lock.unlock()方法

13.锁

  1. CAS:Compare And Swap (Compare And Exchange) / 自旋 / 自旋锁 / 无锁 ,解决办法(版本号 AtomicStampedReference),基础类型简单值不需要版本号
  2. Unsafe:AtomicInteger中用到了Unsafe类,最终实现是:
lock cmpxchg 指令

14.锁升级过程

无锁 - 偏向锁 - 轻量级锁 (自旋锁,自适应自旋)- 重量级锁

  1. 如果有线程上锁,上偏向锁,指的就是,把markword的线程ID改为自己线程ID的过程
  2. 如果有线程竞争,撤销偏向锁,升级轻量级锁。线程在自己的线程栈生成LockRecord ,用CAS操作将markword设置为指向自己这个线程的LR的指针,设置成功者得到锁
  3. 如果竞争加剧,有线程超过10次自旋, 或者自旋线程数超过CPU核数的一半, 1.6之后,加入自适应自旋 Adapative Self Spinning , JVM自己控制
    升级重量级锁:-> 向操作系统申请资源,linux mutex , CPU从3级-0级系统调用,线程挂起,进入等待队列,等待操作系统的调度,然后再映射回用户空间

15.锁消除 lock eliminate

public void add(String str1,String str2){
         StringBuffer sb = new StringBuffer();
         sb.append(str1).append(str2);
}

我们都知道 StringBuffer 是线程安全的,因为它的关键方法都是被 synchronized 修饰过的,但我们看上面这段代码,我们会发现,sb 这个引用只会在 add 方法中使用,不可能被其它线程引用(因为是局部变量,栈私有),因此 sb 是不可能共享的资源,JVM 会自动消除 StringBuffer 对象内部的锁。

16.锁粗化 lock coarsening

public String test(String str){
       
       int i = 0;
       StringBuffer sb = new StringBuffer():
       while(i < 100){
           sb.append(str);
           i++;
       }
       return sb.toString():
}

JVM 会检测到这样一连串的操作都对同一个对象加锁(while 循环内 100 次执行 append,没有锁粗化的就要进行 100 次加锁/解锁),此时 JVM 就会将加锁的范围粗化到这一连串的操作的外部(比如 while 虚幻体外),使得这一连串操作只需要加一次锁即可。

17.volatile的用途

  1. 线程可见性
  2. 防止指令重排序

18.volatile如何解决指令重排序

  1. volatile i
  2. ACC_VOLATILE
  3. JVM的内存屏障
  4. hotspot实现

JSR内存屏障
在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值