精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

ThreadLocal 實(shí)踐與源碼解析

開發(fā)
ThreadLocal 是 Java 提供的一種簡單而強(qiáng)大的機(jī)制,用于實(shí)現(xiàn)線程局部變量,即每個(gè)線程都有自己的獨(dú)立副本,互不干擾。

在多線程編程中,共享資源的管理和同步一直是開發(fā)人員面臨的挑戰(zhàn)之一。ThreadLocal 是 Java 提供的一種簡單而強(qiáng)大的機(jī)制,用于實(shí)現(xiàn)線程局部變量,即每個(gè)線程都有自己的獨(dú)立副本,互不干擾。這種機(jī)制不僅簡化了并發(fā)編程中的數(shù)據(jù)管理,還提高了代碼的可讀性和可維護(hù)性。

一、詳解ThreadLocal

1.什么是ThreadLocal?它有什么用?

為了保證特定變量對當(dāng)前線程可見,我們就可以使用ThreadLocal關(guān)鍵字,ThreadLocal可以為每個(gè)線程創(chuàng)建這個(gè)變量的副本并存到每個(gè)線程的存儲(chǔ)空間中(關(guān)于這個(gè)存儲(chǔ)空間后文會(huì)展開講述),從而確保共享變量對每個(gè)線程隔離:

2.ThreadLocal基礎(chǔ)使用示例

如上文所說ThreadLocal最典型的用法就是維護(hù)各個(gè)線程各自需要獨(dú)享變量,基于ThreadLocal為每個(gè)將每個(gè)線程的id存到線程內(nèi)部,彼此之間互不影響。

ThreadLocal<String> THREAD_LOCAL = new ThreadLocal<>();

        Thread t1 = new Thread(() -> {
            log.info("t1往THREAD_LOCAL存入變量:[{}]", Thread.currentThread().getName());
            THREAD_LOCAL.set(Thread.currentThread().getName());
            log.info("t1獲取THREAD_LOCAL的值為:[{}]", THREAD_LOCAL.get());
        }, "t1");


        Thread t2 = new Thread(() -> {
            log.info("t2往THREAD_LOCAL存入變量:[{}]", Thread.currentThread().getName());
            THREAD_LOCAL.set(Thread.currentThread().getName());
            log.info("t2獲取THREAD_LOCAL的值為:[{}]", THREAD_LOCAL.get());
            THREAD_LOCAL.remove();
            log.info("t2刪除THREAD_LOCAL的后值為:[{}]", THREAD_LOCAL.get());
        }, "t2");

        t1.start();
        t2.start();

        ThreadUtil.sleep(1,TimeUnit.DAYS);

從輸出結(jié)果可以看出,兩個(gè)線程都用THREAD_LOCAL 在自己的內(nèi)存空間中存儲(chǔ)了變量的副本,彼此互相隔離的使用

21:59:51.351 [t2] INFO MultiApplication - t2往THREAD_LOCAL存入變量:[t2]
21:59:51.351 [t1] INFO MultiApplication - t1往THREAD_LOCAL存入變量:[t1]
21:59:51.358 [t1] INFO MultiApplication - t1獲取THREAD_LOCAL的值為:[t1]
21:59:51.359 [t2] INFO MultiApplication - t2獲取THREAD_LOCAL的值為:[t2]
21:59:51.359 [t2] INFO MultiApplication - t2刪除THREAD_LOCAL的后值為:[null]

二、從兩種應(yīng)用場景來介紹一下ThreadLocal

1.日期格式化工具類

我們創(chuàng)建100個(gè)線程使用同一個(gè)dateFormat完成日期格式化:

 private static Logger logger = LoggerFactory.getLogger(MyThreadLocalDemo3.class);


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


    public static void main(String[] args) throws InterruptedException {

        ExecutorService threadPool = Executors.newFixedThreadPool(100);

        for (int i = 0; i < 100; i++) {
            int finalI = i;
            //線程池中的線程
            threadPool.submit(()->{
                new MyThreadLocalDemo3().caclData(finalI);

            });
        }

        threadPool.shutdown();

    }

    /**
     * 計(jì)算second后的日期
     * @param second
     * @return
     */
    public String caclData(int second){
        Date date=new Date(1000*second);
        String dateStr = dateFormat.format(date);
        logger.info("{}得到的時(shí)間字符串為:{}",Thread.currentThread().getId(),dateStr);
        return dateStr;
    }

從輸出結(jié)果可以看出,間隔幾毫秒的線程出現(xiàn)相同結(jié)果:

基于該問題我們使用ThreadLocal為線程分配SimpleDateFormat副本:

