前面也据说了ThreadLocal来实现高并发,之前都是用锁来实现,看了挺多资料的,发现其实仍是区别挺大的(感受严格来讲ThreadLocal并不算高并发的解决方案),如今总结一下吧。java
高并发中会出现的问题就是线程安全问题,能够说是多个线程对共享资源访问如何处理的问题,处理不当会的话,会出现结果和预期会彻底不一样。web
通常状况下,多个线程访问一个变量都是公用他们的值,不过有时候虽然也是访问共享变量,不过每一个线程却须要本身的私有变量。这个时候ThreadLocal就有用武之地了。下面是个ThreadLocal的简单实例:spring
public class ThreadLocalExample { public static void main(String[] args){ //建立一个ThreadLocal对象 ThreadLocal<Integer> threadLocal = new ThreadLocal<>(); //设置主线程私有变量值 threadLocal.set(100); //建立一个新线程 new Thread(new Runnable(){ public void run(){ //使用共享变量,设置线程私有变量 threadLocal.set(50); System.out.println(Thread.currentThread().getName() + ":" + threadLocal.get()); } }).start(); System.out.println(Thread.currentThread().getName() + ":" + threadLocal.get()); } }
输出结果:数据库
main:100
Thread-0:50
很神奇,对多个资源之间的共享,又不想他们之间相互影响,因此使用这个是挺不错的。具体应用,spring中应用我记得挺多的,链接数据库的每一个链接,还有session的存储。数组
思考了一下,要我实现的话就用个map来存储,由于这个其实就是键值对,只不过键是线程惟一标识,值就是对应的私有变量。安全
具体看了源码发现差很少,不过使用内部本身实现的一个ThreadLocalMap类,内部还一个Entry类并且Entry类继承weakRefrence(说实话第一次遇到弱应用,之前只是在jvm那本书学习了下),具体方法以下:session
先看下他的set方法吧并发
public void set(T value) { Thread t = Thread.currentThread(); //得到全部线程共享的ThreadLocalMap对象 ThreadLocalMap map = getMap(t); //对象已经存在就直接插入键值对 //不存在就建立而后再插入 if (map != null) map.set(this, value); else createMap(t, value); }
getMap方法的话一个得到全部线程共享的ThreadLocalMap对象以下:jvm
ThreadLocalMap getMap(Thread t) { return t.threadLocals; }
而后进入Thread类进去找一下这个容器,找到下面:高并发
ThreadLocal.ThreadLocalMap threadLocals = null;
而后建立:
void createMap(Thread t, T firstValue) {
//建立ThreadLocalMap对象赋给threadLocals t.threadLocals = new ThreadLocalMap(this, firstValue); }
至此,ThreadLocal的基本原理就已经很清晰了:各线程对共享的ThreadLocal实例进行操做,其实是以该实例为键对内部持有的ThreadLocalMap对象进行操做。
还有get()方法的话就是利用设置的键进行获取,remove()方法也是,其实和Hashmap差很少不过解决冲突使用的拉链法(对了,下次写一篇HashHap的还有ConcurrentHashMap的话,很有研究)。这里有个问题就是由于这个ThreadLocalMap是静态的因此在方法区中(jdk8以后为元数据区),不进行回收的话会形成内存泄漏,并且可能会出现内存溢出,因此使用后记得remove();
基本上其实能够了,不过好奇ThreadLocalMap怎么实现的能够接着往下看,我也好奇,因此也偷偷看了,嘿嘿嘿
那就来分析一下这个ThreadLocalMap这个内部类吧。
ThreadLocalMap属于一个自定义的map,是一个带有hash功能的静态内部类,其实和java.util包下提供的Map类并无关系。内部有一个静态的Entry类,下面具体分析Entry。
/** * The entries in this hash map extend WeakReference, using * its main ref field as the key (which is always a * ThreadLocal object). Note that null keys (i.e. entry.get() * == null) mean that the key is no longer referenced, so the * entry can be expunged from table. Such entries are referred to * as "stale entries" in the code that follows. */ static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } }
偷了一下官方的解释:
主要是说entry继承自WeakReference,用main方法引用的字段做为entry中的key。当entry.get() == null的时候,意味着键将再也不被引用。
注意看到一个super(k),说明调用父类的构造,去看看
Reference(T referent) { this(referent, null); } Reference(T referent, ReferenceQueue<? super T> queue) { this.referent = referent; this.queue = (queue == null) ? ReferenceQueue.NULL : queue; }
就上面这个其余没了,看了半天有点没看懂,而后去学了四种引用回来终于看懂,因为篇幅过多,在结尾我给出两篇别人的博客,能够去看完了,再回来,多学点哈哈哈。
再看了下发现这个内部类好多,可是其实就是map的一种实现,上面也讲了set方法那就简单提一下ThreadLocalMap的set()方法相关的吧,代码下面:
private void set(ThreadLocal<?> key, Object value) { // We don't use a fast path as with get() because it is at // least as common to use set() to create new entries as // it is to replace existing ones, in which case, a fast // path would fail more often than not. //把Entry对象数组拿到来 ThreadLocal.ThreadLocalMap.Entry[] tab = table; //长度也拿到来 int len = tab.length; //经过拿到key的hashcode值,进去发现神奇的一幕这里利用经过累加这个值0x61c88647来做为hashcode, // 这里提一下往下走发现由于要公用这个属性,多个实例访问会有问题 // 因此使用了AtomicInteger原子操做来写值 //而且与总长度-1作与运算就是取模,由于扩容都是2的n次方因此这样直接取模就行,速度快 int i = key.threadLocalHashCode & (len-1); //定位到对应的数组位置,进行冲突判断之类的处理 for (ThreadLocal.ThreadLocalMap.Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { //这里是冲突遍历 //这里里面就拿对应tabel下对应位置的当前引用 ThreadLocal<?> k = e.get(); //判断是否是对应的键,是的话就覆盖 if (k == key) { e.value = value; return; } //没有的话就生成Entry代替掉 if (k == null) { replaceStaleEntry(key, value, i); return; } } //这里就直接插入了 tab[i] = new ThreadLocal.ThreadLocalMap.Entry(key, value); //长度加1 int sz = ++size; //判断是否作扩容 if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); }
里面其实挺复杂的,具体的话就是正常是使用开放定址法处理,这里使用累加一个定值解决的冲突,由于多个实例共用,特殊处理,厉害厉害。
//threadLocalHashCode代码也贴在这里吧,有兴趣能够直接去看 private static AtomicInteger nextHashCode = new AtomicInteger(); private static final int HASH_INCREMENT = 0x61c88647; private static int nextHashCode() { return nextHashCode.getAndAdd(HASH_INCREMENT); } private final int threadLocalHashCode = nextHashCode();
总结
看完源码以后神清气爽,学到了不少啦。之前对java引用只是知道四个引用和对应的相应简单概念,为了看懂这个Entry,去学习了weakReference源码,看了别人的关于四个引用的博客写的真好,偷偷学习了下,而且知道怎么使用了。划重点会用了!!!固然对于ThreadLocal也会用了,并且好像能够手写一个简单的版本哎,能够动手试试。
关于四种引用博客,写的真的很棒。