ThreadLocal
一.ThreadLocal简介
多线程访问同一个共享变量的时候容易出现并发问题,特别是多个线程对一个变量进行写入的时候,为了保证线程安全,一般使用者在访问共享变量的时候需要进行额外的同步措施才能保证线程安全性。ThreadLocal是除了加锁这种同步方式之外的一种保证一种规避多线程访问出现线程不安全的方法,加锁会有性能问题,当我们在创建一个变量后,如果每个线程对其进行访问的时候访问的都是线程自己的变量这样就不会存在线程不安全问题。
二.ThreadLocal的使用场景
1.第一种场景 重写initialValue
initialValue 不重写的情况下是null ,调用get方法时才会去执行initialValue ,每个线程最多调用一次,如果调用了remove再调用get方法则可以再次调用initialValue
利用ThreadLocal,给每个线程分配自己的dateFormat对象,保证了线程安全,高效利用内存
package threadlocal; import java.text.SimpleDateFormat; import java.util.Date; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * 描述: 利用ThreadLocal,给每个线程分配自己的dateFormat对象,保证了线程安全,高效利用内存 */ public class ThreadLocalNormalUsage { public static ExecutorService threadPool = Executors.newFixedThreadPool(10); public static void main(String[] args) throws InterruptedException { for (int i = 0; i < 5; i++) { int finalI = i; threadPool.submit(new Runnable() { @Override public void run() { String date = new ThreadLocalNormalUsage().date(finalI); System.out.println(date); } }); } threadPool.shutdown(); } public String date(int seconds) { //参数的单位是毫秒,从1970.1.1 00:00:00 GMT计时 Date date = new Date(1000 * seconds); // SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); SimpleDateFormat dateFormat = ThreadSafeFormatter.dateFormatThreadLocal2.get(); System.out.println( Thread.currentThread().getName() + ThreadSafeFormatter.dateFormatThreadLocal); //通过get方法取出ThreadLocal中的SimpleDateFormat SimpleDateFormat simpleDateFormat = ThreadSafeFormatter.dateFormatThreadLocal.get(); System.out.println(Thread.currentThread().getName() + simpleDateFormat.toString()); System.out.println( Thread.currentThread().getName() + System.identityHashCode(simpleDateFormat)); return dateFormat.format(date); } } class ThreadSafeFormatter { public static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal = new ThreadLocal<SimpleDateFormat>() { @Override protected SimpleDateFormat initialValue() { return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); } }; //使用lambda方式重写initialValue方法 public static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal2 = ThreadLocal .withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")); }
2.第二种场景 手动调用set方法 避免传递参数的麻烦
package threadlocal; /** * 描述: 演示ThreadLocal用法2:避免传递参数的麻烦 */ public class ThreadLocalNormalUsage06 { public static void main(String[] args) { new Service1().process(""); } } class Service1 { public void process(String name) { User user = new User("超哥"); UserContextHolder.holder.set(user); new Service2().process(); } } class Service2 { public void process() { User user = UserContextHolder.holder.get(); ThreadSafeFormatter.dateFormatThreadLocal.get(); System.out.println("Service2拿到用户名:" + user.name); new Service3().process(); } } class Service3 { public void process() { User user = UserContextHolder.holder.get(); System.out.println("Service3拿到用户名:" + user.name); UserContextHolder.holder.remove(); } } class UserContextHolder { public static ThreadLocal<User> holder = new ThreadLocal<>(); } class User { String name; public User(String name) { this.name = name; } }
三.Thread、ThreadLocal以及ThreadLocalMap三者之间的关系
每个Thread线程内部有一个名为threadLocals的成员变量,该变量的类型为ThreadLocal.ThreadLocalMap类型(类似于一个HashMap)
ThreadLocalMap可以存放多个ThreadLocal,因为一个线程中可能有多个ThreadLocal
其中的key为当前定义的ThreadLocal变量的this引用,value为我们使用set方法设置的值
四.注意点
ThreadLocalMap中key是弱引用value是强引用,因为线程可能长时间运行,key被GC回收之后,value和线程之间还存在强引用链路,导致无法回收,可能出现OOM
因为一个ThreadLocal不被使用就可能导致value的内存泄漏,所以使用完之后应该主动调用remove方法删除对应的value,避免内存泄漏