继续面试大纲系列文章。java
(强烈推荐关注公众号:pnxsxb ,有更多更及时的学习内容分享,还会不按期有专属于程序员的好礼相送)也能够长按识别如下二维码关注:程序员
ThreadLocal和Valotile是两个比较常见的知识点,虽然简单,可是能从必定程度上考察一个程序员,对多线程环境下,线程通讯和数据安全的认知。闲话少说,进入正题:面试
看下源码,以get()方法为切入口:编程
1 public T get() { 2 Thread t = Thread.currentThread(); 3 ThreadLocalMap map = getMap(t); 4 if (map != null) { 5 ThreadLocalMap.Entry e = map.getEntry(this); 6 if (e != null) { 7 @SuppressWarnings("unchecked") 8 T result = (T)e.value; 9 return result; 10 } 11 } 12 return setInitialValue(); 13 }
重点是第三行,当前线程做为参数传入,咱们来看下getMap(t)作了什么?缓存
1 ThreadLocalMap getMap(Thread t) { 2 return t.threadLocals; 3 }
是的,拿到当前线程对象的threadLocals对象,咱们能够经过方法返回值推断,是一个ThreadLocalMap类型的对象。那么这个对象在哪定义的呢?继续看源码:安全
1 public class Thread implements Runnable { 2 ...... 3 ThreadLocal.ThreadLocalMap threadLocals = null; 4 ...... 5 }
很明显,是在Thread类里定义。多线程
4.扩展:内存泄漏问题。并发
ThreadLocal对象是弱引用。在GC时,会直接回收。这种状况下,Map中的key为null,value值还在,没法获得及时的释放。目前的策略是在调用get、set、remove等方法时,会启动回收这些值。可是若是一直没调用呢?嗯,很容易就致使内存泄漏了。固然,并不能由于此就认为是弱引用致使的内存泄露,而应该是,设计的这个变量存储机制,致使了泄露。因此在使用的时候,要及时释放(经过以上描述,你确定已经想到怎么合理释放了吧?)jvm
1.问:请你说下对Valotile的了解,以及使用场景。函数
2.分析:多线程编程,咱们要解决的问题集中在三个方面:
a.原子性,最简单的例子就是,i++,在多线程环境下,最终的结果是不肯定的,为何?就是由于这么一个++操做,被编译为指令 后,是多个指令来完成的。那么遇到并发的状况,就会致使彼此“覆盖”的状况。
b.可见性,通俗解释就是,在A线程对一个变量作了修改,在B线程中,能正确的读取到修改后的结果。究其原理,是cpu不是直 接 和系统内存通讯,而是把变量读取到L1,L2等内部的缓存中,也叫做私有的数据工做栈。修改也是在内部缓存中,可是什么时候 同步到系统内存是不能肯定的,有了这个时间差,在并发的时候,就可能会致使,读到的值,不是最新值。
c.有序性:这里只说指令重排序,虚拟机在把代码编译为指令后执行,出于优化的目的,在保证结果不变的状况下,可能会调整指 令的执行顺序。
3.答:valotile,能知足上述的可见性和有序性。可是没法保证原子性。
可见性,是在修改后,强制把对变量的修改同步到系统内存。而其余cpu在读取本身的内部缓存中的值的时候,发现是valotile修饰 的,会把内部缓存中的值,置为无效,而后从系统内存读取。
有序性,是经过内存屏障来实现的。所谓的内存屏障,能够理解为,在某些指令中,插入屏障指令,用以确保,在向屏障指令后面 继续执行的时候,其前面的全部指令已经执行完毕。
4.扩展:在写单例模式时,咱们一般会采用双层判断的方式,在最内层:
instance = new Singleton()
其实这也有一个隐含的问题:这句赋值语句,实际上是分三步来操做的:
a.为instance分配内存
b.调用Singleto构造函数来初始化变量
c.instance指向上一步初始化的对象
在jvm作了指令重排序优化后,上述步骤b和c不能保证,可能出现,c先执行,可是对象却没初始化,这时候其余线程判断的时候,发现是非null,可是使用的时候,却没有具体实例,致使报错。
因此,咱们能够用valotile来修饰instance,避免该问题。
有了以上知识储备,相信能够应对80%的面试挑战了。若是还有兴趣深刻了解,能够留言交流。
欢迎扫描如下二维码,关注我的公众号,更及时获取第一手学习资料: