面试@JUC多线程及并发包
问题1 谈谈你对volatile的理解
1 volatile是Java虚拟机提供的轻量级的同步机制
- 保证可见性
- 不保证原子性
- 禁止指令重排
2 再谈谈JMM,线程安全性获得保证
JMM(Java内存模型Java Memory Model,简称JMM)本身是一种抽象的概念 并不真实存在,它描述的是一组规则或规范通过规范定制了程序中各个变量(包括实例字段,静态字段和构成数组对象的元素)的访问方式.
JMM关于同步规定:
- 线程解锁前,必须把共享变量的值刷新回主内存
- 线程加锁前,必须读取主内存的最新值到自己的工作内存
- 加锁解锁是同一把锁
由于JVM运行程序的实体是线程,而每个线程创建时JVM都会为其创建一个工作内存(有些地方成为栈空间),工作内存是每个线程的私有数据区域,而Java内存模型中规定所有变量都存储在主内存,主内存是共享内存区域,所有线程都可访问,但线程对变量的操作(读取赋值等)必须在工作内存中进行,首先要将变量从主内存拷贝到自己的工作空间,然后对变量进行操作,操作完成再将变量写回主内存,不能直接操作主内存中的变量,各个线程中的工作内存储存着主内存中的变量副本拷贝,因此不同的线程无法访问对方的工作内存,此案成间的通讯(传值) 必须通过主内存来完成,其简要访问过程
2.1 可见性
通过前面对JMM的介绍,我们知道
各个线程对主内存中共享变量的操作都是各个线程各自拷贝到自己的工作内存操作后再写回主内存中的.
这就可能存在一个线程AAA修改了共享变量X的值还未写回主内存中时 ,另外一个线程BBB又对内存中的一个共享变量X进行操作,但此时A线程工作内存中的共享比那里X对线程B来说并不不可见.这种工作内存与主内存同步延迟现象就造成了可见性问题.
2.2 原子性
number++在多线程下是非线程安全的,如何不加synchronized解决?
2.3 VolatileDemo代码演示可见性+原子性代码
2.4 有序性
计算机在执行程序时,为了提高性能,编译器和处理器常常会做指令重排,一般分为以下3种
- 单线程环境里面确保程序最终执行结果和代码顺序执行的结果一致.
- 处理器在进行重新排序是必须要考虑指令之间的数据依赖性
- 多线程环境中线程交替执行,由于编译器优化重排的存在,两个线程使用的变量能否保持一致性是无法确定的,结果无法预测
重排1
public void mySort(){ int x=11;//语句1 int y=12;//语句2 x=x+5;//语句3 y=x*x;//语句4 } 1234 2134 1324
问题: 请问语句4 可以重排后变成第一条码?
存在数据的依赖性 没办法排到第一个
重排2
int a ,b ,x,y=0;线程1线程2x=a;y=b;b=1;a=2;x=0 y=0
如果编译器对这段代码进行执行重排优化后,可能出现下列情况:线程1线程2b=1;a=2;x=a;y=b;x=2 y=1
这也就说明在多线程环境下,由于编译器优化重排的存在,两个线程使用的变量能否保持一致是无法确定的。
3 你在哪些地方用到过volatile?
3.1 单例模式DCL代码
public class SingletonDemo { private static volatile SingletonDemo instance=null; private SingletonDemo(){ System.out.println(Thread.currentThread().getName()+"\t 构造方法"); } /** * 双重检测机制 */ public static SingletonDemo getInstance(){ if(instance==null){ synchronized (SingletonDemo.class){ if(instance==null){ instance=new SingletonDemo(); } } } return instance; } public static void main(String[] args) { for (int i = 1; i <=10; i++) { new Thread(() ->{SingletonDemo.getInstance();},String.valueOf(i)).start(); } } }
3.2 代理模式volatile分析
DCL(双端检锁) 机制不一定线程安全,原因是有指令重排的存在,加入volatile可以禁止指令重排
原因在于某一个线程在执行到第一次检测,读取到的instance不为null时,instance的引用对象可能没有完成初始化
instance=new SingletonDem(); 可以分为以下步骤(伪代码)
memory=allocate();//1.分配对象内存空间
instance(memory);//2.初始化对象
instance=memory;//3.设置instance的指向刚分配的内存地址,此时instance!=null
步骤2和步骤3不存在数据依赖关系.而且无论重排前还是重排后程序执行的结果在单线程中并没有改变,因此这种重排优化是允许的
memory=allocate();//1.分配对象内存空间
instance=memory;//3.设置instance的指向刚分配的内存地址,此时instance!=null 但对象还没有初始化完.
instance(memory);//2.初始化对象
但是指令重排只会保证串行语义的执行一致性(单线程) 并不会关心多线程间的语义一致性
所以当一条线程访问instance不为null时,由于instance实例未必完成初始化,也就造成了线程安全问题.
问题2 CAS你知道吗
1 比较并交换
2 CAS底层原理?如果知道,谈谈你对UnSafe的理解
atomicInteger.getAndIncrement();
atomicInteger.getAndIncrement()方法的源代码:
UnSafe类是什么?
- 1.UnSafe,是CAS的核心类 由于Java 方法无法直接访问底层 ,需要通过本地(native)方法来访问,UnSafe相当于一个后面,基于该类可以直接操作特额定的内存数据.UnSafe类在于sun.misc包中,其内部方法操作可以向C的指针一样直接操作内存,因为Java中CAS操作的助兴依赖于UNSafe类的方法.注意UnSafe类中所有的方法都是native修饰的,也就是说UnSafe类中的方法都是直接调用操作底层资源执行响应的任务
- 2.变量ValueOffset,便是该变量在内存中的偏移地址,因为UnSafe就是根据内存偏移地址获取数据的
- 3.变量value和volatile修饰,保证了多线程之间的可见性.
3 CAS缺点
问题3 原子类AtomicInteger的ABA问题谈谈?原子更新引用知道吗
1 ABA问题的产生
2 原子引用
3 时间戳原子引用
问题4 我们知道ArrayList是线程不安全,请编写一个不安全的案例并给出解决方案
1 解决方案1
2 解决方案2
限制不可以使用vector和Collections工具类
问题5 公平锁/非公平锁/可重入锁/递归锁/自旋锁谈谈你的理解?请手写一个自旋锁
1 公平锁和非公平锁
2 可重入锁(又名递归锁)
3 自旋锁
4 独占锁(写)/共享锁(读)/互斥锁
5 读写锁
问题6 CountDownLatch/CyclicBarrier/Semaphore使用过吗?
1 CountDownLatch
2 CyclicBarrier
3 Semaphore
问题7 阻塞队列知道吗?
1 队列+阻塞队列
2 为什么用?有什么好处?
3 BlockingQueue的核心方法
4 架构梳理+种类分析
5 用在哪里
问题8 线程池用过吗?ThreadPoolExecutor谈谈你的理解?
1 为什么使用线程池,优势
2 线程池如何使用?
3 线程池几个重要参数介绍?
4 说说线程池的底层工作原理?
问题9 线程池用过吗?生产上你是如何设置合理参数
1 线程池的拒绝策略请你谈谈
2 你在工作中单一的/固定数的/可变你的三种创建线程池的方法,你用哪个多?超级大坑
3 你在工作中是如何创建线程池的,是否自定义过线程池使用
4 合理配置线程池你是如何考虑的?
问题10 死锁编码及定位分析
1 是什么
2 代码
3 解决
问题11 Java里面锁请谈谈你的理解,能说多少说多少