ThreadLocal

引入主题

public class ThreadLocalDemo {

    //希望每个线程获得的num = 0

    static ThreadLocal<Integer> local = new ThreadLocal() {

        protected Integer initialValue() {

            return 1;

        }

    };

    static ThreadLocal<Integer> local2 = new ThreadLocal() {

        protected Integer initialValue() {

            return 2;

        }

    };

    static ThreadLocal<Integer> local3 = new ThreadLocal() {

        protected Integer initialValue() {

            return 3;

        }

    };

    public static void main(String[] args) {

        Thread[] threads = new Thread[5];

        for (int i = 0; i < 5; i++) {

            threads[i] = new Thread(()->{

                int num = local.get();

                local.set(num +=5);

                System.out.println(Thread.currentThread().getName() + "  " +local.get());

            });

        }

        for (int i = 0; i < 5; i++) {

            threads[i].start();

        }

    }

}

每个线程对全局共享变量的修改 互相之间不会产生影响 实现线程的隔离

应用场景:数据库连接  全局用户信息存储

经典案例  SimpleDateFormat 非线程安全的

public class ThreadLocalExemple {

    //SimpleDateFormat线程不安全

    private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    public static Date parse(String strDate) {

        try {

            return sdf.parse(strDate);

        catch (ParseException e) {

            e.printStackTrace();

        }

        return null;

    }

    public static void main(String[] args) {

        ExecutorService executorService = Executors.newFixedThreadPool(10);

        for (int i = 0; i < 20; i++) {

            executorService.execute(()->{

                System.out.println(parse("2021-10-07 16:36:48"));

            });

        }

    }

}

代码运行结果:

Thu Oct 07 16:36:48 CST 2021

Thu Oct 07 16:36:48 CST 2021

Thu Oct 07 16:36:48 CST 2021

Thu Oct 07 16:36:48 CST 2021

Thu Oct 07 16:36:48 CST 2021

Thu Oct 07 16:36:48 CST 2021

Thu Oct 07 21:36:02 CST 20212021

Thu Oct 07 16:36:48 CST 2021

Thu Oct 07 16:36:48 CST 2021

Thu Oct 07 16:36:48 CST 2021

Thu Oct 07 16:36:48 CST 2021

Thu Oct 07 16:36:48 CST 2021

Thu Oct 07 16:36:48 CST 2021

Thu Oct 07 16:36:48 CST 2021

java.lang.NumberFormatException: multiple points

    at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1890)

    at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110)

    at java.lang.Double.parseDouble(Double.java:538)

    at java.text.DigitList.getDouble(DigitList.java:169)

    at java.text.DecimalFormat.parse(DecimalFormat.java:2056)

    at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:2162)   //存在对共享变量numberFormat的操作   通过查看底层源码 发现针对numberFormat的操作不安全 需要外部干预

    at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)

    at java.text.DateFormat.parse(DateFormat.java:364)

    at com.example.demo.concurrent.threadlocal.ThreadLocalExemple.parse(ThreadLocalExemple.java:22)

    at com.example.demo.concurrent.threadlocal.ThreadLocalExemple.lambda$main$0(ThreadLocalExemple.java:33)

    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)

    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)

    at java.lang.Thread.run(Thread.java:745)

查看SimpleDateFormat 源码

* Date formats are not synchronized.

* It is recommended to create separate format instances for each thread.

* If multiple threads access a format concurrently, it must be synchronized externally

public class SimpleDateFormat extends DateFormat {}

public abstract class DateFormat extends Format {

    protected Calendar calendar;

     

    //全局变量  存在竞争

