当一个类只需要在全局只需要一份实例存在,可以使用单例模式实例该类,比如mybatis的错误日志打印类ErrorContext,现在利用饿汉模式实现一个单例类,这个类保证了程序每个地方拿到的实例都是同一个。
public class ThreadLocalLearn { private static ThreadLocalLearn threadLocalLearn = new ThreadLocalLearn(); private ThreadLocalLearn() { // 单例模式 } public static ThreadLocalLearn instance() { return threadLocalLearn; } }
这个类目前是线程安全的,但是当这个类含有私有属性,用来记录最终打印的内容时,这样的实现是线程不安全的。
public class ThreadLocalLearn { // 线程不安全的单例模式 private static ThreadLocalLearn threadLocalLearn = new ThreadLocalLearn(); private String name = null; private Integer age = null; private ThreadLocalLearn() { // 单例模式 } public static ThreadLocalLearn unSafeInstance() { return threadLocalLearn; }
}
一个线程设置name时,另一个线程可能同时设置了age,导致name和age并非同一个线程赋的值。
而为了一个类在多线程中针对每个线程的单例模式实现,一种设计方法需要用到ThreadLocal,简要的来说,每个线程都有一个独立的对象区,而ThreadLocal就是给线程设置或者获取线程对象的方法。
public class ThreadLocalLearn { // 线程安全的单例模式 private static final ThreadLocal<ThreadLocalLearn> LOCAL = new ThreadLocal<>(); private String name = null; private Integer age = null; private ThreadLocalLearn() { // 单例模式 } public static ThreadLocalLearn safeInstance() { ThreadLocalLearn threadLocalLearn = LOCAL.get(); if (threadLocalLearn == null) { threadLocalLearn = new ThreadLocalLearn(); LOCAL.set(threadLocalLearn); } return threadLocalLearn; } }
当ThreadLoal调用set方法时,会将实例出的ThreadLocalLearn对象绑定到当前线程上,get方法时会从当前线程获取这个对象,从而达到针对每个线程的单例作用,由于每个线程获取到的ThreadLocalLearn都是自身线程创建的,也就不存在name,age设置冲突问题。
ThreadLocal的具体实现是:
1.对每个Thread,都维护了一个Map(ThreadLocal.ThreadLocalMap),用于绑定ThreadLocal Set的实例集合
2.ThreadLocal 调用Get方法时,首先获取当前线程(Thread.currentThread()),再获取ThreadLocalMap,像使用HashMap一样Get出对象(Key为ThreadLocal<T> 中的T, 比如ThreadLocalLearn)
3. Set 与(2)类型,实现非常简单。
------------------------------------------------------------------------------------------------------------------------------------------------------------------
完整例子代码:
public class ThreadLocalLearn { // 线程不安全的单例模式 private static ThreadLocalLearn threadLocalLearn = new ThreadLocalLearn(); // 线程安全的单例模式 private static final ThreadLocal<ThreadLocalLearn> LOCAL = new ThreadLocal<>(); private String name = null; private Integer age = null; private ThreadLocalLearn() { // 单例模式 } public static ThreadLocalLearn unSafeInstance() { return threadLocalLearn; } public static ThreadLocalLearn safeInstance() { ThreadLocalLearn threadLocalLearn = LOCAL.get(); if (threadLocalLearn == null) { threadLocalLearn = new ThreadLocalLearn(); LOCAL.set(threadLocalLearn); } return threadLocalLearn; } public ThreadLocalLearn personName(String personName) { this.name = personName; return this; } public ThreadLocalLearn personAge(Integer personAge) { this.age = personAge; return this; } @Override public String toString() { StringBuilder description = new StringBuilder(); if (name != null) { description.append("|Name: "); description.append(this.name); } if (age != null) { description.append("|Age: "); description.append(this.age); } return description.toString(); } public static void main(String[] args) { for (int i = 0; i < 100; i++) { // 线程不安全例子 // Thread thread = new Thread(new UnsafeTester(i)); // 线程安全例子 Thread thread = new Thread(new SafeTester(i)); thread.start(); } } } class UnsafeTester implements Runnable { private Integer index; public UnsafeTester(Integer index) { this.index = index; } @Override public void run() { ThreadLocalLearn threadLocalLearn = ThreadLocalLearn.unSafeInstance(); threadLocalLearn.personAge(index).personName(String.valueOf(index)); System.out.println(threadLocalLearn.toString()); } } class SafeTester implements Runnable { private Integer index; public SafeTester(Integer index) { this.index = index; } @Override public void run() { ThreadLocalLearn threadLocalLearn = ThreadLocalLearn.safeInstance(); threadLocalLearn.personAge(index).personName(String.valueOf(index)); System.out.println(threadLocalLearn.toString()); } }
参照:
1. mybatis3 ErrorContext 实现。