轻松突击ThreadLocal

时间:2023-03-09 23:33:38
轻松突击ThreadLocal

本文出自

代码大湿

代码大湿

ThreadLocal是用来保存线程的本地变量,可以保证每个线程都有一个自己的变量(包括static变量)。

本文所有代码请点击我

1 看个实际场景。

我们要设计一个序列号生成器,每个线程之间对序列号的获取是是隔离的。初始我们可能会这样设计。使用一个static变量。

首先有一个序列号生成器的接口

package ThreadLocal;
/*
*2016年8月28日 下午2:48:17
*@Author Pin-Wang
*@E-mail 1228935432@qq.com
*/
public interface NumberConstruct {
public int get();
}

生成器的具体实现是:

package ThreadLocal;
/*
*2016年8月28日 下午2:49:34
*@Author Pin-Wang
*@E-mail 1228935432@qq.com
*/
public class ConcreteNumberConstructA implements NumberConstruct{
private volatile static int n=0;
@Override
public synchronized int get() {
return ++n;
}
}

客户端:

package ThreadLocal;

/*
*2016年8月28日 下午2:46:10
*@Author Pin-Wang
*@E-mail 1228935432@qq.com
*/
public class Test {
//不使用ThreadLocal
private static NumberConstruct numberConstruct=new ConcreteNumberConstructA();; //使用ThreadLocal
//private static NumberConstruct numberConstruct=new ConcreteNumberConstructB();; public static void main(String[] args){
//每个线程获取三个序列号
Runnable task=new Runnable() {
public void run() {
for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName()+" "+numberConstruct.get());
}
}
};
//开启是哪个线程
Thread t1=new Thread(task);
Thread t2=new Thread(task);
Thread t3=new Thread(task);
t1.start();
t2.start();
t3.start();
} }

结果;

轻松突击ThreadLocal

可以看到3个线程之间都共享了static变量(没有考虑到共享资源的线程安全),这并不是我们想要的结果。

所以我们用ThreadLocal解决:

生成器的具体实现:

package ThreadLocal;
/*
*2016年8月28日 下午2:49:34
*@Author Pin-Wang
*@E-mail 1228935432@qq.com
*/
public class ConcreteNumberConstructB implements NumberConstruct{
private static ThreadLocal<Integer> n=new ThreadLocal<Integer>(){ @Override
protected Integer initialValue() {
return 0;
}}; @Override
public int get() {
n.set(n.get()+1);
return n.get();
}
}

客户端中将

//不使用ThreadLocal
private static NumberConstruct numberConstruct=new ConcreteNumberConstructA();

替换为

//使用ThreadLocal
private static NumberConstruct numberConstruct=new ConcreteNumberConstructB();

其它均不变

结果:

轻松突击ThreadLocal

这是我们想要的结果。可以看到对于每个共享变量,每个线程之间都有自己的副本,线程之间是隔离的。

2 实现我们自己的ThreadLocal。

ThreadLocal内部其实非常简单。主要是一个同步的HashMap(因为涉及到多线程共享资源),主要有以下几个方法;

//得到当前线程的副本值
get() //设定当前线程的副本值
set() //删除当前线程的副本值
remove() //初始化当前线程的副本值
initialValue()

code;

MyThreadLocal类

package ThreadLocal;
/*
*2016年8月28日 下午3:57:17
*@Author Pin-Wang
*@E-mail 1228935432@qq.com
*/ import java.util.concurrent.ConcurrentHashMap; public class MyThreadLocal<T> {
private ConcurrentHashMap<Thread, T> map=new ConcurrentHashMap<>();
//initialValue()
protected T initialValue(){
//返回null,由子类指定初始值
return null;
} //set()
public void set(T value){
map.put(Thread.currentThread(), value);
}
//get()
public T get(){
if(!map.containsKey(Thread.currentThread())){
T value=initialValue();
map.put(Thread.currentThread(), value);
}
return map.get(Thread.currentThread());
}
//remove()
public void remove(){
map.remove(Thread.currentThread());
} }

ConcreteNumberConstructC 类

package ThreadLocal;
/*
*2016年8月28日 下午2:49:34
*@Author Pin-Wang
*@E-mail 1228935432@qq.com
*/
public class ConcreteNumberConstructC implements NumberConstruct{
private MyThreadLocal<Integer> n=new MyThreadLocal<Integer>(){ @Override
protected Integer initialValue() {
return 0;
}}; @Override
public int get() {
n.set(n.get()+1);
return n.get();
}
}

将客户端中的

//使用ThreadLocal
private static NumberConstruct numberConstruct=new ConcreteNumberConstructB();

替换为

//使用自己的MyThreadLocal
private static NumberConstruct numberConstruct=new ConcreteNumberConstructC();

结果:

轻松突击ThreadLocal

总结:如果你需要多个线程之间共享变量的时候,想下是否需要考虑线程安全的问题,如果需要则可以使用ThreadLocal简单解决。

本文出自

代码大湿

代码大湿