ThreadLocal được triển khai như thế nào? Nó được triển khai bằng Java (sử dụng một số bản đồ đồng thời từ ThreadID đến đối tượng) hay nó sử dụng một số móc nối JVM để làm việc đó hiệu quả hơn?
Câu trả lời:
Tất cả các câu trả lời ở đây đều đúng, nhưng hơi đáng thất vọng vì chúng hơi che lấp cách ThreadLocal
triển khai thông minh của nó. Tôi chỉ đang xem mã nguồnThreadLocal
và rất ấn tượng về cách nó được triển khai.
Thực hiện Naive
Nếu tôi yêu cầu bạn triển khai một ThreadLocal<T>
lớp được cung cấp bởi API được mô tả trong javadoc, bạn sẽ làm gì? Một triển khai ban đầu có thể sẽ là ConcurrentHashMap<Thread,T>
sử dụng Thread.currentThread()
làm khóa của nó. Điều này sẽ hoạt động hợp lý nhưng có một số nhược điểm.
ConcurrentHashMap
là một lớp khá thông minh, nhưng cuối cùng nó vẫn phải đối phó với việc ngăn chặn nhiều luồng liên kết với nó theo bất kỳ cách nào và nếu các luồng khác nhau thường xuyên đánh nó, sẽ có hiện tượng chậm.Triển khai thân thiện với GC
Ok, hãy thử lại, hãy giải quyết vấn đề thu gom rác bằng cách sử dụng các tham chiếu yếu . Xử lý các tài liệu tham khảo yếu có thể khó hiểu, nhưng chỉ cần sử dụng một bản đồ được xây dựng như vậy là đủ:
Collections.synchronizedMap(new WeakHashMap<Thread, T>())
Hoặc nếu chúng ta đang sử dụng Guava (và chúng ta nên làm như vậy!):
new MapMaker().weakKeys().makeMap()
Điều này có nghĩa là một khi không ai khác đang nắm giữ Chủ đề (ngụ ý rằng nó đã kết thúc) thì khóa / giá trị có thể được thu thập rác, đây là một cải tiến, nhưng vẫn không giải quyết được vấn đề tranh chấp chủ đề, có nghĩa là cho đến nay chúng ta ThreadLocal
không phải là tất cả tuyệt vời của một lớp học. Hơn nữa, nếu ai đó quyết định giữ Thread
các đồ vật sau khi chúng hoàn thành, chúng sẽ không bao giờ bị GC'ed, và do đó các đồ vật của chúng ta cũng vậy, mặc dù về mặt kỹ thuật chúng không thể tiếp cận được.
Triển khai Thông minh
Chúng tôi đã từng nghĩ về việc ThreadLocal
ánh xạ các chuỗi tới các giá trị, nhưng có lẽ đó không thực sự là cách đúng để nghĩ về nó. Thay vì nghĩ về nó như một ánh xạ từ Threads đến các giá trị trong mỗi đối tượng ThreadLocal, điều gì sẽ xảy ra nếu chúng ta nghĩ về nó như một ánh xạ các đối tượng ThreadLocal đến các giá trị trong mỗi Thread ? Nếu mỗi luồng lưu trữ ánh xạ và ThreadLocal chỉ cung cấp một giao diện đẹp cho ánh xạ đó, chúng ta có thể tránh tất cả các vấn đề của các lần triển khai trước đó.
Một triển khai sẽ trông giống như sau:
// called for each thread, and updated by the ThreadLocal instance
new WeakHashMap<ThreadLocal,T>()
Không cần phải lo lắng về tính đồng thời ở đây, vì chỉ có một luồng sẽ truy cập vào bản đồ này.
Các nhà phát triển Java có lợi thế lớn hơn chúng tôi ở đây - họ có thể trực tiếp phát triển lớp Thread và thêm các trường cũng như hoạt động vào nó, và đó chính xác là những gì họ đã làm.
Trong java.lang.Thread
đó có những dòng sau:
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
Như nhận xét gợi ý thực sự là một ánh xạ gói-riêng của tất cả các giá trị đang được theo dõi bởi ThreadLocal
các đối tượng cho việc này Thread
. Việc thực hiện ThreadLocalMap
không phải là a WeakHashMap
, nhưng nó tuân theo cùng một hợp đồng cơ bản, bao gồm việc nắm giữ các khóa của nó bằng cách tham chiếu yếu.
ThreadLocal.get()
sau đó được thực hiện như vậy:
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();
}
Và ThreadLocal.setInitialValue()
như vậy:
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
Về cơ bản, sử dụng một bản đồ trong Chủ đề này để chứa tất cả các ThreadLocal
đối tượng của chúng ta . Bằng cách này, chúng ta không bao giờ cần phải lo lắng về các giá trị trong các Luồng khác (theo ThreadLocal
nghĩa đen chỉ có thể truy cập các giá trị trong Luồng hiện tại) và do đó không có vấn đề về đồng thời. Hơn nữa, sau khi Thread
hoàn thành, bản đồ của nó sẽ tự động được GC'ed và tất cả các đối tượng cục bộ sẽ được dọn dẹp. Ngay cả khi Thread
được giữ lại, các ThreadLocal
đối tượng được giữ bằng tham chiếu yếu và có thể được dọn sạch ngay khi ThreadLocal
đối tượng ra khỏi phạm vi.
Không cần phải nói, tôi khá ấn tượng bởi cách triển khai này, nó khá thanh lịch xoay quanh rất nhiều vấn đề đồng thời (phải thừa nhận bằng cách tận dụng lợi thế là một phần của Java lõi, nhưng điều đó có thể tha thứ cho chúng vì nó là một lớp thông minh) và cho phép nhanh chóng và truy cập an toàn luồng vào các đối tượng chỉ cần được truy cập bởi một luồng tại một thời điểm.
Việc triển khai tl; dr ThreadLocal
khá tuyệt và nhanh hơn / thông minh hơn nhiều so với những gì bạn có thể nghĩ lúc đầu.
Nếu bạn thích câu trả lời này, bạn cũng có thể đánh giá cao cuộc thảo luậnThreadLocalRandom
(ít chi tiết hơn) của tôi .
Thread
/ ThreadLocal
đoạn mã lấy từ việc triển khai Java 8 của Oracle / OpenJDK .
WeakHashMap<String,T>
giới thiệu một số vấn đề, nó không phải là luồng an toàn và nó "chủ yếu được thiết kế để sử dụng với các đối tượng chính có phương thức bằng nhau kiểm tra nhận dạng đối tượng bằng toán tử ==" - vì vậy thực sự sử dụng Thread
đối tượng làm khóa sẽ tốt hơn. Tôi sẽ đề xuất sử dụng bản đồ Guava thinKeys mà tôi mô tả ở trên cho trường hợp sử dụng của bạn.
Thread.exit()
được gọi và bạn sẽ thấy threadLocals = null;
ngay tại đó. Một bình luận đề cập đến lỗi này mà bạn cũng có thể thích đọc.
Ý bạn là java.lang.ThreadLocal
. Nó khá đơn giản, thực sự, nó chỉ là một Bản đồ của các cặp tên-giá trị được lưu trữ bên trong mỗi Thread
đối tượng (xem Thread.threadLocals
trường). API ẩn chi tiết triển khai đó, nhưng đó ít nhiều là tất cả.
Các biến ThreadLocal trong Java hoạt động bằng cách truy cập vào HashMap do cá thể Thread.currentThread () nắm giữ.
Giả sử bạn sắp triển khai ThreadLocal
, làm thế nào để bạn làm cho nó trở nên cụ thể? Tất nhiên, phương pháp đơn giản nhất là tạo một trường không tĩnh trong lớp Thread, chúng ta hãy gọi nó threadLocals
. Bởi vì mỗi luồng được đại diện bởi một cá thể luồng, vì vậy threadLocals
trong mỗi luồng cũng sẽ khác nhau. Và đây cũng là những gì Java làm:
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
Cái gì ThreadLocal.ThreadLocalMap
ở đây? Bởi vì bạn chỉ có một threadLocals
cho một luồng, vì vậy nếu bạn chỉ đơn giản lấy threadLocals
của bạn ThreadLocal
(giả sử, định nghĩa threadLocals là Integer
), bạn sẽ chỉ có một ThreadLocal
cho một luồng cụ thể. Điều gì xảy ra nếu bạn muốn nhiều ThreadLocal
biến cho một chủ đề? Cách đơn giản nhất là tạo threadLocals
một HashMap
, key
mỗi mục nhập là tên của ThreadLocal
biến và value
giá trị của mỗi mục nhập là giá trị của ThreadLocal
biến. Một chút khó hiểu? Giả sử chúng ta có hai luồng, t1
và t2
. chúng lấy cùng một Runnable
trường hợp là tham số của hàm Thread
tạo và cả hai đều có hai ThreadLocal
biến có tên tlA
và tlb
. Đây là những gì nó giống như.
t1.tlA
+-----+-------+
| Key | Value |
+-----+-------+
| tlA | 0 |
| tlB | 1 |
+-----+-------+
t2.tlB
+-----+-------+
| Key | Value |
+-----+-------+
| tlA | 2 |
| tlB | 3 |
+-----+-------+
Lưu ý rằng các giá trị được tạo ra bởi tôi.
Bây giờ nó có vẻ hoàn hảo. Nhưng là ThreadLocal.ThreadLocalMap
gì? Tại sao nó không chỉ sử dụng HashMap
? Để giải quyết vấn đề, hãy xem điều gì sẽ xảy ra khi chúng ta đặt một giá trị thông qua set(T value)
phương thức của ThreadLocal
lớp:
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
getMap(t)
chỉ đơn giản là trả lại t.threadLocals
. Bởi vì t.threadLocals
được bắt đầu để null
, vì vậy chúng tôi nhập createMap(t, value)
trước:
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
Nó tạo một phiên bản mới ThreadLocalMap
bằng cách sử dụng phiên bản hiện tại ThreadLocal
và giá trị được đặt. Hãy xem ThreadLocalMap
nó như thế nào, nó thực sự là một phần của ThreadLocal
lớp học
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;
}
}
...
/**
* 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);
}
...
}
Phần cốt lõi của ThreadLocalMap
lớp là phần Entry class
mở rộng WeakReference
. Nó đảm bảo rằng nếu luồng hiện tại thoát ra, nó sẽ được thu gom rác tự động. Đây là lý do tại sao nó sử dụng ThreadLocalMap
thay vì đơn giản HashMap
. Nó chuyển hiện tại ThreadLocal
và giá trị của nó làm tham số của Entry
lớp, vì vậy khi chúng ta muốn lấy giá trị, chúng ta có thể lấy nó từ table
đó, là một thể hiện của Entry
lớp:
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();
}
Đây là những gì giống như trong toàn bộ bức tranh:
Về mặt khái niệm, bạn có thể ThreadLocal<T>
coi a giống như việc giữ một Map<Thread,T>
lưu trữ các giá trị của luồng cụ thể, mặc dù đây không phải là cách nó thực sự được triển khai.
Các giá trị của thread-speci fi c được lưu trữ trong chính đối tượng Thread; khi luồng kết thúc, các giá trị chỉ định của luồng có thể được thu thập rác.
Tham khảo: JCIP