Java ThreadLocal详解

 

    在多线程开发中,ThreadLocal是很是常见的Java API。它能够轻松的建立一个线程安全的变量,使得每个线程拥有各自的实例变量。java

    如下代码为每个线程分配了一个线程ID。    web

public class ThreadId {
     // Atomic integer containing the next thread ID to be assigned
     private static final AtomicInteger nextId = new AtomicInteger(0);

     // Thread local variable containing each thread's ID
     private static final ThreadLocal<Integer> threadId =
         new ThreadLocal<Integer>() {
             @Override protected Integer initialValue() {
                 return nextId.getAndIncrement();
         }
     };

     // Returns the current thread's unique ID, assigning it if necessary
     public static int get() {
         return threadId.get();
     }
 }

 

ThreadLocal如何作到一个实例存储了不一样线程的值?tomcat

           跟踪ThreadLocal get()方法能够发现,它先获取了当前线程Thread.currentThread,再从当前线程中获取ThreadLocalMap实例。理一下思路,实际上每个线程拥有一个ThreadLocalMap实例,这个Map存储了当前线程的值。安全

        进一步跟踪ThreadLocalMap。它以WeakReference<ThreadLocal>作Key,Object作Value。ThreadLocal作key的做用?为什么须要定义成WeakReference?多线程

package com.sd.concurrent;

import org.junit.Test;

import java.util.concurrent.TimeUnit;

/**
 * Created by sunda on 17-8-23.
 */
public class ThreadLocalTest {

    class User {
        String name;
        String desc;

        public User() {
        }

        public User(String desc, String name) {
            this.desc = desc;
            this.name = name;
        }

        public String getDesc() {
            return desc;
        }

        public void setDesc(String desc) {
            this.desc = desc;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        @Override
        public String toString() {
            return "ValueHolder{" +
                    "desc='" + desc + '\'' +
                    ", name='" + name + '\'' +
                    '}';
        }
    }

    class Product{
        String name;
        String desc;

        public Product() {
        }

        public Product(String desc, String name) {
            this.desc = desc;
            this.name = name;
        }

        public String getDesc() {
            return desc;
        }

        public void setDesc(String desc) {
            this.desc = desc;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }
    }
    ThreadLocal<User> userThreadLocal = new ThreadLocal<>();
    ThreadLocal<Product> productThreadLocal = new ThreadLocal<>();
    @Test
    public void test() throws InterruptedException {

        Thread aaThread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                userThreadLocal.set(new User("aa", "aa11"));
                productThreadLocal.set(new Product("aa", "aa11"));

                try {
                    TimeUnit.SECONDS.sleep(1);
                    User valueHolder = userThreadLocal.get();
                    System.out.println("aa "+valueHolder);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        aaThread1.start();

        Thread bbthread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                userThreadLocal.set(new User("bb", "bb11"));
                productThreadLocal.set(new Product("bb", "bb11"));

                try {
                    TimeUnit.SECONDS.sleep(1);
                    User valueHolder = userThreadLocal.get();
                    System.out.println("bb "+valueHolder);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        bbthread1.start();

        User valueHolder = userThreadLocal.get();
        System.out.println("main  "+valueHolder);

        TimeUnit.SECONDS.sleep(300);

    }
}

        以上代码,两个ThreadLocal变量,两个Thread。同一个线程拥有两个ThreadLocal变量的时候,须要互相区分,防止值被覆盖。ide

        在HashMap中,若是以Object作key,咱们都会复写HashCode方法。一样的,在ThreadLocal中,有一个名为threadLocalHashCode的变量,它作为key的hashCode。在设计hashCode的时候,咱们会生成一个尽量稀疏的值,来获取更高读写的性能。性能

        在ThreadLocal,它以递增一个固定值的方式给ThreadLocal实例分配threadLocalHashCode。该值是精心设计的,在map中能够均匀分布。this

        设计成WeakReference是为了尽早的垃圾回收.Thread的建立和销毁都须要耗必定的资源,一般建立完以后会重复使用,每每生命周期很长,而线程中的实例则否则,须要更早的被回收.变量定义成WeakReference将在GC触发后被回收。线程

          ThreadLocal为咱们提供了很是大的便利,可是不正确的使用很容易引发较为隐晦的内存泄漏,这种内存泄漏常见于web程序,例如tomcat下运行的一段代码.            设计

public class UserIfaceThreadLocal {

    public static ThreadLocal<UserIface> userIfaceThreadLocal = new ThreadLocal<>();
   .......

    
}

           该程序乍看起来毫无问题,前期也能稳定运行,可是过一段时间后,会在永久区溢出.出现这种问题甚至重启web也无济于事.这段"经典"的代码是ClassLoader溢出的范例.

           在分析以前,有两点须要注意:

                1:ThreadLocal实例被申明为static的类变量.它从ClassLoader加载后存活,直到最后Class被回收.

                2:Tomcat做为web容器,它的工做线程存活时间比web程序更长.web程序的全部Class都是被tomat的ClassLoader加载进去的.               

           假设web程序终止,JVM开始回收上述Class,这时候发现有一个ThreadLocal实例的存活.因为tomcat的工做线程并未终止,UserIface仍被线程引用到.因而classloader没法被回收,致使了全部的Class文件仍在JVM中,没法卸载.

            在使用ThreadLocal的时候,须要像使用资源同样去处理,建立出来须要正确"关闭".在上述例子中,须要主动调用remove清空.

相关文章
相关标签/搜索