static ThreadLocal<SimpleDateFormat> threadLocal=ThreadLocal.withInitial(()->new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SS"));


    public static void main(String[] args) throws InterruptedException {

        ExecutorService threadPool = Executors.newFixedThreadPool(100);

        for (int i = 0; i < 100; i++) {
            int finalI = i;
            //線程池中的線程
            threadPool.submit(()->{
                new MyThreadLocalDemo3().caclData(finalI);

            });
        }

        threadPool.shutdown();

    }

    /**
     * 計(jì)算second后的日期
     * @param second
     * @return
     */
    public String caclData(int second){
        Date date=new Date(1000*second);
        SimpleDateFormat simpleDateFormat = threadLocal.get();
        String dateStr = simpleDateFormat.format(date);
        logger.info("{}得到的時(shí)間字符串為:{}",Thread.currentThread().getId(),dateStr);
        return dateStr;
    }

2.服務(wù)間調(diào)用的線程變量共享

我們?nèi)粘eb開發(fā)都會(huì)涉及到各種service的調(diào)用,例如某個(gè)controller需要調(diào)用完service1之后再調(diào)用service2。因?yàn)槲覀兊腸ontroller和service都是單例的,所以如果我們希望多線程調(diào)用這些controller和service保證共享變量的隔離,也可以用到ThreadLocal。

為了實(shí)現(xiàn)這個(gè)示例,我們編寫了線程獲取共享變量的工具類:

public class MyUserContextHolder {
    private static ThreadLocal<User> holder = new ThreadLocal<>();

    public static ThreadLocal<User> getHolder() {
        return holder;
    }
}

service調(diào)用鏈?zhǔn)纠缦?,筆者創(chuàng)建service1之后,所有線程復(fù)用這個(gè)service完成了調(diào)用,并且在服務(wù)間調(diào)用直接通過ThreadLocal完成了線程副本共享:

public class MyThreadLocalGetUserId {

    private static Logger logger = LoggerFactory.getLogger(MyThreadLocalGetUserId.class);

    private static ExecutorService threadPool = Executors.newFixedThreadPool(10);

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            int finalI = i;
            MyService1 service1 = new MyService1();
            threadPool.submit(() -> {

                service1.doWork1("username" + (finalI+1));
            });

        }


    }
}


class MyService1 {

    private static Logger logger = LoggerFactory.getLogger(MyThreadLocalGetUserId.class);

    public void doWork1(String name) {

        logger.info("service1 存儲(chǔ)userName:" + name);
        ThreadLocal<String> holder = MyUserContextHolder.getHolder();
        holder.set(name);
        MyService2 service2 = new MyService2();
        service2.doWork2();
    }

}

class MyService2 {
    private static Logger logger = LoggerFactory.getLogger(MyThreadLocalGetUserId.class);

    public void doWork2() {
        ThreadLocal<String> holder = MyUserContextHolder.getHolder();

        logger.info("service2 獲取userName:" + holder.get());
        MyService3 service3 = new MyService3();
        service3.doWork3();
    }
}


class MyService3 {
    private static Logger logger = LoggerFactory.getLogger(MyThreadLocalGetUserId.class);

    public void doWork3() {
        ThreadLocal<String> holder = MyUserContextHolder.getHolder();

        logger.info("service3獲取 userName:" + holder.get());

// 避免oom問題
        holder.remove();
    }
}

從輸出結(jié)果來看,在單例對象情況下,既保證了同一個(gè)線程間變量共享。

也保證了不同線程之間變量的隔離。

三、基于源碼了解ThreadlLocal工作原理

1.ThreadlLocal如何做到線程隔離的?

我們下面這段代碼為例進(jìn)行分析,本質(zhì)上ThreadLocal的withInitial指明了每個(gè)線程初始化時(shí)設(shè)置默認(rèn)值:

ThreadLocal<SimpleDateFormat> threadLocal = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SS"));

當(dāng)我們執(zhí)行g(shù)et操作時(shí),threadLocal 就會(huì)為當(dāng)前線程完成內(nèi)部map的初始化,然后通過initialValue獲取上一步聲明的SimpleDateFormat實(shí)例,由此保證每個(gè)線程內(nèi)部都有一個(gè)獨(dú)有的SimpleDateFormat:

對應(yīng)的我們給出ThreadlLocal的get的源碼,整體邏輯與上述差不多,即初始化線程內(nèi)部的map,然后通過setInitialValue調(diào)用initialValue創(chuàng)建初始值存到線程的map中:

public T get() {
  //獲取當(dāng)前線程
        Thread t = Thread.currentThread();
        //拿到當(dāng)前線程中的map
        ThreadLocalMap map = getMap(t);
        //如果map不為空則取用當(dāng)前這個(gè)ThreadLocal作為key取出值,否則通過setInitialValue完成ThreadLocal初始化
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }


private T setInitialValue() {
  //執(zhí)行initialValue為當(dāng)前線程創(chuàng)建變量value,在這里也就是我們要用的SimpleDateFormat 
        T value = initialValue();
        //獲取當(dāng)前線程map,有則直接以ThreadLocal為key將SimpleDateFormat 設(shè)置進(jìn)去,若沒有先創(chuàng)建再設(shè)置
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
//返回SimpleDateFormat 
        return value;
    }

2.ThreadLocalMap有什么特點(diǎn)?和HashMap有什么區(qū)別

我們通過源碼查看到這個(gè)map為ThreadLocalMap,它是由一個(gè)個(gè)Entry 構(gòu)成的數(shù)組:

 private Entry[] table;

