前置证明:从共享不安全num++到线程拥有自己的ThreadLocal变量
我们先知道ThreadLocal是线程中的变量,ThreadLocal为我们提供了get,set和remove方法。
我们先创建一个类,里面有一个变量并且有get,set和remove方法
package com.hzt.learnthreadlocal.learnwrite;
/**
* @Author: hzt
* @Date: 2022/2/24 19:42
*/
public class MyThreadLocal<T> {
T value;
public T init(){
return null;
}
public T get(){
if (value==null){
//为空证明第一次调用,得先赋值再获取
value = init();
}
return value;
}
public void set(T v){
value = v;
}
}
在这个类里面我们提供了get,set和init方法,remove 方法暂时不实现
然后我们编写接口,传入一个整形,并请求一次加一操作
package com.hzt.learnthreadlocal.controller;
import com.hzt.learnthreadlocal.learnwrite.MyThreadLocal;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @Author: hzt
* @Date: 2022/2/24 11:29
*/
@RestController
public class TestMyThreadLocal {
//重载init方法将初始值传入进MyThreadLocal
private MyThreadLocal<Integer> num = new MyThreadLocal<Integer>(){
@Override
public Integer init() {
return 0;
}
};
@RequestMapping("/get")
public Integer getNum(){
return num.get();
}
@RequestMapping("/add")
public Integer add(){
num.set(num.get()+1);
return num.get();
}
@RequestMapping("/reset")
public Integer resetNum(){
num.set(0);
return num.get();
}
}
可以发现我们所写的程序并不能实现我们想要的数据安全的效果,原因就是我们上面实现的不是在线程内部的变量,而是另外一个全局变量,就类似于我们实现的num++这样子的操作一样。
基于上面的代码,我们可以有一下思路,我们可以建一个可以标记当前线程的为key,value为一个hashMap的共享hashMap,然后取和拿都是基于当前线程,但是要注意,我们这个hashMap是一个共享临界区,也就是说,我们在线程第一次去获取map的时候,如果为空,就会进行设置值,这时候我们要注意,因为hashMap是线程不安全的,也就是说,当多个线程一起第一次进去的设置值的时候,可能会造成hashMap的扩容,而hashMap的扩容是不安全的,所以我们需要在线程第一次进去设置值的时候为线程加锁。具体代码如下:
package com.hzt.learnthreadlocal.learnwrite;
import java.util.HashMap;
/**
* @Author: hzt
* @Date: 2022/2/24 19:42
*/
public class MyThreadLocal01<T> {
// 每个线程里面都会维护一个线程本地变量的hashMap,子HashMap中是以当前对象this为key,值为value 这个变量是线程共享的
static HashMap<Thread,HashMap<MyThreadLocal01<?>,Object>> threadLocalMap= new HashMap<>();
// 需要加锁
synchronized static HashMap<MyThreadLocal01<?>,Object> getMap(){
Thread thread = Thread.currentThread();
//如果为空,就放置一个空的hashmap
if (!threadLocalMap.containsKey(thread)){
//因为hashMap不是线程安全的,多个线程可能同时执行到这句话,导致hashMap扩展,hashMap扩展的时候是不安全的
threadLocalMap.put(thread,new HashMap<MyThreadLocal01<?>,Object>());
}
return threadLocalMap.get(thread);
}
public T init(){
return null;
}
public T get(){
HashMap<MyThreadLocal01<?>, Object> map = getMap();
if (!map.containsKey(this)){
map.put(this,init());
}
return (T) map.get(this);
}
public void set(T v){
HashMap<MyThreadLocal01<?>, Object> map = getMap();
map.put(this,v);
}
}
接口代码:
package com.hzt.learnthreadlocal.controller;
import com.hzt.learnthreadlocal.learnwrite.MyThreadLocal01;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
/**
* @Author: hzt
* @Date: 2022/2/24 11:29
*/
@RestController
public class TestMyThreadLocal01 {
Map<Thread,Integer> map = new HashMap<>();
private MyThreadLocal01<Integer> num = new MyThreadLocal01<Integer>(){
@Override
public Integer init() {
return 0;
}
};
@RequestMapping("/get")
public Integer getNum(){
int res = 0;
for(Map.Entry<Thread, Integer> entry : map.entrySet()){
res+=entry.getValue();
}
return res;
}
@RequestMapping("/getOne")
public Integer getOne(){
return num.get();
}
@RequestMapping("/add")
public Integer add(){
num.set(num.get()+1);
map.put(Thread.currentThread(),num.get());
return num.get();
}
@RequestMapping("/reset")
public Integer resetNum(){
num.set(0);
return num.get();
}
}
使用jmeter的测试结果如下
可以看到这次的总数 是正确的,通过请求getone接口获取每个线程内部的情况,可以看到每个线程的内部都是不一样 的数字,通过断点查看map情况,可以看到map的数量有两百个(因为spring容器一开始默认的连接就是200),而且每个entity内部的value都不一样。