手动实现threadlocal

前置证明:从共享不安全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都不一样。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GnIhIKQl-1649319906239)(C:\Users\hzt\AppData\Roaming\Typora\typora-user-images\image-20220225150228739.png)]

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值