如果在多线程并发环境中,一个可变对象涉及到共享与竞争,那么该可变对象就一定会涉及到线程间同步操作,这是多线程并发问题。
若可变对象将作为线程私有对象,可通过ThreadLocal进行管理,实现线程间私有对象隔离的目的。
首先,ThreadLocal
不是用来解决共享对象的多线程访问问题的(一般情况下,通过ThreadLocal.set() 到线程中的对象是该线程自己使用的对象,其他线程是不需要访问的,也访问不到的。各个线程中访问的是不同的对象).
ThreadLocal使得各线程能够保持各自独立的一个对象,并不是通过ThreadLocal.set()来实现的,而是通过每个线程中的new 对象的操作来创建的对象,每个线程创建一个,不是什么对象的拷贝或副本。通过ThreadLocal.set()将这个新创建的对象的引用保存到各线程的自己的一个map中,每个线程都有这样一个map,执行ThreadLocal.get()时,各线程从自己的map中取出放进去的对象,因此取出来的是各自自己线程中的对象,ThreadLocal实例是作为map的key来使用的。
如果ThreadLocal.set()进去的东西本来就是多个线程共享的同一个对象,那么多个线程的ThreadLocal.get()取得的还是这个共享对象本身,还是有并发访问问题。
总之,ThreadLocal不是用来解决对象共享访问问题的,而主要是提供了保持对象的方法和避免参数传递的方便的对象访问方式。归纳了两点:
1。每个线程中都有一个自己的ThreadLocalMap类对象,可以将线程自己的对象保持到其中,各管各的,线程可以正确的访问到自己的对象。
2。将一个共用的ThreadLocal静态实例作为key,将不同对象的引用保存到不同线程的ThreadLocalMap中,然后在线程执行的各处通过这个静态ThreadLocal实例的get()方法取得自己线程保存的那个对象,避免了将这个对象作为参数传递的麻烦。
源码
threadlocal中的get和set方法
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
return (T)e.value;
}
return setInitialValue();
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
1、ThreadLocalMap,是ThreadLocal的静态内部类
2、ThreadLocalMap把其外部类ThreadLocal的实例对象作为key,把要管理的可变对象作为value
3、ThreadLocalMap的实例对象,由当前线程对象Thread的实例持有,而不是由ThreadLocal持有
然后继续看setInitialValue()方法和initialValue()方法:
/**
* Variant of set() to establish initialValue. Used instead
* of set() in case user has overridden the set() method.
*
*
@return
the initial value
*/
private
T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if
(map !=
null)
map.set(this,
value);
else
createMap(t, value);
return
value;
}
protected
T initialValue() {
return
null;
}
补充说明:
1、initialValue()方法声明为protected,目的就是让子类覆盖重新的,如果不覆盖重写,则返回null
2、如果没有重写initialValue()方法,ThreadLocal对象直接调用get()方法,最终从setInitialValue()返回的对象为null
对象持有图,手画的
看一下createMap(t, value)方法:
/**
* Create the map associated with a ThreadLocal. Overridden in
* InheritableThreadLocal.
*
* @param t the current thread
* @param firstValue value for the initial entry of the map
*/
void createMap(Thread t, T firstValue) {
//直接new一个新的ThreadLocalMap实例,封装进入当前线程对象t
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
最后看一下set(T value)方法:
/**
* Sets
the
current thread's
copy
of
this thread-local
variable
*
to
the
specified value. Most subclasses will have no need
to
* override this method, relying solely
on
the
{@link
#initialValue}
* method
to
set
the
values
of
thread-locals.
*
* @param value
the
value
to
be stored
in
the
current thread's
copy
of
* this thread-local.
*/
public void
set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if
(map != null)
map.set(this,
value);
else
createMap(t, value);
}
补充解释:
1、set方法用来修改或者初始化ThreadLocal管理的变量对象。
2、ThreadLocal对象调用get方法获取变量的,要么重写initialValue()方法,要么主动调用set方法,否则将返回null。
注意:ThreadLocalMap
中的entity继承了弱引用(1.3版以前是Map的,弱引用解释放在了下面),具体用多大作用我还不清楚。
弱引用:
A a = new A();
B b = new B(a);
B的默认构造函数上是需要一个A的实例作为参数的,那么这个时候 A和B就产生了依赖,也可以说a和b产生了依赖,我们再用一个接近内存结构的图来表达:
a是对象A的引用,b是对象B的引用,对象B同时还依赖对象A,那么这个时候我们认为从对象B是可以到达对象A的。
于是我又修改了一下代码
A a = new A();
B b = new B(a);
a = null;
A对象的引用a置空了,a不再指向对象A的地址,我们都知道当一个对象不再被其他对象引用的时候,是会被GC回收的,很显然及时a=null,那么A对象也是不可能被回收的,因为B依然依赖与A,在这个时候,造成了内存泄漏!
那么如何避免上面的例子中内存泄漏呢?
很简单:
A a = new A();
B b = new B(a);
a = null;
b = null;
这个时候B对象再也没有被任何引用,A对象只被B对象引用,尽管这样,GC也是可以同时回收他们俩的,因为他们处于不可到达区域。
弱引用来了!
A a = new A();
WeakReference wr = new WeakReference(a);
//B b = new B(a);
当 a=null ,这个时候A只被弱引用依赖,那么GC会立刻回收A这个对象,这就是弱引用的好处!他可以在你对对象结构和拓扑不是很清晰的情况下,帮助你合理的释放对象,造成不必要的内存泄漏!!