Java高效并发(十一)----无同步方案实现线程安全ThreadLocal

前面我们学习了同步控制线程安全,同步的实现有本地锁Synchronized关键字以及重入锁ReentrantLock。同步就是控制访问资源的角度实现线程安全,但是实现线程安全,不一定非得同步,也可以从增加共享资源的角度出发。

人手一支笔(ThreadLocal)

如果一个变量要被线程独享,可以通过java.lang.ThreadLocal类实现线程本地存储的功能。既然是本地存储的,那么就只有当前线程可以访问,自然是线程安全的。

package xidian.lili.ThreadLocal;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadLockDemo {
	private static ThreadLocal<SimpleDateFormat> t1=new ThreadLocal<SimpleDateFormat>();
	public static class ParseDate implements Runnable
	{
		int i=0;
		
		public ParseDate(int i) {
			this.i = i;
		}
		@Override
		public void run() {
			if(t1.get()==null){
				t1.set(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
				}
			try {
				Date t=t1.get().parse("2018-07-29 19:29:" + i%60);
				System.out.println(Thread.currentThread().getId()+":"+t);
			} catch (ParseException e) {
				e.printStackTrace();
			}
			}			
		}	
	public static void main(String[] args) {
		ExecutorService es=Executors.newFixedThreadPool(5);
		for(int i=0;i<10;i++){
			es.execute(new ParseDate(i));
		}

	}

}

通过上面的例子我们知道,ThreadLocal支持泛型。是通过set方法和get方法来为线程创建局部变量,通过get方法检查当前线程是否持有SimpleDateFormat对象,不持有就新建一个然后存储到当前线程。这样就不存在资源共享,也就是线程安全的。

ThreadLocal实现原理

 首先我们分析ThreadLocal的set和get方法源码实现。

 get方法首先获取当前线程,然后获得线程的ThreadLocalMap,那说明每个线程内部都维护一个LocalThreadMap,确实我们在Thread源码中发现:

然后我们看ThreadLocal的方法getMap

 

所以我们就知道线程的局部变量是存在线程的threadLocals变量中,那么获取线程的threadLocals如果不为空,以当前ThreadLocal对象为key获得Entry,然后获取value

    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

set方法,首先获取当前线程,然后获取ThreadLocalMap,然后通过ThreadLocal对象为key,传入的对象为value,存入到map中。 

    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

如上我们发现,通过ThreadLocal创建的线程局部变量都是存放在LocalThreadMap中,那么我们在来看一看LocalThreadMap类

 LocalThreadMap类

 它是ThreadLocal的静态内部类,java.lang.ThreadLocal.ThreadLocalMap<T>,它结构类似于Map,可以看到ThreadLocalMap内部是一个Entry数组实现的,初始大小是16,支持扩容,而Entry又是继承了WeakReference,在解释文档我们可以看出,这个map的key一般是ThreadLocal对象,那么当这个对象为空时,这个entry就可以别回收

 

 

所以在当我们使用线程池的时候,当前线程未必会退出,那么在线程中的threadLocals变量(LocalThreadMap的实例)中的这些局部变量就会发生内存泄露,所以当你使用完ThreadLocal时,可以调用ThreadLocal.remove()来删除一个LocalThread对象,防止内存泄漏。

这是ThreadLocal的remove方法,它又会调用LocalThreadMap中的remove方法来清空threadLocals变量中的entry

 

还有一个方法就是,你让ThreadLocal对象为空,那么这个LocalThread对应的所有线程的局部变量也会被回收,因为LocalThreadMap的Entry继承弱引用,那么jvm可能就会把这个ThreadLocal所在的entry回收。 

猜你喜欢

转载自blog.csdn.net/wangdongli_1993/article/details/81282761
今日推荐