一、Thread Local Storage 模式
1. ThreadLocal 的使用
Thread Local Storage 表示线程本地存储模式。
大多数并发问题都是由于变量的共享导致的,多个线程同时读写同一变量便会出现原子性,可见性等问题。局部变量是线程安全的,本质上也是由于各个线程各自拥有自己的变量,避免了变量的共享。
Java 中使用了 ThreadLocal 来实现避免变量共享的方案。ThreadLocal 保证在线程访问变量时,会创建一个这个变量的副本,这样每个线程都有自己的变量值,没有共享,从而避免了线程不安全的问题。
下面是 ThreadLocal 的一个简单使用示例:
public class ThreadLocalTest { private static final ThreadLocal<SimpleDateFormat> threadLocal = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")); public static SimpleDateFormat safeDateFormat() { return threadLocal.get(); } public static void main(String[] args) throws ExecutionException, InterruptedException { FutureTask<SimpleDateFormat> task1 = new FutureTask<>(ThreadLocalTest::safeDateFormat); FutureTask<SimpleDateFormat> task2 = new FutureTask<>(ThreadLocalTest::safeDateFormat); Thread t1 = new Thread(task1); Thread t2 = new Thread(task2); t1.start(); t2.start(); System.out.println(task1.get() == task2.get());//返回false,表示两个对象不相等 } }
程序中构造了一个线程安全的 SimpleDateFormat ,两个线程取到的是不同的示例对象,这样就保证了线程安全。
2. ThreadLocal 原理浅析
线程 Thread 类内部有两个 ThreadLocalMap 类型的变量:
/* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null; /* * InheritableThreadLocal values pertaining to this thread. This map is * maintained by the InheritableThreadLocal class. */ ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
其中第二个变量的用途是创建可继承父线程变量的子线程,只不过这并不常用,主要介绍第一个。
ThreadLocalMap 是一个用于存储 ThreadLocal 的特殊 HashMap,map 中 key 就是 ThreadLocal,value 是线程变量值。只不过这个 map 并不被 ThreadLocal 持有,而是被 Thread 持有。
当调用 ThreadLocal 类中的 set 方法时,就会创建 Thread 中的 threadLocals 属性。
//ThreadLocal的set方法 public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t);//获取Thread中的ThreadLocalMap if (map != null) map.set(this, value); else createMap(t, value); }
可以看到,最终的 ThreadLocal 对象和变量值并不是创建在 ThreadLocal 内部,而是 Thread 中的 ThreadLocalMap,ThreadLocal 在这里只是充当了代理的作用。
3. ThreadLocal 内存泄漏问题
存储数据的 TheadLocalMap 被 Thread 持有,而不是 ThreadLocal,主要的原因便是 ThreadLocal 的生命周期比 Thread 要长,如果 ThreadLocal 对象一直存在,那么 map 中的线程就不能被回收,容易导致内存泄漏。
而 Thread 持有 ThreadLocalMap,并且 ThreadLocalMap 对 ThreadLocal 的引用还是弱引用,这样当线程被回收时,map 也能够被回收,更加安全。
但是 Java 的这种设计并没有完全避免内存泄漏问题。如果线程池中的线程存活时间过长,那么其持有的 ThreadLocalMap 一直不会被释放。ThreadLocalMap 中的 Entry 对其 value 是强引用的(对 ThreadLocal 是弱引用),这样就算 ThreadLocalMap 的生命周期结束了,但是 value 值并没有被回收。
解决的办法便是手动释放 ThreadLocalMap 中对 value 的强引用,可以使用 TheadLocal 的 remove 方法。在 finally 语句块中执行。例如下面这个简单的示例:
public class ThreadLocalTest { private final ThreadLocal<Integer> threadLocal = new ThreadLocal<>(); public void test(){ //设置变量值 threadLocal.set(10); try { System.out.println(threadLocal.get()); } finally { //释放 threadLocal.remove(); } } }
二、Immutability 模式
1. 不可变的概念
Immutability,即不变模式。可以理解为只要对象一经创建,其状态是不能够被改变的,无法进行写操作。
要实现 Immuatability 模式很简单,将一个类本身及其所有的属性都设为 final ,并且方法都是只读的,需要注意的是,如果类的属性也是引用类型,那么其对应的类也要满足不可变的特性。final 应该都很熟悉了,用它来修饰类和方法,分别表示类不可继承、属性不可改变。
Java 中具备不可变性的类型包括:
- String
- final 修饰的基本数据类型
- Integer、Long、Double 等基本数据类型的包装类
- Collections 中的不可变集合
具备不可变性的类,如果需要有类似修改这样的功能,那么它不会像普通的对象一样改变自己的属性,而是创建新的对象。
下面是 String 的字符串连接方法 concat() 的源码,仔细观察,可以看到最后方法返回的时候,创建了一个新的 Sring 对象:
public String concat(String str) { int otherLen = str.length(); if (otherLen == 0) { return this; } int len = value.length; char buf[] = Arrays.copyOf(value, len + otherLen); str.getChars(buf, len); //创建新的对象 return new String(buf, true); }
而 Collections 工具可以将集合变为不可变的,完全禁止写、修改等操作。示例如下:
//Collections 中构建不可变集合的方法
Collections.unmodifiableList();
Collections.unmodifiableSet();
Collections.unmodifiableMap();
Collections.unmodifiableSortedSet();
Collections.unmodifiableSortedMap();
---
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
//构建不可变集合
List<Integer> unmodifiableList = Collections.unmodifiableList(list);
unmodifiableList.remove(1);//抛出异常
2. 对象池
对于一个不可变性的类,如果频繁的对其进行修改操作,那么一直会创建性新的对象,这样就比较浪费内存空间了,一种解决办法便是利用对象池。
原理也很简单,新建对象的时候,去对象池看是否存在对象,如果存在则直接利用,如果不存在才会创建新的对象,创建之后再将对象放到对象池中。
以长整型的包装类 Long 为例,它缓存了 -128 到 127 的数据,如果创建的是这个区间的对象,那么会直接使用缓存中的对象。例如 Long 中的 valueOf 方法就用到了这个缓存,然后直接返回:
public static Long valueOf(long l) { final int offset = 128; //在这个区间则直接使用缓存中的对象 if (l >= -128 && l <= 127) { // will cache return LongCache.cache[(int)l + offset]; } return new Long(l); }