首頁(yè)常見(jiàn)問(wèn)題正文

ThreadLocal是怎么解決并發(fā)安全的?

更新時(shí)間:2023-04-12 來(lái)源:黑馬程序員 瀏覽量:

IT培訓(xùn)班

  ThreadLocal是Java中一種線程封閉技術(shù),它提供了一種線程本地變量的機(jī)制,使得每個(gè)線程都擁有一個(gè)獨(dú)立的變量副本,這樣可以避免多個(gè)線程訪問(wèn)同一個(gè)變量時(shí)產(chǎn)生的并發(fā)問(wèn)題。

  ThreadLocal的實(shí)現(xiàn)機(jī)制是,每個(gè)線程都有一個(gè)ThreadLocalMap實(shí)例,ThreadLocalMap中存儲(chǔ)了當(dāng)前線程所持有的所有ThreadLocal對(duì)象以及對(duì)應(yīng)的變量副本。當(dāng)需要獲取變量副本時(shí),當(dāng)前線程會(huì)先獲取ThreadLocal對(duì)象,然后通過(guò)ThreadLocal對(duì)象獲取對(duì)應(yīng)的變量副本。

  以下是一個(gè)簡(jiǎn)單的示例代碼,展示了如何使用ThreadLocal解決多線程并發(fā)訪問(wèn)同一個(gè)變量時(shí)產(chǎn)生的并發(fā)問(wèn)題:

public class ThreadLocalDemo {

    private static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
        @Override
        protected Integer initialValue() {
            return 0; // 設(shè)置初始值為0
        }
    };

    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    int num = threadLocal.get(); // 獲取變量副本
                    num += 5;
                    threadLocal.set(num); // 更新變量副本
                    System.out.println(Thread.currentThread().getName() + ": " + num);
                }
            }).start();
        }
    }
}

  在這個(gè)示例中,我們創(chuàng)建了一個(gè)ThreadLocal對(duì)象,然后在每個(gè)線程中獲取對(duì)應(yīng)的變量副本,對(duì)其進(jìn)行操作后再更新變量副本。由于每個(gè)線程都擁有自己的變量副本,因此對(duì)變量的操作不會(huì)影響其他線程,從而解決了并發(fā)安全問(wèn)題。

  ThreadLocal提供的線程本地變量機(jī)制,可以使得每個(gè)線程都有自己的變量副本,從而避免了多線程并發(fā)訪問(wèn)同一個(gè)變量所帶來(lái)的安全問(wèn)題。其實(shí)現(xiàn)方式是,每個(gè)線程都會(huì)持有一個(gè)ThreadLocalMap對(duì)象,該對(duì)象中存儲(chǔ)了當(dāng)前線程所持有的所有ThreadLocal對(duì)象以及對(duì)應(yīng)的變量副本。

  當(dāng)一個(gè)線程調(diào)用ThreadLocal的get()方法時(shí),實(shí)際上是先獲取當(dāng)前線程持有的ThreadLocalMap對(duì)象,然后根據(jù)當(dāng)前ThreadLocal對(duì)象的hashCode值作為key,從ThreadLocalMap中獲取對(duì)應(yīng)的變量副本。如果ThreadLocalMap中不存在對(duì)應(yīng)的變量副本,則會(huì)調(diào)用ThreadLocal的initialValue()方法創(chuàng)建一個(gè)新的變量副本,并將其存儲(chǔ)到ThreadLocalMap中。如果存在,則直接返回對(duì)應(yīng)的變量副本。

  當(dāng)一個(gè)線程調(diào)用ThreadLocal的set()方法時(shí),同樣是先獲取當(dāng)前線程持有的ThreadLocalMap對(duì)象,然后將當(dāng)前ThreadLocal對(duì)象的hashCode值作為key,將傳入的值作為value,存儲(chǔ)到ThreadLocalMap中。由于每個(gè)線程都擁有自己的ThreadLocalMap對(duì)象,因此不會(huì)出現(xiàn)多線程同時(shí)訪問(wèn)同一個(gè)變量副本的問(wèn)題。

  接下來(lái)我們通過(guò)一段復(fù)雜一些的代碼,來(lái)演示如何使用ThreadLocal實(shí)現(xiàn)一個(gè)線程安全的計(jì)數(shù)器:

public class Counter {
    private static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
        @Override
        protected Integer initialValue() {
            return 0; // 設(shè)置初始值為0
        }
    };

    public static int getCount() {
        int count = threadLocal.get(); // 獲取變量副本
        count++; // 對(duì)變量進(jìn)行操作
        threadLocal.set(count); // 更新變量副本
        return count;
    }
}

  在這個(gè)示例中,我們使用了一個(gè)靜態(tài)的ThreadLocal對(duì)象作為計(jì)數(shù)器的實(shí)現(xiàn)。通過(guò)getCount()方法獲取計(jì)數(shù)器的值時(shí),先獲取當(dāng)前線程持有的ThreadLocalMap對(duì)象,然后根據(jù)ThreadLocal對(duì)象的hashCode值作為key,從ThreadLocalMap中獲取對(duì)應(yīng)的變量副本,對(duì)其進(jìn)行操作后再更新變量副本。

  由于每個(gè)線程都擁有自己的變量副本,因此不會(huì)出現(xiàn)多線程并發(fā)訪問(wèn)同一個(gè)變量的問(wèn)題。這樣就可以實(shí)現(xiàn)線程安全的計(jì)數(shù)器了。

分享到:
在線咨詢 我要報(bào)名
和我們?cè)诰€交談!