zoukankan      html  css  js  c++  java
  • ThreadLocal详解

    ThreadLocal 分析

    首先我们看一下下面这个程序

    public class ThreadLockDemo {
    
        //初始tl per对象名是 zs
    	static ThreadLocal<per> tl = new ThreadLocal<per>() {
    		 protected per initialValue() {
    		        return new per("zs");
    		    }
    	};
    	static class per{
    		String name;
    		public per(String n) {
    			this.name = n; 
    		}
    	} 
    		new Thread(()->{
    			try {
                    //线程0 在tl中放入了新的per对象 ls ,并将线程停留
    				tl.set(new per("ls"));
    				System.out.println(tl.get().name);
    			} catch (Exception e) {
    				// TODO Auto-generated catch block
    				e.printStackTrace();
    			}
    		}).start();
    		new Thread(()->{
    			try {
                    //线程1 首先睡眠 1S
    				Thread.sleep(1000);
    			} catch (InterruptedException e) {
    				// TODO Auto-generated catch block
    				e.printStackTrace();
    			}
                //按照正常的逻辑,tl被 线程0 set 了 ls 对象,此时取出应该是 ls,而结果却是 zs 是tl设定的初始值
    			System.out.println(tl.get().name);
    		}).start();
    	}
    }
    
    zs
    ls
    

    然后我们在看下面这个程序,按照上面程序的理解,下面程序的输出结果应该是zs ls zs 但是结果却是 zs ls ls,证明了tl 里面的对象并不是在本地新建一个对象,而是在本地复制了对象的引用,然后线程1通过这个引用把对象修改了,所以线程0后面取到的数据就是修改后的了。

    public class ThreadLockDemo {
    	static ThreadLocal<per> tl = new ThreadLocal<per>();
    	static class per{
    		String name;
    		public per(String n) {
    			this.name = n; 
    		}
    	} 
    	public static void main(String[] args) throws InterruptedException {
    		per p  = new per("zs");
    		new Thread(()->{
    			try {
                    //在线程0中添加p对象
    				tl.set(p);
    				System.out.println(p.name); //输出此时p对象name
    				Thread.sleep(5000);
    				System.out.println(tl.get().name);//获取此时p对象name
    			} catch (InterruptedException e) {
    				// TODO Auto-generated catch block
    				e.printStackTrace();
    			}
    		}).start();
    		new Thread(()->{
                //同样添加p对象
    			tl.set(p);			
    			per pp = tl.get();
                //修改p对象
    			pp.name = "ls";
    			System.out.println(tl.get().name);
    		}).start();
    	}
    }
    zs
    ls
    ls
    

    下面是第二个程序的分析图

    下面是ThreadLoacl的引用关系,简单说明一下set和get过程

    set

    在Thread内部的ThreadLocalMao中添加一个key 指向 ThreadLocal,value指向 需要保存对象的 entey 对象,此时的key指向entry是虚引用,这样就能保证在执行remove的时候,3这条指向断开或者指向其他对象),2这个指向因为是虚引用,所以Entry可以被垃圾回收,不然的话这个Entry被new ThreadLocal引用,造成内存泄漏。当然,如果不执行remove,5这条指向不会断开,同样会造成 new per("ZS")这个地方内存泄漏。
    补充: 除了2这条指向new ThreadLocal的引用,还有一个tl指向它的强引用,这样才能保证new ThreadLocal这个对象不被回收。

    下面是源码分析

    //ThreadLocal的set方法  
    public void set(T value) {
        //获取当前线程
        Thread t = Thread.currentThread();
        //得到当前线程的ThreadLocalMap对象
        ThreadLocalMap map = getMap(t);
        	/*
        		这是getMap方法,显然是返回了一个当前线程的ThreadLocalMap对象
                ThreadLocalMap getMap(Thread t) {
                    return t.threadLocals;
                }
             */
        //如果map不是空的
        if (map != null)
            //将当前的value设置到map里面,此时的this是当前线程,
            //所以这个map的key是当前线程,以第二个程序为例,value是一个 per对象的引用连接,这里可以在设置之前打印看一下,是一个对象的引用
            map.set(this, value);
        	/*
        		这是set方法实现细节
        		private void set(ThreadLocal<?> key, Object value) {
    			因为此时map不为空,需要拿到当前Thread里面的ThreadLocalMap里面的enery数组
    			Entry[] tab = table;
                int len = tab.length;
                拿到当前key的hashCode(被重写了,线程使用了AtomicInteger类)并和len-1,len肯定是2的倍数,与len-1 进行与操作可以大幅度保证 i 与hashCode相同
                int i = key.threadLocalHashCode & (len-1);
    			从当前hashCode的位置开始找位置放当前value
                for (Entry e = tab[i]; 
                	 如果不为空就一直找下去
                     e != null;
                     如果 i+1 < len  nextIndex 返回 i+1 ,否则返回 0 ,作用:如果当前key有值,就一直往后找,到达最后还没有找到空值位置再从第一个开始找。不会找不到的,因为上一次循环结束如果没位置了就会扩容,保证下一个有空位置。
                     e = tab[i = nextIndex(i, len)]) {
                    下面过程要记得key 是 ThreadLocal 对象,不是 Thread 对象
                    ThreadLocal<?> k = e.get();
    				如果存在相同key则直接覆盖,这是第一个程序中tl.set(new per("ls")) ls 覆盖 zs 处理部分
    				结束循环
                    if (k == key) {
                        e.value = value;
                        return;
                    }
    				如果当前ThreadLocal不存在这个key,结束循环
                    if (k == null) {
                    	从当前位置开始将脏的Entry都处理掉
                        replaceStaleEntry(key, value, i);
                        return;
                    }
    
                }
    			把当前元素的值给到Entry[i]
                tab[i] = new Entry(key, value);
                大小加1
                int sz = ++size;
                如果清理了一部分脏数据就判断是否需要扩容,否则判断一下
                if (!cleanSomeSlots(i, sz) && sz >= threshold)
                	扩容,这个依旧是先判断有没有脏数据,如果清理成功就不扩容
                    rehash();
            }
        	*/
        else
            //创建一个新的ThreadLocalMap
            createMap(t, value);
        	/*
        	void createMap(Thread t, T firstValue) {
                t.threadLocals = new ThreadLocalMap(this, firstValue);
            }
            ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
            	INITIAL_CAPACITY = 16
                table = new Entry[INITIAL_CAPACITY];
                int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
                table[i] = new Entry(firstKey, firstValue);
                size = 1;
                设置上限为16 * 2/3
                setThreshold(INITIAL_CAPACITY);
            }
        	*/
    }
    

    下面考虑最后一个问题,假设同时又两个线程执行 tl.set()

    解答:

    执行 tl.set 首先是调用 ThreadLocal 内部的 set 方法,内部是不同的线程的各自的ThreadLocalMap对象的增减,彼此线程之间不影响,所以可以多线程使用,这部分是安全的。虽然是同一个ThreadLocal

  • 相关阅读:
    逆光拍摄常见的问题(解决大光比问题)
    HDP和包围曝光
    直方图
    linux查找文件的命令【转】
    100篇大数据文章[转]
    squid
    修改/etc/resolv.conf又恢复到原来的状态?[转]
    python字符串及正则表达式[转]
    GraphLab介绍[转]
    Scala 中的 apply 和 update 方法[转]
  • 原文地址:https://www.cnblogs.com/aierben/p/14764996.html
Copyright © 2011-2022 走看看