并且每個(gè)Entry 的key是弱引用,這就意味著當(dāng)觸發(fā)GC時(shí),Entry 的key也就是ThreadLocal就會(huì)被回收。

 static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

除上面所說,thread中的map和hashmap還有一個(gè)不同點(diǎn)就是數(shù)據(jù)結(jié)構(gòu),因?yàn)閠hreadLocal的適用場景特殊,所以大部分情況下其內(nèi)部存儲(chǔ)空間不會(huì)存儲(chǔ)太多元素,所以出于簡單的考慮,線程中的map本質(zhì)上就是一個(gè)數(shù)組,一旦發(fā)生沖突則直接通過線性探測法找到數(shù)組中空閑的位置將值存入:

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

           //......

            Entry[] tab = table;
            int len = tab.length;
            //定位鍵值對存儲(chǔ)的索引位置
            int i = key.threadLocalHashCode & (len-1);
   //通過線性探測法循環(huán)找到空閑位置存入元素
            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                ThreadLocal<?> k = e.get();

               //......
            }
   //找到合適的位置將元素存入
            tab[i] = new Entry(key, value);
            //更新一下容量信息
            int sz = ++size;
            //......
        }

四、ThreadLocal使用注意事項(xiàng)

1.內(nèi)存泄漏問題

我們有下面這樣一段web代碼,每次請求test0就會(huì)像線程池中的線程存一個(gè)4M的byte數(shù)組:

RestController
public class TestController {
    final static ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(100, 100, 1, TimeUnit.MINUTES,
            new LinkedBlockingQueue<>());// 創(chuàng)建線程池,通過線程池,保證創(chuàng)建的線程存活

    final static ThreadLocal<Byte[]> localVariable = new ThreadLocal<Byte[]>();// 聲明本地變量

    @RequestMapping(value = "/test0")
    public String test0(HttpServletRequest request) {
        poolExecutor.execute(() -> {
            Byte[] c = new Byte[4* 1024* 1024];
            localVariable.set(c);// 為線程添加變量

        });
        return "success";
    }

   
}

我們將這個(gè)代碼打成jar包部署到服務(wù)器上并啟動(dòng)

java -jar -Xms100m -Xmx100m # 調(diào)整堆內(nèi)存大小
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/heapdump.hprof  # 表示發(fā)生OOM時(shí)輸出日志文件,指定path為/tmp/heapdump.hprof
-XX:+PrintGCTimeStamps -XX:+PrintGCDetails -Xloggc:/tmp/heapTest.log # 打印日志、gc時(shí)間以及指定gc日志的路徑
demo-0.0.1-SNAPSHOT.jar

只需頻繁調(diào)用幾次,就會(huì)輸出OutOfMemoryError

Exception in thread "pool-1-thread-5" java.lang.OutOfMemoryError: Java heap space
        at com.example.jstackTest.TestController.lambda$test0$0(TestController.java:25)
        at com.example.jstackTest.TestController$$Lambda$582/394910033.run(Unknown Source)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
        at java.lang.Thread.run(Thread.java:748)

問題的根本原因是我們沒有及時(shí)回收Thread從ThreadLocal中得到的變量副本。因?yàn)槲覀兊氖褂玫木€程是來自線程池中,所以線程使用結(jié)束后并不會(huì)被銷毀,這就使得ThreadLocal中的變量副本會(huì)一直存儲(chǔ)與線程池中的線程中,導(dǎo)致OOM。

可能你會(huì)問了,不是說Java有GC回收機(jī)制嘛?為什么還會(huì)出現(xiàn)Thread中的ThreadLocalMap的value不會(huì)被回收呢?

我們上文提到ThreadLocal得到值,都會(huì)以ThreadLocal為key,ThreadLocal的initialValue方法得到的value作為值生成一個(gè)entry對象,存到當(dāng)前線程的ThreadLocalMap中。 而我們的Entry的key是一個(gè)弱引用,一旦我們使用的threadLocal臨時(shí)變量用完被垃圾回收之后,這個(gè)key就會(huì)因?yàn)槿跻玫脑虮换厥眨覀冞@個(gè)key所對應(yīng)的value仍然被線程池中的線程的強(qiáng)引用引用著,所以就遲遲無法回收,隨著時(shí)間推移每個(gè)線程都出現(xiàn)這種情況導(dǎo)致OOM。

所以我們每個(gè)線程使用完ThreadLocal之后,一定要使用remove方法清楚ThreadLocalMap中的value:

localVariable.remove()

從源碼中可以看到remove方法會(huì)遍歷當(dāng)前線程map然后將強(qiáng)引用之間的聯(lián)系切斷,確保下次GC可以回收掉可以無用對象。

private void remove(ThreadLocal<?> key) {
            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);
            //定位,并將entry清除
            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                if (e.get() == key) {
                    e.clear();
                    expungeStaleEntry(i);
                    return;
                }
            }
        }

2.空指針問題

使用ThreadLocal存放包裝類的時(shí)候也需要注意添加初始化方法,否則在拆箱時(shí)可能會(huì)出現(xiàn)空指針問題。

 private  static ThreadLocal<Long> threadLocal = new ThreadLocal<>();


    public static void main(String[] args) {
        Long num = threadLocal.get();
        long sum=1+num;

    }