    protected NumberFormat numberFormat;

解决方式

1,可以加锁,避免竞争

public synchronized static Date parse(String strDate) {
        try {
            return sdf.parse(strDate);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return null;
    }

2,线程隔离  让每个线程使用的SimpleDateFormat  都是各自的

public class ThreadLocalUpgradeExample {

    private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    private static ThreadLocal<DateFormat> dateFormatThreadLocal = new ThreadLocal<>();

    public static  DateFormat getDateFormat()

    {

        DateFormat dateFormat = dateFormatThreadLocal.get();

        if(null == dateFormat) {

            dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

            //要在当前线程的范围内设置一个SimpleDateFormat对象

            dateFormatThreadLocal.set(dateFormat);

        }

        return dateFormat;

    }

    public static Date parse(String strDate) {

        //从当前线程的范围内获得一个DateFormat

        try {

            return getDateFormat().parse(strDate);

        catch (ParseException e) {

            e.printStackTrace();

        }

        return null;

    }

    public static void main(String[] args) {

        ExecutorService executorService = Executors.newFixedThreadPool(10);

        for (int i = 0; i < 20; i++) {

            executorService.execute(()->{

                System.out.println(parse("2021-10-07 16:36:48"));

            });

        }

    }

}

常用方法

set()

     在当前线程范围内,设置一个值存储到ThreadLocal中,这个值仅仅对当前线程可见,相当于在当前线程范围内建立一个副本

get()

     从当前线程范围内取出set()方法设置的值

remove()

    移除当前线程存储的值

withInitial()

      java8提供,快速初始化一个threadLocal

原理分析

能够实现线程的隔离  线程私有

存储副本,需要一个容器  数据结构

get()方法  key为当前线程

下方示例图 表示一个线程会有多个ThreadLocal  对象

private ThreadLocal  local1;

private ThreadLocal  local2;

 

源码分析

set()

public void set(T value) {

    Thread t = Thread.currentThread();

    ThreadLocalMap map = getMap(t);  //当前线程变量的副本

    if (map != null)

        map.set(this, value);

    else

        createMap(t, value);

}

//初始化

void createMap(Thread t, T firstValue) {

    //this  表示当前threadLocal对象的实例

    //value  表示初始化的时候或者调用set()方法设置的值

    t.threadLocals = new ThreadLocalMap(this, firstValue);

}

//firstKey  是指向ThreadLocal 对象的弱引用  假如local 被回收掉 key=null

ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {

    table = new Entry[INITIAL_CAPACITY]; // 初始化数组 16

    int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); //计算当前local所处的数组下标

    table[i] = new Entry(firstKey, firstValue); //设置值

    size = 1;

    setThreshold(INITIAL_CAPACITY);

}

//斐波那契数列  黄金分割  均匀分布

private final int threadLocalHashCode = nextHashCode();

private static final int HASH_INCREMENT = 0x61c88647;

private static int nextHashCode() {

    return nextHashCode.getAndAdd(HASH_INCREMENT);

}

private void set(ThreadLocal<?> key, Object value) {

    Entry[] tab = table;

    int len = tab.length;

    int i = key.threadLocalHashCode & (len-1); //不同的threadlocal 得到的下标值可能一样

    //开放寻址法  线性探索  解决hash冲突   每次从位置i 开始查找

    for (Entry e = tab[i];

         e != null;

         e = tab[i = nextIndex(i, len)]) {

        ThreadLocal<?> k = e.get();

        // i的位置已经存在了值 直接替换 不需要做线性探索

        if (k == key) {

            e.value = value;

            return;

        }

        //如果key==null 则进行replaceStaleEntry() 替换空余的数组

        if (k == null) {

            replaceStaleEntry(key, value, i);

            return;

        }

    }

    tab[i] = new Entry(key, value);

    int sz = ++size;

    if (!cleanSomeSlots(i, sz) && sz >= threshold)

        rehash();

}

//把当前的value保存到entry 数组中    清理无效的key  冲突key 做覆盖

//扩大搜索范围 如果当前key 无效猜测一下key周围的key是否也无效

key的清理过程 有四种情况

1,向前有脏Entry 向后查找到可覆盖的Entry

2,向前有脏Entry 向后未查找到可覆盖的Entry

3,向前没有脏Entry 向后查找到可覆盖的Entry

4,向前没有脏Entry 向后未查找到可覆盖的Entry

private void replaceStale Entry(ThreadLocal<?> key, Object value,

                               int staleSlot) {

    Entry[] tab = table;

    int len = tab.length;

    Entry e;

    int slotToExpunge = staleSlot; //定义需要被清理的位置

    for (int i = prevIndex(staleSlot, len);  //从slotToExpunge位置往前查找 查找到不为空的后一个位置

         (e = tab[i]) != null;

         i = prevIndex(i, len))

        if (e.get() == null)

            slotToExpunge = i;

    for (int i = nextIndex(staleSlot, len); //从slotToExpunge位置往后查找 找到不为空的位置

         (e = tab[i]) != null;

         i = nextIndex(i, len)) {

        ThreadLocal<?> k = e.get();

        // 可覆盖

        if (k == key) {

            e.value = value;

            tab[i] = tab[staleSlot];

            tab[staleSlot] = e;

            if (slotToExpunge == staleSlot)

                slotToExpunge = i;

            cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);/清理查找范围内无效的key  循环遍历[slotToExpunge 以后的元素 找出为null的清理

            return;

        }

        if (k == null && slotToExpunge == staleSlot)

            slotToExpunge = i;

    }

    tab[staleSlot].value = null;

    tab[staleSlot] = new Entry(key, value);

    if (slotToExpunge != staleSlot)

        cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);

}

 

key的清理过程1 示意图   向前有脏Entry slotToExpunge值发生变化 向后查找到可覆盖的Entry 发现冲突 覆盖值并且交换位置

 

key的清理过程2 示意图   向前有脏Entry slotToExpunge值发生变化 向后未查找到可覆盖的Entry  往后查找没有key冲突直接覆盖staleSlot处的值不需要替换

 

key的清理过程3 示意图   向前没有脏Entry slotToExpunge值不变化 向后查找到可覆盖的Entry 发现key冲突 覆盖值并且交换位置

 

key的清理过程4 示意图   向前没有脏Entry slotToExpunge值不变化 向后查未找到可覆盖的Entry  往后查找没有key冲突直接覆盖staleSlot处的值不需要替换

 

具体的清理代码如下,就是该entry[i]位置 元素=null  

private int expungeStaleEntry(int staleSlot) {

    Entry[] tab = table;

    int len = tab.length;

    tab[staleSlot].value = null;

    tab[staleSlot] = null;

    size--;

    Entry e;

    int i;

    for (i = nextIndex(staleSlot, len);

         (e = tab[i]) != null;

         i = nextIndex(i, len)) {

        ThreadLocal<?> k = e.get();

        if (k == null) {

            e.value = null;  //清理

            tab[i] = null;   //清理

            size--;

        else {

            int h = k.threadLocalHashCode & (len - 1);

            if (h != i) {

                tab[i] = null;

                while (tab[h] != null)

                    h = nextIndex(h, len);

                tab[h] = e;

            }

        }

    }

    return i;

}

解决hash冲突的常用方法

方法

应用场景

备注

开放寻址法-线性探索threadlocal
链式寻址法hashmap
再hash

java对象引用关系

强引用 :存在强引用的对象=null 后,垃圾回收器不会回收它

软引用

弱引用:存在弱引用的对象=null 后,垃圾回收器会回收它

虚饮用

get()  

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

}

内存泄露的问题

key=null ,value没有引用指向 清理无效key 的过程可能会清理不到,占用空间 浪费内存 引发内存泄露

解决方法:使用完以后 调用remove()方法 不存在空引用指向 内存就会被释放

public void remove() {

    ThreadLocalMap m = getMap(Thread.currentThread());

    if (m != null)

        m.remove(this);

}

private void remove(ThreadLocal<?> key) {

    Entry[] tab = table;

    int len = tab.length;

    int i = key.threadLocalHashCode & (len-1);

    for (Entry e = tab[i];

         e != null;

         e = tab[i = nextIndex(i, len)]) {

        if (e.get() == key) {

            e.clear();

            expungeStaleEntry(i);

            return;

        }

    }

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

tudou186

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

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

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

打赏作者

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

抵扣说明:

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

余额充值