为了可以解决并发问题,可以考虑将并发问题的源头共享变量变得不共享
没有共享,就没有伤害
在多线程中将共享变量变得不共享,可以考虑针对每个线程,都让其拥有自己的变量,彼此不共享,就没有并发问题了
在Java中,提供的符合上面的思想的实现为 线程本地存储 ThreadLoacl
ThreadLoacl的简单使用可以看下面的示例代码
static class ThreadId {
static final AtomicLong nextId = new AtomicLong(0);
//定义ThreadLocal变量
static final ThreadLocal<Long> tl = ThreadLocal.withInitial(() -> nextId.getAndIncrement());
//此方法会为每个线程分配一个唯一的Id
static long get() {
return tl.get();
}
}
如果是同一个线程调用这个类,会获得相同的Id,不同的线程获得不同的Id
那么对于其的工作原理:
ThreadLoacl的目标是让不同的线程有不同的变量V,那么可以设置一个Map,其的key是线程,Value是每个线程对应的变量
class MyThreadLocal<T> {
Map<Thread, T> locals = new ConcurrentHashMap<>();
//获取线程变量
T get() {
return locals.get(
Thread.currentThread());
}
//设置线程变量
void set(T t) {
locals.put(
Thread.currentThread(), t);
}
}
在我们的猜想中,ThreadLocal的实现可以基本如上
但是实际上,对于ThreadLocal的实现就没有这么简单了
其实现为,在Thread这个类中有一个私有属性 threadLocals,其类型就是ThreadLocalMap,其key为ThreadLocal
class Thread {
//内部持有ThreadLocalMap
ThreadLocal.ThreadLocalMap
threadLocals;
}
class ThreadLocal<T>{
public T get() {
//首先获取线程持有的
//ThreadLocalMap
ThreadLocalMap map = Thread.currentThread().threadLocals;
//在ThreadLocalMap中
//查找变量
Entry e = map.getEntry(this);
return e.value;
}
static class ThreadLocalMap{
//内部是数组而不是Map
Entry[] table;
//根据ThreadLocal查找Entry
Entry getEntry(ThreadLocal key){
//省略查找逻辑
}
//Entry定义
static class Entry extends
WeakReference<ThreadLocal>{
Object value;
}
}
}
ThreadLocal只是作为一个工具类,内部不持有和线程相关的数据,因为是一个静态类型的数据,将所有线程相关的线程存储的Thread里面,
而且不容易产生内存泄漏,将ThreadLocalMap交给Thread持有,而且引用还是弱引用,只要Thread对象可以被回收,那么ThreadLocalMap就能被回收,防止内存泄漏,但是还是可能出现内存泄漏的问题
如果选择实现一个额外的类去维护,那么即使Vavle的生命周期结束了,由于在整个Map的Entry中是被强引用,也无法被回收掉
而且其有一个实现子类 InheritableThreadLocal来支持从子线程继承父线程的线程变量,但是不建议使用,因为可能导致业务逻辑混乱
总而言之,线程本地存储模式是一个避免共享变量的方案,没有共享,就没有并发问题,这是一个解决并发问题的常用思路