ThreadLocal-单例模式下高并发线程安全

在多例的情况下,每个对象在堆中声明内存空间,多线程对应的Java栈中的句柄或指针指向堆中不同的对象,对象各自变量的变更只会印象到对应的栈,也就是对应的线程中,不会影响到其它线程。所以多例的情况下不需要考虑线程安全的问题,因为一定是安全的。


而在单例的情况下却完全不一样了,在堆中只有一个对象,多线程对应的Java栈中的句柄或指针指向同一个对象,方法的参数变量和方法内变量是线程安全的,因为每执行一个方法,都会在独立的空间创建局部变量,它不是共享的资源。但是成员变量只有一份,所有指向堆中该对象的句柄或指针都可以随时修改和读取它,所以是非线程安全的。

 


为了解决线程安全的问题,我们有3个思路:

第一每个线程独享自己的操作对象,也就是多例,多例势必会带来堆内存占用、频繁GC、对象初始化性能开销等待等一些列问题。

第二单例模式枷锁,典型的案例是HashTableHashMap,对读取和变更的操作用synchronized限制起来,保证同一时间只有一个线程可以操作该对象。虽然解决了内存、回收、构造、初始化等问题,但是势必会因为锁竞争带来高并发下性能的下降。

第三个思路就是今天重点推出的ThreadLocal。单例模式下通过某种机制维护成员变量不同线程的版本。


 

假设三个人想从镜子中看自己,第一个方案就是每人发一个镜子互不干扰,第二个方案就是只有一个镜子,一个人站在镜子前其他人要排队等候,第三个方案就是我这里发明了一种“魔镜”,所有人站在镜子前可以并且只能看到自己!!!

 

主程序:

public static void main(String[] args) {
		//Mirror是个单例的,只构建了一个对象
		Mirror mirror = new Mirror("魔镜");
		
		//三个线程都在用这面镜子
		MirrorThread thread1 = new MirrorThread(mirror,"张三");
		MirrorThread thread2 = new MirrorThread(mirror,"李四");
		MirrorThread thread3 = new MirrorThread(mirror,"王二");
		
		thread1.start();
		thread2.start();
		thread3.start();
	}

很好理解,创建了一面镜子,3个人一起照镜子。

MirrorThread

public class MirrorThread extends Thread{
	private Mirror mirror;
	
	private String threadName;
	
	public MirrorThread(Mirror mirror, String threadName){
		this.mirror = mirror;
		this.threadName = threadName;
	}
		
	//照镜子
	public String lookMirror() {
		return threadName+" looks like "+ mirror.getNowLookLike().get();
	}
	
	//化妆
	public void makeup(String makeupString) {
		mirror.getNowLookLike().set(makeupString);
	}
	
	@Override
    public void run() {
		int i = 1;//阈值
		while(i<5) {
			try {
				long nowFace = (long)(Math.random()*5000);
				sleep(nowFace);
				StringBuffer sb = new StringBuffer();
				sb.append("第"+i+"轮从");
				sb.append(lookMirror());
				makeup(String.valueOf(nowFace));
				sb.append("变为");
				sb.append(lookMirror());
				System.out.println(sb);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			i++;
		}
	}
}

也很好理解,就是不断的更新自己的外貌同时从镜子里读取自己的外貌。


重点是Mirror:

public class Mirror {
	private String mirrorName;
	
	//每个人要看到自己的样子,所以这里要用ThreadLocal
	private ThreadLocal<String> nowLookLike;
	
	public Mirror(String mirrorName){
		this.mirrorName=mirrorName;
		nowLookLike = new ThreadLocal<String>();
	}

	public String getMirrorName() {
		return mirrorName;
	}

	public ThreadLocal<String> getNowLookLike() {
		return nowLookLike;
	}
}

对每个人长的样子用ThreadLocal类型来表示。

先看测试结果:

第1轮从张三 looks like null变为张三 looks like 3008
第2轮从张三 looks like 3008变为张三 looks like 490
第1轮从王二 looks like null变为王二 looks like 3982
第1轮从李四 looks like null变为李四 looks like 4390
第2轮从王二 looks like 3982变为王二 looks like 1415
第2轮从李四 looks like 4390变为李四 looks like 1255
第3轮从王二 looks like 1415变为王二 looks like 758
第3轮从张三 looks like 490变为张三 looks like 2746
第3轮从李四 looks like 1255变为李四 looks like 845
第4轮从李四 looks like 845变为李四 looks like 1123
第4轮从张三 looks like 2746变为张三 looks like 2126
第4轮从王二 looks like 758变为王二 looks like 4516

OK,一面镜子所有人一起照,而且每个人都只能看的到自己的变化,这就达成了单例线程安全的目的。

 

我们来细看下它是怎么实现的。

先来看Thread

Thread中维护了一个ThreadLocal.ThreadLocalMapthreadLocals = null; ThreadLocalMap这个MapkeyThreadLocalvalue是维护的成员变量。现在的跟踪链是Thread->ThreadLocalMap-><ThreadLocal,Object>,那么我们只要搞明白Thread怎么跟ThreadLocal关联的,从线程里找到自己关心的成员变量的快照这条线就通了。

ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
            table = new Entry[INITIAL_CAPACITY];
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }

再来看ThreadLocal:它里面核心方法两个get()set(T)

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();
    }

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


方法里通过Thread.currentThread()的方法得到当前线程,然后做为key存储到当前线程对象的threadLocals中,也就是TreadLocalMap中。

OK,这样整个关系链已经建立,真正要去访问的成员变量在一个map中,key是线程号,值是属于该线程的快照。

ThreadLocal里还有map的创建createMap(t, value)、取值时对象的初始值setInitialValue()、线程结束时对象的释放remove()等细节,有兴趣的可以继续跟进了解下。

 

ThreadLocal应用其实很多,例如Spring容器中实例默认是单例的,transactionManager也一样,那么事务在处理时单例的manager是如何控制每个线程的事务要如何处理呢,这里面就应用了大量的ThreadLocal






“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:技术黑板 设计师:CSDN官方博客 返回首页
评论 3

打赏作者

牛麦康纳

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值