輸出錯(cuò)誤:

Exception in thread "main" java.lang.NullPointerException
 at com.guide.base.MyThreadLocalNpe.main(MyThreadLocalNpe.java:11)

解決方式:

 private  static ThreadLocal<Long> threadLocal = ThreadLocal.withInitial(()->new Long(0));

3.線程重用問題

這個(gè)問題和OOM問題類似,在線程池中服用同一個(gè)線程未及時(shí)清理,導(dǎo)致下一次HTTP請求時(shí)得到上一次ThreadLocal存儲(chǔ)的結(jié)果。


ThreadLocal<String> threadLocal = ThreadLocal.withInitial(() -> null);


 * 線程池中使用threadLocal示例
     *
     * @param accountCode
     * @return
     */
    @GetMapping("/account/getAccountByCode/{accountCode}")
    @SentinelResource(value = "getAccountByCode")
    ResultData<Map<String, Object>> getAccountByCode(@PathVariable(value = "accountCode") String accountCode) throws InterruptedException {
        Map<String, Object> result = new HashMap<>();
        
        CountDownLatch countDownLatch = new CountDownLatch(1);
        
        
        threadPool.submit(() -> {

            String before = Thread.currentThread().getName() + ":" + threadLocal.get();
            log.info("before:" + before);
            result.put("before", before);

            log.info("調(diào)用getByCode,請求參數(shù):{}", accountCode);
            QueryWrapper<Account> queryWrapper = new QueryWrapper<>();
            queryWrapper.eq("account_code", accountCode);
            Account account = accountService.getOne(queryWrapper);

            String after = Thread.currentThread().getName() + ":" + account.getAccountName();
            result.put("after", account.getAccountName());
            log.info("after:" + after);

            threadLocal.set(account.getAccountName());
            
            //完成計(jì)算后,使用countDown按下倒計(jì)時(shí)門閂,通知主線程可以執(zhí)行后續(xù)步驟
            countDownLatch.countDown();

        });

        //等待上述線程池完成
        countDownLatch.await();

        return ResultData.success(result);
    }

從輸出結(jié)果可以看出,我們第二次進(jìn)行HTTP請求時(shí),threadLocal第一get獲得了上一次請求的值,出現(xiàn)臟數(shù)據(jù)。

C:\Users\xxx>curl http://localhost:9000/account/getAccountByCode/demoData
{"status":100,"message":"操作成功","data":{"before":"pool-2-thread-1:null","after":"pool-2-thread-1:demoData"},"success":true,"timestamp":1678410699943}
C:\Users\xxx>curl http://localhost:9000/account/getAccountByCode/Zsy
{"status":100,"message":"操作成功","data":{"before":"pool-2-thread-1:demoData","after":"pool-2-thread-1:zsy"},"success":true,"timestamp":1678410707473}

解決方法也很簡單,手動(dòng)添加一個(gè)threadLocal的remove方法即可:

@GetMapping("/account/getAccountByCode/{accountCode}")
    @SentinelResource(value = "getAccountByCode")
    ResultData<Map<String, Object>> getAccountByCode(@PathVariable(value = "accountCode") String accountCode) throws InterruptedException {
        Map<String, Object> result = new HashMap<>();

        CountDownLatch countDownLatch = new CountDownLatch(1);

        try {
            threadPool.submit(() -> {

                String before = Thread.currentThread().getName() + ":" + threadLocal.get();
                log.info("before:" + before);
                result.put("before", before);

                log.info("調(diào)用getByCode,請求參數(shù):{}", accountCode);
                QueryWrapper<Account> queryWrapper = new QueryWrapper<>();
                queryWrapper.eq("account_code", accountCode);
                Account account = accountService.getOne(queryWrapper);

                String after = Thread.currentThread().getName() + ":" + account.getAccountName();
                result.put("after", after);
                log.info("after:" + after);

                threadLocal.set(account.getAccountName());

                //完成計(jì)算后,使用countDown按下倒計(jì)時(shí)門閂,通知主線程可以執(zhí)行后續(xù)步驟
                countDownLatch.countDown();

            });
        } finally {
            threadLocal.remove();
        }


        //等待上述線程池完成
        countDownLatch.await();

        return ResultData.success(result);
    }

五、ThreadLocal的不可繼承性

1.通過代碼證明ThreadLocal的不可繼承性

如下代碼所示,ThreadLocal子線程無法拿到主線程維護(hù)的內(nèi)部變量

/**
 * ThreadLocal 不具備可繼承性
 */
public class ThreadLocalInheritTest {
    private static ThreadLocal<String> THREAD_LOCAL = new ThreadLocal<>();

    private static Logger logger = LoggerFactory.getLogger(ThreadLocalInheritTest.class);

