激情久久久_欧美视频区_成人av免费_不卡视频一二三区_欧美精品在欧美一区二区少妇_欧美一区二区三区的

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務器之家 - 編程語言 - Java教程 - Java多線程 ThreadLocal原理解析

Java多線程 ThreadLocal原理解析

2022-03-05 15:04冬日毛毛雨 Java教程

這篇文章主要介紹了Java多線程 ThreadLocal原理,ThreadLoal 變量,線程局部變量,同一個 ThreadLocal 所包含的對象,在不同的 Thread 中有不同的副本,下面文章也是圍繞Java多線程 ThreadLocal展開內容,需要的朋友可以參考一下

1、什么是ThreadLocal變量

ThreadLoal 變量,線程局部變量,同一個 ThreadLocal 所包含的對象,在不同的 Thread 中有不同的副本。

這里有幾點需要注意:

  • 因為每個 Thread 內有自己的實例副本,且該副本只能由當前 Thread 使用。這是也是 ThreadLocal 命名的由來。
  • 既然每個 Thread 有自己的實例副本,且其它 Thread 不可訪問,那就不存在多線程間共享的問題。

ThreadLocal 提供了線程本地的實例。它與普通變量的區別在于,每個使用該變量的線程都會初始化一個完全獨立的實例副本。ThreadLocal 變量通常被private static修飾。當一個線程結束時,它所使用的所有 ThreadLocal 相對的實例副本都可被回收。

總的來說,ThreadLocal 適用于每個線程需要自己獨立的實例且該實例需要在多個方法中被使用,也即變量在線程間隔離而在方法或類間共享的場景。

2、ThreadLocal實現原理

首先 ThreadLocal 是一個泛型類,保證可以接受任何類型的對象。

因為一個線程內可以存在多個 ThreadLocal 對象,所以其實是 ThreadLocal 內部維護了一個 Map ,這個 Map 不是直接使用的 HashMap ,而是 ThreadLocal 實現的一個叫做 ThreadLocalMap 的靜態內部類。而我們使用的 get()set() 方法其實都是調用了這個ThreadLocalMap類對應的 get() set() 方法。

例如下面的 set 方法:

?
1
2
3
4
5
6
7
8
public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

get方法:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
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();
}

createMap方法:

?
1
2
3
void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

ThreadLocalMap是個靜態的內部類:

 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
static class ThreadLocalMap {
 
        /**
         * The entries in this hash map extend WeakReference, using
         * its main ref field as the key (which is always a
         * ThreadLocal object).  Note that null keys (i.e. entry.get()
         * == null) mean that the key is no longer referenced, so the
         * entry can be expunged from table.  Such entries are referred to
         * as "stale entries" in the code that follows.
         */
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;
 
            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
 
        /**
         * The initial capacity -- MUST be a power of two.
         */
        private static final int INITIAL_CAPACITY = 16;
 
        /**
         * The table, resized as necessary.
         * table.length MUST always be a power of two.
         */
        private Entry[] table;
 
        /**
         * The number of entries in the table.
         */
        private int size = 0;
 
        /**
         * The next size value at which to resize.
         */
        private int threshold; // Default to 0
 
        /**
         * Set the resize threshold to maintain at worst a 2/3 load factor.
         */
        private void setThreshold(int len) {
            threshold = len * 2 / 3;
        }
 
        /**
         * Increment i modulo len.
         */
        private static int nextIndex(int i, int len) {
            return ((i + 1 < len) ? i + 1 : 0);
        }
 
        /**
         * Decrement i modulo len.
         */
        private static int prevIndex(int i, int len) {
            return ((i - 1 >= 0) ? i - 1 : len - 1);
        }
 
        /**
         * Construct a new map initially containing (firstKey, firstValue).
         * ThreadLocalMaps are constructed lazily, so we only create
         * one when we have at least one entry to put in it.
         */
        ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
            table = new Entry[INITIAL_CAPACITY];
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }
...
}

最終的變量是放在了當前線程的 ThreadLocalMap 中,并不是存在 ThreadLocal 上,ThreadLocal 可以理解為只是ThreadLocalMap的封裝,傳遞了變量值。

3、內存泄漏問題

實際上 ThreadLocalMap 中使用的 key 為 ThreadLocal 的弱引用,弱引用的特點是,如果這個對象只存在弱引用,那么在下一次垃圾回收的時候必然會被清理掉。

所以如果 ThreadLocal 沒有被外部強引用的情況下,在垃圾回收的時候會被清理掉的,這樣一來 ThreadLocalMap中使用這個 ThreadLocal 的 key 也會被清理掉。但是,value 是強引用,不會被清理,這樣一來就會出現 key 為 null 的 value。

ThreadLocalMap實現中已經考慮了這種情況,在調用 set()get() remove() 方法的時候,會清理掉 key 為 null 的記錄。如果說會出現內存泄漏,那只有在出現了 key 為 null 的記錄后,沒有手動調用 remove() 方法,并且之后也不再調用 get() set()remove() 方法的情況下。

