Java并发六十问,快来看看你会多少道

请参考下面这篇文章,总结的非常好。
面渣逆袭:Java并发六十问,快来看看你会多少
并发这里主要从下面这几个大块:
1、基础
2、ThreadLocal
3、Java内存模型
4、锁
5、并发工具类
6、线程池
7、并发容器和框架

基础

TODO

ThreadLocal

  1. ThreadLocal 内存泄漏是怎么回事?
  1. 栈中存储:ThreadLocal,Thread的弱引用
  2. 堆中存储:ThreadLocal、ThreadLoaclMap 的实例
  3. jvm垃圾回收机制运行:弱引用,不管jvm内存空间是否足够,都会回收该对象的内存。
  4. 内存泄漏:ThreadLocalMap 的key,即TjreadLocal的弱引用被垃圾回收器回收,而ThreadLocalMap 的生命周期是Thread是一样的。如果value此时没有被回收,则会造成内存泄漏。
  5. 如何解决:每次使用完ThreadLocal后,及时调用remove()方法,释放内存空间
  1. 那为什么ThreadLocal key还要设计成弱引用?

原因:防止内存泄漏
疑问TODO:假如key被设计成强引用,如果ThreadLocal Reference被销毁,此时它指向ThreadLoca的强引用就没有了,但是此时key还强引用指向ThreadLoca,就会导致ThreadLocal不能被回收,这时候就发生了内存泄漏的问题。

  1. ThreadLocalMap的结构了解吗?

元素数组:

  1. 底层是个Entry数组;Entry 是个k,v 结构
  2. 一个table数组,存储Entry类型的元素,Entry是ThreaLocal弱引用作为key,Object作为value的结构。

散列方法:

  1. 目的:把key映射成table数组的下标
  2. 算法:哈希取余法
  3. 算法实现:int i = key.threadLocalHashCode & (table.length - 1);
  4. threadLocalHashCode:每创建一个ThreadLocal对象,就会新增:0x61c88647。这个值很特殊,它是斐波那契数 也叫 黄金分割数。hash增量为 这个数字,带来的好处就是 hash 分布非常均匀
  1. ThreadLocalMap怎么解决Hash冲突的?
  1. 方法:开放寻址法
  2. 思想:简单来说,就是这个坑被人占了,那就接着去找空着的坑。i = i+1
  1. ThreadLocalMap扩容机制了解吗?

set—> rehash(): 1. 清理过期Entry,2. resize()
扩容阈值:theshild = (len*2)/3
决定是否扩容:size >= threshold - threshold /4
resize()
扩容过程:

  1. 创建2倍大小新数组
  2. 老数组元素散列到新数组
  3. table 引用指向新数组
  1. ThreadLocal 是怎么实现的?
  1. Thread类有一个类型为ThreadLocal.ThreadLocalMap的实例变量threadLocals,每个线程都有一个属于自己的ThreadLocalMap。
  2. ThreadLocalMap内部维护着Entry数组,每个Entry代表一个完整的对象,key是ThreadLocal的弱引用,value是ThreadLocal的泛型值。
  3. 每个线程在往ThreadLocal里设置值的时候,都是往自己的ThreadLocalMap里存,读也是以某个ThreadLocal作为引用,在自己的map里找对应的key,从而实现了线程隔离。
  4. ThreadLocal本身不存储值,它只是作为一个key来让线程往ThreadLocalMap里存取值。
  1. 你在工作中用到过ThreadLocal吗?
  1. 做用户信息上下文的存储
  2. 数据库连接池的实现,也用到了TreadLocal:数据库连接池的连接交给ThreadLoca进行管理,保证当前线程的操作都是同一个Connnection。
  1. ThreadLocal是什么?
  1. 线程本地变量
  2. 你创建了一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个本地拷贝,多个线程操作这个变量的时候,实际是操作自己本地内存里面的变量,从而起到线程隔离的作用,避免了线程安全问题。
  1. 父子线程怎么共享数据?

父线程能用ThreadLocal来给子线程传值吗?毫无疑问,不能。那该怎么办?
这时候可以用到另外一个类——InheritableThreadLocal。
。 使用方法:主线程中对InheritableThreadLocal设置值,在子线程中可以获取到值。
2. 原理:Thread类里还有另外一个变量:ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
在Thread.init的时候,如果父线程的inheritableThreadLocals不为空,就把它赋给当前线程(子线程)的inheritableThreadLocals。

Java内存模型

  1. 说一下你对Java内存模型(JMM)的理解?

是什么?
1.

  1. 说说你对原子性、可见性、有序性的理解?
  1. 原子性:一个操作是不可分割、不可中断的,要么全部执行并且执行的过程不会被任何因素打断,要么就全不执行
  2. 可见性:一个线程修改了某一个共享变量的值时,其它线程能够立即知道这个修改。
  3. 有序性:有序性指的是对于一个线程的执行代码,从前往后依次执行,单线程下可以认为程序是有序的,但是并发时有可能会发生指令重排。
  1. 代码分析原子性?

int i = 2;// 基本类型赋值,是原子性操作。
int j = i; // 先读i的值,再赋值到j,两步操作,不能保证原子性。
i++; //先读取i的值,再+1,最后赋值到i,三步操作了,不能保证原子性
i = i + 1;// 同上

  1. 原子性、可见性、有序性都应该怎么保证呢?

原子性:JMM只能保证基本的原子性,如果要保证一个代码块的原子性,需要使用synchronized。
可见性:Java是利用volatile关键字来保证可见性的,除此之外,final和synchronized也能保证可见性。
有序性:synchronized或者volatile都可以保证多线程之间操作的有序性。

  1. 那说说什么是指令重排?

重排序目的: 提高程序执行的性能
谁会做:编译器、处理器
重排序分类:

  1. 编译器优化的重排序
  2. 指令级并行的重排序
  3. 内存系统的重排序

经典指令重拍需例子:双重校验单例模式。
对应的JVM指令分为三步:

  1. 分配内存空间–>
  2. 初始化对象—>
  3. 对象指向分配的内存空间

但是经过了编译器的指令重排序,第二步和第三步就可能会重排序。 通过禁止特定类型的编译器重排序和处理器重排序,为程序员提供一致的内存可见性保证。

  1. 指令重排有限制吗?happens-before了解吗?

有限制:通过两个规则happens-before和as-if-serial来约束

  1. as-if-serial又是什么?单线程的程序一定是顺序的吗?

意思:不论如何重排序,单线程程序的执行结果不能被改变。

  1. volatile实现原理了解吗?

作用:有2个,保证可见性;保证有序性
问题01:volatie如何保证可见性?

  • volatile可以确保对某个变量的更新对其他线程马上可见,一个变量被声明为volatile 时,线程在写入变量时不会把值缓存在寄存器或者其他地方,而是会把值刷新回主内存。当其它线程读取该共享变量 ,会从主内存重新获取最新值,而不是使用当前线程的本地内存中的值。

问题02:volatie如何保证有序性?

  • 重排序可以分为编译器重排序和处理器重排序,valatile保证有序性,就是通过分别限制这两种类型的重排序。

如何限制呢?
写:写操作前:StormStore屏障,写操作后:StoreLoad屏障
读:读操作前:LoadLoad屏障,读操作后:LoadStore屏障

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值