    public static void main(String[] args) {
        THREAD_LOCAL.set("mainVal");
        logger.info("主線程的值為: " + THREAD_LOCAL.get());

        new Thread(() -> {
            try {
                //睡眠3s確保上述邏輯運(yùn)行
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            logger.info("子線程獲取THREAD_LOCAL的值為:[{}]", THREAD_LOCAL.get());
        }).start();
    }

}

2.使用InheritableThreadLocal實(shí)現(xiàn)主線程內(nèi)部變量繼承

如下所示,我們將THREAD_LOCAL 改為InheritableThreadLocal類即可解決問題。

/**
 * ThreadLocal 不具備可繼承性
 */
public class ThreadLocalInheritTest {

    private static ThreadLocal<String> THREAD_LOCAL = new InheritableThreadLocal<>();

    private static Logger logger = LoggerFactory.getLogger(ThreadLocalInheritTest.class);

    public static void main(String[] args) {
        THREAD_LOCAL.set("mainVal");
        logger.info("主線程的值為: " + THREAD_LOCAL.get());

        new Thread(() -> {
            try {
                //睡眠3s確保上述邏輯運(yùn)行
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            logger.info("子線程獲取THREAD_LOCAL的值為:[{}]", THREAD_LOCAL.get());
        }).start();
    }

}

3.基于源碼剖析原因

因?yàn)?nbsp;ThreadLocal會(huì)將變量存儲(chǔ)在線程的 ThreadLocalMap中,所以我們先看看InheritableThreadLocal的getMap方法,從而定位到了inheritableThreadLocals:

 ThreadLocalMap getMap(Thread t) {
       return t.inheritableThreadLocals;
    }

然后我們到Thread類去定位這個(gè)變量的使用之處,所以我們在創(chuàng)建線程的地方打了個(gè)斷點(diǎn):

從而定位到這段初始化,它會(huì)獲取主線程的ThreadLocalMap并將主線程ThreadLocalMap中的值存到子線程的ThreadLocalMap中。

private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
        if (name == null) {
            throw new NullPointerException("name cannot be null");
        }

        this.name = name;

  //獲取當(dāng)前線程的主線程
        Thread parent = currentThread();
       
        if (inheritThreadLocals && parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
            //將主線程的map的值存到子線程中
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
        //......
    }

createInheritedMap內(nèi)部就會(huì)調(diào)用ThreadLocalMap方法將主線程的ThreadLocalMap的值存到子線程的ThreadLocalMap中。

private ThreadLocalMap(ThreadLocalMap parentMap) {
            Entry[] parentTable = parentMap.table;
            int len = parentTable.length;
            setThreshold(len);
            table = new Entry[len];
   //遍歷父線程數(shù)據(jù)復(fù)制到子線程map中
            for (int j = 0; j < len; j++) {
                Entry e = parentTable[j];
                if (e != null) {
                    @SuppressWarnings("unchecked")
                    ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
                    if (key != null) {
                     //......
                     //定位當(dāng)前子線程bucket位置將value存入
                        while (table[h] != null)
                            h = nextIndex(h, len);
                        table[h] = c;
                        size++;
                    }
                }
            }
        }

六、ThreadLocal在Spring中的運(yùn)用

其實(shí)針對日期格式化問題,Spring已經(jīng)為我們內(nèi)置好了相應(yīng)的工具類即DateTimeContextHolder:

 private static final ThreadLocal<DateTimeContext> dateTimeContextHolder =
   new NamedThreadLocal<>("DateTimeContext");

該工具類和simpledateformate差不多,使用示例如下所示,是spring封裝的,使用起來也很方便:

public class DateTimeContextHolderTest {


    protected static final Logger logger = LoggerFactory.getLogger(DateTimeContextHolderTest.class);

    private final static DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");

    private Set<String> set = new ConcurrentHashSet<String>();

    @Test
    public void test_withLocale_same() throws Exception {
        ExecutorService threadPool = Executors.newFixedThreadPool(30);

        for (int i = 0; i < 30; i++) {
            int finalI = i;
            threadPool.execute(() -> {
                LocalDate currentdate = LocalDate.now();
                int year = currentdate.getYear();
                int month = currentdate.getMonthValue();
                int day = 1 + finalI;
                LocalDate date = LocalDate.of(year, month, day);

                DateTimeFormatter fmt = DateTimeContextHolder.getFormatter(formatter, null);
                String text = date.format(fmt);
                set.add(text);
                logger.info("轉(zhuǎn)換后的時(shí)間為" + text);
            });
        }

        threadPool.shutdown();
        while (!threadPool.isTerminated()) {

        }

        logger.info("查看去重后的數(shù)量"+set.size());


    }
}

七、為什么JDK建議將ThreadLocal設(shè)置為static

我們都知道使用static是屬于類,存在于方法區(qū)中,即修飾的變量是全局共享的,這意味著當(dāng)前ThreadLocal在通過static之后,即所有的實(shí)例對象都共享一個(gè)ThreadLocal。從而避免重復(fù)創(chuàng)建TSO(Thread Specific Object)即ThreadLocal所關(guān)聯(lián)的對象的創(chuàng)建的開銷。以及這種方案使得即使出現(xiàn)內(nèi)存泄漏也是O(1)級(jí)別的內(nèi)存泄露,場景如下:

  • 假設(shè)使用線程非線程池模式,即線程結(jié)束后threadLocalMap就會(huì)被回收,這種情況下也只有在threadLocal第一次調(diào)用get到線程銷毀之間的時(shí)間段存在內(nèi)存泄漏的情況。
  • 如果使用的是全局線程池,因?yàn)榫€程池的線程并不會(huì)被回收,所以threadLocalMap中的entry一直存在于堆內(nèi)存中,但由于該ThreadLocal屬于全局共享,所以大量線程進(jìn)行操作時(shí)一定概率觸發(fā)expungeStaleEntry清除過期對象,一定程度上避免了內(nèi)存泄漏的情況。
  • 極端情況下,如果threadLocal創(chuàng)建之后只有線程池中的一個(gè)線程get或初始化后完全沒有線程再去使用,這就會(huì)導(dǎo)致threadLocalMap存在強(qiáng)引用而導(dǎo)致無法被回收,O(1)級(jí)別的內(nèi)存泄漏由此誕生。

對應(yīng)的實(shí)例變量的ThreadLocal的O(n)內(nèi)存泄漏,這就不必多說。

八、小結(jié)

  • ThreadLocal通過在將共享變量拷貝一份到每個(gè)線程內(nèi)部的ThreadLocalMap保證線程安全。
  • ThreadLocal使用完成后記得使用remove方法手動(dòng)清理線程中的ThreadLocalMap過期對象,避免OOM和一些業(yè)務(wù)上的錯(cuò)誤。
  • ThreadLocal是不可被繼承了,如果想使用主線的的ThreadLocal,就必須使用InheritableThreadLocal。
責(zé)任編輯:趙寧寧 來源: 寫代碼的SharkChili
相關(guān)推薦

2025-02-06 08:24:25

AQS開發(fā)Java

2024-10-28 08:15:32

2025-06-27 07:19:48

2021-05-06 08:55:24

ThreadLocal多線程多線程并發(fā)安全

2021-12-08 15:07:51

鴻蒙HarmonyOS應(yīng)用

2024-08-22 18:49:23

2024-10-31 09:24:42

2025-03-27 04:10:00

2023-09-21 22:02:22

Go語言高級(jí)特性

2023-05-18 08:54:22

OkHttp源碼解析

2024-08-30 09:53:17

Java 8編程集成

2024-09-19 08:49:13

2019-07-17 14:03:44

運(yùn)維DevOps實(shí)踐

2021-03-19 06:31:06

vue-lazyloa圖片懶加載項(xiàng)目

2025-11-05 03:00:55

2025-03-07 10:23:46

2025-11-04 01:30:00

Paimon分布式文件系統(tǒng)

2022-03-09 23:02:30

Java編程處理模型

2023-12-04 16:18:30

2021-07-03 08:51:30

源碼Netty選擇器
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