4、使用場景

如上文所述,ThreadLocal 適用于如下兩種場景

每個線程需要有自己單獨的實例
實例需要在多個方法中共享,但不希望被多線程共享
對于第一點,每個線程擁有自己實例,實現它的方式很多。例如可以在線程內部構建一個單獨的實例。ThreadLoca 可以以非常方便的形式滿足該需求。

對于第二點,可以在滿足第一點(每個線程有自己的實例)的條件下,通過方法間引用傳遞的形式實現。ThreadLocal 使得代碼耦合度更低,且實現更優雅。

1)存儲用戶Session

一個簡單的用ThreadLocal來存儲Session的例子:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
private static final ThreadLocal threadSession = new ThreadLocal();
 
    public static Session getSession() throws InfrastructureException {
        Session s = (Session) threadSession.get();
        try {
            if (s == null) {
                s = getSessionFactory().openSession();
                threadSession.set(s);
            }
        } catch (HibernateException ex) {
            throw new InfrastructureException(ex);
        }
        return s;
    }

2)解決線程安全的問題

比如Java7中的SimpleDateFormat不是線程安全的,可以用ThreadLocal來解決這個問題:

?
1
2
3
4
5
6
7
8
9
10
11
12
public class DateUtil {
    private static ThreadLocal<SimpleDateFormat> format1 = new ThreadLocal<SimpleDateFormat>() {
        @Override
        protected SimpleDateFormat initialValue() {
            return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        }
    };
 
    public static String formatDate(Date date) {
        return format1.get().format(date);
    }
}

這里的DateUtil.formatDate()就是線程安全的了。(Java8里的 [java.time.format.DateTimeFormatter]

(http://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html) 是線程安全的,Joda time里的DateTimeFormat也是線程安全的)。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Context {
 
    private String name;
    private String cardId;
 
    public String getCardId() {
        return cardId;
    }
 
    public void setCardId(String cardId) {
        this.cardId = cardId;
    }
 
    public String getName() {
        return this.name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
}
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class ExecutionTask implements Runnable {
 
    private QueryFromDBAction queryAction = new QueryFromDBAction();
 
    private QueryFromHttpAction httpAction = new QueryFromHttpAction();
 
    @Override
    public void run() {
 
        final Context context = new Context();
        queryAction.execute(context);
        System.out.println("The name query successful");
        httpAction.execute(context);
        System.out.println("The cardId query successful");
 
        System.out.println("The Name is " + context.getName() + " and CardId " + context.getCardId());
    }
}
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class QueryFromDBAction {
 
    public void execute(Context context) {
 
        try {
            Thread.sleep(1000L);
            String name = "Jack " + Thread.currentThread().getName();
            context.setName(name);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
 
 
    public void execute(Context context) {
        String name = context.getName();
        String cardId = getCardId(name);
        context.setCardId(cardId);
    }
 
    private String getCardId(String name) {
        try {
            Thread.sleep(1000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "444555" + Thread.currentThread().getId();
    }
}
?
1
2
3
4
5
6
7
8
9
10
public class ContextTest {
 
    public static void main(String[] args) {
 
        IntStream.range(1, 5)
                .forEach(i ->
                        new Thread(new ExecutionTask()).start()
                );
    }
}

The name query successful
The name query successful
The name query successful
The name query successful
The cardId query successful
The Name is Jack Thread-0 and CardId 44455511
The cardId query successful
The Name is Jack Thread-1 and CardId 44455512
The cardId query successful
The Name is Jack Thread-2 and CardId 44455513
The cardId query successful
The Name is Jack Thread-3 and CardId 44455514

問題:需要在每個調用Context的方法中傳入進去

?
1
2
public void execute(Context context) {
}

3)使用ThreadLocal重新設計一個上下文設計模式

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public final class ActionContext {
 
    private static final ThreadLocal<Context> threadLocal = new ThreadLocal() {
        @Override
        protected Object initialValue() {
            return new Context();
        }
    };
 
    public static ActionContext getActionContext() {
        return ContextHolder.actionContext;
    }
 
    public Context getContext() {
 
        return threadLocal.get();
    }
 
    private static class ContextHolder {
        private final static ActionContext actionContext = new ActionContext();
 
    }
}
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Context {
 
    private String name;
    private String cardId;
 
    public String getCardId() {
        return cardId;
    }
 
    public void setCardId(String cardId) {
        this.cardId = cardId;
    }
 
    public String getName() {
        return this.name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
}
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class ExecutionTask implements Runnable {
 
    private QueryFromDBAction queryAction = new QueryFromDBAction();
 
    private QueryFromHttpAction httpAction = new QueryFromHttpAction();
 
    @Override
    public void run() {
 
        queryAction.execute();
        System.out.println("The name query successful");
        httpAction.execute();
        System.out.println("The cardId query successful");
 
        final Context context = ActionContext.getActionContext().getContext();
        System.out.println("The Name is " + context.getName() + " and CardId " + context.getCardId());
    }
}
?
1
2
3
4
5
6
7
8
9
10
11
12
13
public class QueryFromDBAction {
 
    public void execute() {
 
        try {
            Thread.sleep(1000L);
            String name = "Jack " + Thread.currentThread().getName();
            ActionContext.getActionContext().getContext().setName(name);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class QueryFromHttpAction {

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
    public void execute() {
        Context context = ActionContext.getActionContext().getContext();
        String name = context.getName();
        String cardId = getCardId(name);
        context.setCardId(cardId);
 
 
    }
 
    private String getCardId(String name) {
        try {
            Thread.sleep(1000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "444555" + Thread.currentThread().getId();
    }
}
?
1
2
3
4
5
6
7
8
9
10
public class ContextTest {
 
    public static void main(String[] args) {
 
        IntStream.range(1, 5)
                .forEach(i ->
                        new Thread(new ExecutionTask()).start()
                );
    }
}

The name query successful
The name query successful
The name query successful
The name query successful
The cardId query successful
The Name is Jack Thread-3 and CardId 44455514
The cardId query successful
The cardId query successful
The Name is Jack Thread-0 and CardId 44455511
The cardId query successful
The Name is Jack Thread-2 and CardId 44455513
The Name is Jack Thread-1 and CardId 44455512

這樣寫 執行過程中不會看到context的定義和聲明

注意:在使用之前記得將上個線程中context舊值清除調,否則會重復調用(比如線程池操作)

4)ThreadLocal注意事項

臟數據

線程復用會產生臟數據。由于結程池會重用Thread對象,那么與Thread綁定的類的靜態屬性ThreadLocal變量也會被重用。如果在實現的線程run()方法體中不顯式地調用remove() 清理與線程相關的ThreadLocal信息,那么倘若下一個結程不調用set() 設置初始值,就可能get() 到重用的線程信息,包括 ThreadLocal所關聯的線程對象的value值。

內存泄漏

通常我們會使用使用static關鍵字來修飾ThreadLocal(這也是在源碼注釋中所推薦的)。在此場景下,其生命周期就不會隨著線程結束而結束,寄希望于ThreadLocal對象失去引用后,觸發弱引用機制來回收EntryValue就不現實了。如果不進行remove() 操作,那么這個線程執行完成后,通過ThreadLocal對象持有的對象是不會被釋放的。

以上兩個問題的解決辦法很簡單,就是在每次用完ThreadLocal時, 必須要及時調用 remove()方法清理。

父子線程共享線程變量

很多場景下通過ThreadLocal來透傳全局上下文,會發現子線程的value和主線程不一致。比如用ThreadLocal來存儲監控系統的某個標記位,暫且命名為traceId。某次請求下所有的traceld都是一致的,以獲得可以統一解析的日志文件。但在實際開發過程中,發現子線程里的traceld為null,跟主線程的并不一致。這就需要使用InheritableThreadLocal來解決父子線程之間共享線程變量的問題,使整個連接過程中的traceId一致。

到此這篇關于Java多線程 ThreadLocal原理解析的文章就介紹到這了,更多相關Java多線程 ThreadLocal內容請搜索服務器之家以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持服務器之家!

原文鏈接:https://juejin.cn/post/7022879808967639070

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 国产精品久久久久久影院8一贰佰 | 久久国产免费视频 | 成人国产精品一区二区毛片在线 | 亚洲小视频在线 | 欧美 国产 亚洲 卡通 综合 | 久草在线高清视频 | 亚洲一二三久久 | 日本欧美国产 | 蜜桃av网| 激情视频免费看 | 欧美黄色试片 | 日韩做爰视频免费 | 成年人高清视频在线观看 | 91福利免费观看 | 成人一级视频 | 久久久久国产成人精品亚洲午夜 | 精品视频一区二区三区四区 | 日本aⅴ在线 | 久久免费观看一级毛片 | 成人黄色短视频在线观看 | 欧美激情第一区 | 一级黄色性感片 | 久久久久亚洲a | 天堂成人一区二区三区 | 欧美日韩亚洲国产精品 | 一区二区免费看 | 亚洲91网 | 亚洲性一区 | 男女羞羞视频在线免费观看 | 狼网| 99久久久精品 | 叶子楣成人爽a毛片免费啪啪 | 精品一区二区三区免费毛片爱 | 欧美成年人在线视频 | 国产免费看片 | 有色视频在线观看 | 72pao成人国产永久免费视频 | 亚洲性夜色噜噜噜7777 | 国产91对白叫床清晰播放 | 男人的天堂色偷偷 | 久久最新网址 |