Java并发面试题

  1. 为什么多线程不一定快,如何解决?
因为有上下文切换消耗时间;
可以使用无锁编程,CAS算法,使用最少线程,协程来解决。
  1. 线程有哪些分类?
守护线程:虚拟机创建(不一定),典型垃圾回收线程。
用户线程:程序创建。
虚拟机会等待非守护线程结束运行后退出,但不会等待守护线程。
  1. 什么是上下文切换?
任务从保存到在加载的过程。
线程分时使用cpu。时间一到,线程需要保存任务状态,以待恢复运行时正常开始执行。
CPU调度模式:分时调度与抢占式调度(引发饥饿问题)
  1. 什么是线程饥饿和优先级?
饥饿:是由于线程无法获取需要的资源,由高优先级线程占用资源,线程处于等待状态不被唤醒。
产生饥饿的原因:高优先级线程占用资源;无法获取锁。
优先级是线程的优先执行权,从1 - 10,取决于操作系统,一般不需要设置。
  1. 什么是死锁,活锁?
死锁:多个线程都无法获得资源继续执行。
可以通过避免一个线程获取多个锁;一个锁占用一个资源;使用定时锁;数据库加解锁在一个连接中。
死锁的必要条件:环路等待,不可剥夺,请求保持,互斥条件
活锁:线程之间相互谦让资源,都无法获取所有资源继续执行。
  1. 实现多线程的方式有哪些?
① 继承Thread类:Java单继承,不推荐;
② 实现Runnable接口:Thread类也是继承Runnable接口,推荐;
③ 实现Callable接口:实现Callable接口,配合FutureTask使用,有返回值;
④ 使用线程池:复用,节约资源;
  1. 哪些操作释放锁,哪些不释放锁?
sleep(): 释放资源,不释放锁,进入阻塞状态,唤醒随机线程,Thread类方法。
wait(): 释放资源,释放锁,Object类方法。
yield(): 不释放锁,进入可执行状态,选择优先级高的线程执行,Thread类方法。
如果线程产生的异常没有被捕获,会释放锁。
  1. 如何正确的终止线程?
① 使用共享变量,要用volatile关键字,保证可见性,能够及时终止。
② 使用interrupt()和isInterrupted()配合使用。
  1. interrupt(), interrupted(), isInterrupted()的区别?
interrupt():设置中断标志;
interrupted():响应中断标志并复位中断标志;
isInterrupted():响应中断标志;
  1. 等待通知经典范例?
wait()和notify()会有如下问题
① 假通知:先启动调用notify()方法的线程;通知已经发送,wait()线程没有接收到;
@ 假唤醒:例如List新增一个元素,却唤醒所有删除线程。
等待方范例:
synchronized(对象){
    while(条件不满足){
        对象.wait();
    }
    对应的处理逻辑
}
唤醒方范例:
synchronized(对象){
    改变条件
    对象.notifyAll();
}
  1. volatile是什么,有什么作用?
只能修饰变量,保证可见性,禁止指令重排序,但是不保证原子性。
可见性通过强制将数据写回主存,然后使其它CPU的缓存失效。
禁止指令重排是通过内存屏障来实现的。
  1. synchronized的锁对象是哪些?
普通方法是当前实例对象;
同步方法快是括号中配置内容,可以是类Class对象,可以是实例对象;
静态方法是当前类Class对象。
只要不是同一个锁,就可以并行执行,同一个锁,只能串行执行。
  1. volatile和synchronized的区别是什么?
1. volatile只能使用在变量上;而synchronized可以在类,变量,方法和代码块上。
2. volatile至保证可见性;synchronized保证原子性与可见性。
3. volatile禁用指令重排序;synchronized不会。
4. volatile不会造成阻塞;synchronized会。
  1. 什么是缓存一致性问题,如何解决?
A、B两个线程同时计算i为1时,i++,理论结果是3;但是由于A\B同时操作,将i先读取i并缓存起来,AB看到的i都是1,+1都是2后继续放入缓存中,等待写回贮存。
可以通过总线加锁:但是其它cpu也不能使用总线,效率低。
通过缓存一致性协议:系统发现共享变量被修改,会通知其它cpu,从主存重新读取。
  1. 对象头是什么,有什么用?
对象头由两个或三个字节组成,存放mark word,类型指针,数组存储数组长度。
mark word中,记录了hash code,分代年龄和锁标志。
名称   |   25bit   |   4bit   | 1bit是否偏向锁  | 2bit锁标志 |
无锁   | hash code | 分代年龄 |        0        |     01     |
名称   |   23bit   |  2bit  |   4bit   | 1bit是否偏向锁  | 2bit锁标志 |
偏向锁 |   线程Id  | Epoch  | 分代年龄 |       1         |     01     |
轻量锁 |               指向栈中所记录的指针                |     00     |
重量锁 |                 指向重量锁的指针                  |     10     |
GC     |                      空                         |     11     |
  1. 锁有几种状态,有什么限制?
无锁→偏向锁→轻量锁→重量锁
只能升级,不能降级
  1. 偏向锁的获取与撤销?
获取:
    ①测试对象头中线程id是否指向当前线程id,是获取锁。
    ②如果不指向当前线程,查看对象头是否是偏向锁,不是竞争锁。
    ③是偏向锁则通过CAS尝试将对象头的偏向锁指向当前线程。
撤销:
    ①偏向锁的撤销首先要有竞争,然后在全局安全点暂停线程。
    ②检查线程是否存活,死亡则对象头设置为无锁状态。
    ③线程存活,栈中锁记录和mark word要么偏向其它线程,要么设置成无锁或不适合做偏向锁。
  1. 轻量级锁的获取与撤销?
获取:
     ①将mark word复制到栈中锁记录
     ②将mark word替换为指向锁记录的指针
     ③成功获取锁,失败锁竞争
撤销:
     ①将栈中锁记录复制回mark word,成功解锁,否则锁膨胀。
  1. CAS是什么,有什么问题,如何解决?
通过对指定内存地址的实际值与期望值进行比较,如果相同,则替换成新值,否则不替换。
1. ABA问题,先替换成一个值,修改后在替换回来; 使用版本号;
2. 循环开销时间大;
3. 只能保证一个变量的原子性;
  1. 重排序的分类?
编译器重排序:不改变语义的前提下重排序;
指令级重排序:没有数据依赖前提下重排序;
内存级重排序:使用缓存和读写缓冲区;
  1. 什么是happens-before,有哪些常见的happens-before?
一个操作执行结果需要对另一个操作可见。
程序顺序:一个线程中的每个操作happens-before与该线程任意后续操作;
锁:对一个锁的解锁happens-before随后的加锁;
volatile:一个volatile的写happens-before后续任意的读;
传递性:A happens-before B,B happens-before C,A happens-before C
  1. 如何实现自旋锁?
import java.util.concurrent.atomic.AtomicReference;
public class MyLock {
    AtomicReference threadAtomicReference = new AtomicReference<>();
    public void lock() {
        Thread thread = Thread.currentThread();
        System.out.println("线程:" + thread.getName() + ",尝试获取锁!");
        while (!threadAtomicReference.compareAndSet(null, thread)) {
        }
        System.out.println("线程:" + thread.getName() + ",获取锁!");
    }
     public void unLock() {
        Thread thread = Thread.currentThread();
        threadAtomicReference.compareAndSet(thread, null);
        System.out.println("线程:" + thread.getName() + ", 释放锁!");
    }
}

你可能感兴趣的:(面试题总结)