日本在线观看视频网站| 欧美精品v日韩精品v韩国精品v| 91大神福利视频在线| 成年人网站免费在线观看| av成人免费| 一区二区三区在线免费播放| 久久久久高清| 波多野结衣毛片| 在线观看国产精品入口| 日韩电影中文字幕一区| 污网站在线免费| 国产亚av手机在线观看| 久久久91精品国产一区二区精品| 成人免费福利视频| 性无码专区无码| 天天做天天爱天天综合网2021| 亚洲国产第一页| 亚洲这里只有精品| 久久青草伊人| 亚洲美女屁股眼交| 亚洲不卡中文字幕| 99久久精品日本一区二区免费| 亚洲欧美春色| 欧美成人sm免费视频| 色婷婷在线影院| 国产福利一区二区精品秒拍| 国产一区二区你懂的| 亚洲精品成人在线| 欧美日韩一区综合| 亚洲图片在线视频| 欧美黑人粗大| 亚洲自拍与偷拍| 色一情一乱一伦一区二区三欧美 | 久久精品成人av| 外国成人毛片| 91久久精品网| 国产精品50p| 亚洲欧美一区二区三区| 亚洲影院免费| 欧美日韩ab片| 二区三区四区视频| 五月婷婷综合在线观看| 无遮挡爽大片在线观看视频| 亚洲自拍与偷拍| 国产精品国产三级国产专播品爱网| 亚洲综合男人的天堂| 欧美在线日韩精品| 农村少妇久久久久久久| 国产麻豆午夜三级精品| 国产精品丝袜白浆摸在线| 韩国成人av| 精品久久久久久无码人妻| xxxxx.日韩| 欧美日韩国产在线看| 久操手机在线视频| a级在线观看| 中文字幕一区av| 影音先锋国产在线| 国产亚洲一区二区手机在线观看| 亚洲一区二区四区蜜桃| 成人午夜免费剧场| 成人在线播放| 一区二区三区精品久久久| 欧美日韩亚洲激情| 国产精品久久久久久久久借妻| 国产大片中文字幕| 韩日视频一区| 久久久亚洲国产| 日本少妇bbwbbw精品| 亚洲青涩在线| 五月婷婷综合激情| 91精品国产乱码久久蜜臀| www.天天射.com| 精品自拍视频| 欧美乱熟臀69xxxxxx| 亚洲一级免费观看| 国产精品18| 男人天堂亚洲二区| 欧美色网址大全| 亚洲视频一区二区三区| 麻豆精品免费视频| 国产一区网站| 久久精品国产成人| 精品国产乱码久久久久久鸭王1| 午夜激情一区| 78m国产成人精品视频| 国产性生活视频| 精品午夜一区二区三区在线观看| 91亚色免费| 天堂在线中文资源| 中文字幕精品三区| 色哟哟免费网站| 国内精彩免费自拍视频在线观看网址| 欧美日韩一区二区三区在线免费观看| 久久久久国产精品熟女影院| 99视频有精品高清视频| 亚洲精品一区二区三区精华液| wwwwww日本| 亚洲欧美偷拍自拍| 97超级碰碰碰久久久| 中文字幕精品视频在线观看| 国产一区二区精品久久99| 精品国产乱码久久久久久88av| 国产二区视频在线观看| 一区二区三区欧美久久| 国产精品99久久免费黑人人妻| 高清不卡一区| 日韩av在线免费播放| 日韩福利小视频| 欧美亚洲视频| 91久久久久久久| 四虎精品成人免费网站| 亚洲视频狠狠干| 欧美精品色婷婷五月综合| 精品午夜视频| 亚洲欧洲日韩国产| 久久人人爽人人爽人人| 久久精品国产久精国产| 免费久久99精品国产自| 男女视频在线| 精品国产乱码久久久久久天美| 苍井空浴缸大战猛男120分钟| 伊人精品久久| 久久久国产视频| 无码一区二区三区| 不卡视频一二三四| 在线观看成人免费| 欧美一区=区三区| 亚洲色图在线观看| 91香蕉在线视频| 国产成人精品午夜视频免费| 中文字幕日韩一区二区三区不卡| 欧亚一区二区| 日韩精品一二三四区| 国产在线拍揄自揄拍| 寂寞少妇一区二区三区| 天堂√在线观看一区二区| √最新版天堂资源网在线| 欧美一区二区三区在线视频| www.4hu95.com四虎| 日韩国产精品久久久| 久久99久久精品国产| av色在线观看| 亚洲高清久久网| 国产性一乱一性一伧一色| 国产毛片精品国产一区二区三区| 自拍亚洲欧美老师丝袜| 久久91超碰青草在哪里看| 国产亚洲欧美日韩一区二区| 日韩免费av网站| 久久综合一区二区| 国产极品美女高潮无套久久久| 日韩理论电影中文字幕| 2023亚洲男人天堂| 牛牛热在线视频| 日本精品视频一区二区三区| av女人的天堂| 日韩av中文字幕一区二区三区| 欧美日韩电影一区二区三区| 亚洲人成在线网站| 亚洲天堂av在线免费观看| 波多野结衣在线观看一区| 国产亚洲综合色| 日韩av片网站| 亚洲一区二区日韩| 99影视tv| 亚洲涩涩在线| 中文字幕日韩在线视频| 国产伦理一区二区| 一区二区三区欧美| 中文字幕在线免费看线人| 六月丁香综合| 亚洲精品日韩精品| 日本在线视频一区二区三区| 久久久最新网址| 日本一卡二卡四卡精品| 欧美片在线播放| 欧美人妻精品一区二区免费看| 粉嫩av一区二区三区在线播放| 欧美久久久久久久久久久久久| 日韩极品在线| 国产在线视频一区| h片在线观看视频免费| 亚洲欧美三级伦理| 91国在线视频| 舔着乳尖日韩一区| 91麻豆精品久久毛片一级| 成人一区二区三区| 91蝌蚪视频在线观看| 综合在线视频| 欧美日韩亚洲一区二区三区四区| 欧美美女福利视频| 国内揄拍国内精品少妇国语| 北岛玲日韩精品一区二区三区| 欧美一级精品在线| 国产精品免费精品一区| 自拍偷拍欧美激情| 偷拍女澡堂一区二区三区| 九色综合狠狠综合久久| 91专区在线观看| 久久久久电影| 欧美久久电影| 91精品啪在线观看国产爱臀| 国产精品久久久久久久久久| 黄色污污视频在线观看| 中日韩美女免费视频网站在线观看| 丰满少妇被猛烈进入| 欧美手机在线视频| 五月婷婷中文字幕| 亚洲男人的天堂一区二区| 强伦人妻一区二区三区| 国产白丝精品91爽爽久久| 色片在线免费观看| 久久精品女人天堂| 岛国大片在线播放| 性欧美欧美巨大69| 三区精品视频| 小嫩嫩12欧美| 国外成人在线视频网站| 日韩精品中文字幕吗一区二区| 国产精品成人国产乱一区 | 欧美又粗又大又长| 欧美国产日韩a欧美在线观看| 成人在线视频免费播放| 国产精品一区2区| 久热精品在线播放| 三级欧美韩日大片在线看| 国产aaa免费视频| 亚洲一区二区三区无吗| 亚洲一区二区三区免费看| 沈樵精品国产成av片| 久久精品99久久| 国产伦理久久久久久妇女| av资源一区二区| 麻豆国产一区二区三区四区| 91久久精品在线| **国产精品| 91精品免费视频| 欧美美女福利视频| 成人黄色生活片| 久久久久久一区二区三区四区别墅| 国产成人+综合亚洲+天堂| 天天综合av| 日韩av免费在线| 性感美女一区二区在线观看| 97超级碰碰碰久久久| 涩涩涩在线视频| 欧美怡春院一区二区三区| 成人香蕉视频| 国产成人在线一区二区| 日韩经典一区| 国产免费久久av| 成人在线日韩| 成人综合电影| 欧美黑白配在线| 欧美午夜精品久久久久久蜜| 精品成av人一区二区三区| 视频一区二区综合| 日韩国产欧美一区二区| 在线不卡日本| 一区二区不卡| 久久99中文字幕| 亚洲欧美不卡| 中文字幕亚洲欧洲| 韩国一区二区三区| 人妻 日韩 欧美 综合 制服| www日韩大片| 中文字幕av久久爽一区| 国产精品久久久久久一区二区三区 | 国产亚洲精品成人av久久ww| 欧美日本一道| 色综合视频一区中文字幕| 久久男人av资源站| 国产精品亚洲美女av网站| 精品一区二区三区中文字幕 | 亚洲理论中文字幕| 成人中文字幕电影| 国产精品久久久久无码av色戒| 国产精品美女一区二区三区 | ts人妖另类在线| 婷婷综合福利| 永久免费精品视频网站| 亚洲精选久久| www.精品在线| 成人黄色国产精品网站大全在线免费观看 | 亚洲欧美日韩中文视频| 黄色片网站在线| 91精品国产成人| 日韩精品一级毛片在线播放| 国产精品手机在线| 成人情趣视频网站| 91免费国产精品| 日韩高清欧美激情| 亚洲欧美日韩中文字幕在线观看| 91丝袜高跟美女视频| www.av免费| 日韩欧美999| 成人免费视频国产| 中文国产亚洲喷潮| 日韩脚交footjobhd| 91免费观看网站| 精品一区亚洲| 999一区二区三区| 日本aⅴ免费视频一区二区三区| 亚洲美女高潮久久久| 亚洲国产精品传媒在线观看| 国产无码精品在线播放| 欧美精品tushy高清| 久草在现在线| 久久免费视频在线| 99精品在线免费观看| 日韩三级电影| 一区二区三区精品视频在线观看| 99精品999| 国产女主播一区| 久久久午夜影院| 日韩视频免费观看高清在线视频| 成年人在线视频免费观看| 亚州精品天堂中文字幕| 免费观看亚洲天堂| 亚洲一区二区三区精品视频| 美女精品网站| 国产又粗又长又爽| 亚洲综合一区二区三区| 国产又黄又粗又猛又爽| 国产亚洲一区二区在线| 欧美gv在线| 狠狠色狠狠色综合人人| 欧美日韩爆操| 免费看三级黄色片| 亚洲美女区一区| 国产视频在线观看免费| 久久精品91久久久久久再现| 国产精品天堂蜜av在线播放 | 成人免费在线观看| 亲子乱一区二区三区电影| 开心激情综合| 黄页网站大全在线观看| 成人午夜av在线| 激情五月婷婷小说| 欧美不卡视频一区| 在线视频中文字幕第一页| 亚洲一区亚洲二区| 欧美黄色大片网站| 日本黄色三级网站| 亚洲综合色噜噜狠狠| 免费国产精品视频| 97视频免费观看| 亚洲婷婷丁香| 久久久精品三级| 国产精品亲子乱子伦xxxx裸| 中文字幕一级片| www.日韩视频| 精品国产亚洲一区二区在线观看| 成人性做爰片免费视频| 国产精品一区二区无线| 国产在线观看你懂的| 日韩av在线直播| 久久青青视频| 亚洲一区二区不卡视频| 黄网站免费久久| 久久免费视频6| 亚洲欧美国产日韩中文字幕| 日本欧美韩国| 强伦女教师2:伦理在线观看| 国产福利一区二区三区视频| 国产精品999久久久| 国产视频在线观看一区二区| 久久久成人av毛片免费观看| 中国成人在线视频| 国产成人精品免费| 国内自拍视频在线播放| 最近2019中文字幕一页二页| 欧一区二区三区| 国产精品va无码一区二区| 国产精品美女一区二区三区 | 日本黄大片在线观看| 91原创在线视频| 中文字幕精品在线观看| 欧美日韩福利在线观看| 美女主播精品视频一二三四| 四季av一区二区| 亚洲美女屁股眼交| 人人九九精品| 亚洲在线www| 蜜乳av另类精品一区二区| 欧美特级一级片| 精品亚洲一区二区三区在线播放 | 久久精品色妇熟妇丰满人妻| 精品国产成人在线影院| 国产一区二区三区影视| 91.com在线| 国产精品久久久久久亚洲毛片| 国模人体一区二区| 国产精品午夜一区二区欲梦| 伊人久久亚洲美女图片| 国产第一页精品| 日韩电影免费在线观看中文字幕| 2019中文亚洲字幕| 男人操女人免费|