詳細的ThreadLocal以及與synchronized的區別


ThreadLocal

概述

threadlocal是一個線程內部的存儲類,可以在指定線程內存儲數據,數據存儲以后,只有指定線程可以得到存儲數據

ThreadLocal提供了線程內存儲變量的能力,這些變量不同之處在於每一個線程讀取的變量是對應的互相獨立的。通過get和set方法就可以得到當前線程對應的值。

做個不恰當的比喻,從表面上看ThreadLocal相當於維護了一個map,key就是當前的線程,value就是需要存儲的對象。

這里的這個比喻是不恰當的,實際上是ThreadLocal的靜態內部類ThreadLocalMap為每個Thread都維護了一個數組table,ThreadLocal確定了一個數組下標,而這個下標就是value存儲的對應位置。

ThreadLocal類用來提供線程內部的局部變量。這種變量在多線程環境下訪問(通過get和set方法訪問)時能保證各個線程的變量相對獨立於其他線程內的變量。

ThreadLocal實例通常來說都是private static類型的,用於關聯線程和線程上下文

作用

提供線程內的局部變量,不同的線程之間不會相互干擾,這種變量在線程的生命周期內起作用

總結:
1.線程並發:在多線程並發的場景下(單線程是用不到ThreadLocal的)
2.傳遞數據:我們可以通過ThreadLocal在同一線程,不同組件中傳遞公共變量(和域對象有點相似)
3.線程隔離:每個線程的變量都是獨立的不會互相影響(ThreadLocal的核心)

基本使用

常用方法

方法聲名 描述
ThreadLocal( ) 創建ThreadLocal對象
public void set(T value) 設置當前線程綁定的局部變量
public T get( ) 獲取當前線程綁定的局部變量
public void remove( ) 移除當前線程綁定的局部變量

使用案例

演示問題:

/*
    需求:線程隔離
           在多線程並發的場景下,每個線程中的變量都是相互獨立
           線程A:     設置(變量1)     獲取(變量1)
           線程B:     設置(變量2)     獲取(變量2)
 */
public class MyDemo01 {

    //變量
    private String content;

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }


    public static void main(String[] args) {
        MyDemo01 demo = new MyDemo01();

        //開啟五個線程
        for (int i = 0; i < 5; i++) {

            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    //每個線程:存一個變量,過一會取出這個變量
                    demo.setContent(Thread.currentThread().getName() + "的數據");
                    System.out.println("-----------------");
                    System.out.println(Thread.currentThread().getName() + "--->" + demo.getContent());
                }
            });

            //設置線程名字
            thread.setName("線程"+i);
            thread.start();

        }
    }
}

結果顯示:

多運行幾次,就會出現以上數據,每個線程存入的數據和取出的數據是不一致的,java中的線程調度是搶占式調度,本身具備隨機性

這種情況就是線程不隔離

應該怎么解決呢?

利用ThreadLocal

ThreadLocal 是一個泛型類,綁定的變量類型是不受限制的

使用ThreadLocal改進之后的代碼

/*
    需求:線程隔離
           在多線程並發的場景下,每個線程中的變量都是相互獨立
           線程A:     設置(變量1)     獲取(變量1)
           線程B:     設置(變量2)     獲取(變量2)

    ThreadLocal:
        1.set():    將變量綁定到當前線程中
        2.get():    獲取當前線程綁定的變量
 */
public class MyDemo01 {

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

    //變量
    private String content;

    public String getContent() {
//        return content;
        String s = t1.get();
        return s;
    }

    public void setContent(String content) {
//        this.content = content;

        //將變量content綁定到當前線程
        t1.set(content);
    }


    public static void main(String[] args) {
        MyDemo01 demo = new MyDemo01();

        //開啟五個線程
        for (int i = 0; i < 5; i++) {

            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    //每個線程:存一個變量,過一會取出這個變量
                    demo.setContent(Thread.currentThread().getName() + "的數據");
                    System.out.println("-----------------");
                    System.out.println(Thread.currentThread().getName() + "--->" + demo.getContent());
                }
            });

            //設置線程名字
            thread.setName("線程"+i);
            thread.start();

        }
    }
}

再次運行,多運行幾次,增加出現問題的幾率

結果正常,哪怕加上thread.sleep(200); 結果也還是正常的

ThreadLocal解決了線程隔離的這個需求

與 synchronized 的區別

使用synchronized也可以完成隔離線程的需求

public class MyDemo02 {

    //變量
    private String content;

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }


    public static void main(String[] args) throws InterruptedException {
        MyDemo02 demo = new MyDemo02();

        //開啟五個線程
        for (int i = 0; i < 5; i++) {

            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    //每個線程:存一個變量,過一會取出這個變量
                    //使用synchronized代碼塊來完成需求
                    synchronized (MyDemo02.class){
                        demo.setContent(Thread.currentThread().getName() + "的數據");
                        System.out.println("-----------------");
                        System.out.println(Thread.currentThread().getName() + "--->" + demo.getContent());
                    }
                }
            });

            //設置線程名字
            thread.setName("線程"+i);
            thread.start();
            thread.sleep(200);

        }
    }
}

那么ThreadLocal與synchronized的區別是什么呢?

右邊的代碼加了synchronized鎖,線程只能一個一個去執行,排隊進行訪問,效率低,讓我們的程序失去了並發性

左邊沒有加鎖,一樣可以並發執行

雖然ThreadLocal模式與synchronized關鍵字都用於處理多線程並發訪問變量的問題,

但是兩者處理問題的角度和思路不同

synchronized ThreadLocal
原理 同步機制采用“以時間換空間”的方式,只提供了一份變量,讓不同的線程排隊訪問 ThreadLocal采用“以空間換空間”的方式,為每一個線程都提供了一份變量的副本,從而實現同時訪問而相不干擾
側重點 多個線程之間訪問資源的同步 多線程中讓每個線程之間的數據相互隔離

小結:

synchronized鎖是解決線程的同步問題,線程失去並發性,效率低
ThreadLocal可以使程序擁有更高的並發性,能夠保證程序執行的效率


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM