ThreadLocal adalah kelas utilitas yang cukup umum digunakan dalam paket java.lang di JDK, yang menyediakan fungsi konteks thread. Penerapannya yang paling umum mungkin adalah menggunakan ThreadLocal untuk menyimpan informasi penting seperti konteks pengguna setelah login.
Konteks thread juga dapat menyelesaikan banyak masalah yang tampaknya cukup rumit dengan elegan, misalnya:integrasi framework spring-mybatis:
Saat mengembangkan aplikasi dalam ekosistem spring, kita biasanya menyerahkan pengelolaan transaksi kepada spring, menggunakan anotasi transactional untuk mengonfigurasi pengaktifan transaksi, tingkat isolasi transaksi, dan informasi lainnya. Kita juga tidak mengoperasikan basis data secara langsung melalui jdbc, melainkan mengaksesnya melalui kerangka kerja mybatis. spring dan mybatis adalah dua kerangka kerja yang sepenuhnya terpisah, tetapi saat kita menggunakannya, connection basis data yang dibuka oleh spring seolah dapat langsung diperoleh dan digunakan oleh mybatis. Pada dasarnya kita hampir tidak perlu melakukan apa pun, cukup menambahkan paket jar mybatis-spring-boot-stater.
Kunci penerapan desain yang elegan ini adalah ThreadLocal. Prinsip umumnya adalah paket JAR stater pada akhirnya akan membantu kita menyertakan sebuah MybatisAutoConfiguration dan diinjeksikan melalui SPI ke dalam kontainer bean layanan kita, pada MybatisAutoConfiguration sudah dikonfigurasi di dalam mybatis yang diperlukan SqlSessionFactory,serta menyuntikkan milik spring sendiri berupa SpringManagedTransactionFactory(mengimplementasikan TransactionFactory,pabrik transaksi adalah mekanisme ekstensi yang disediakan mybatis, yang memungkinkan pengelolaan transaksi didelegasikan).
Alur eksekusi secara keseluruhan adalah metode yang diberi anotasi transactional pada akhirnya akan diproksi oleh AOP, kelas proksi akan mengambil sebuah connection dari dataSource dalam advice sebelum eksekusi, lalu menyimpannya dalam sebuah ThreadLocal(sebenarnya yang disimpan dalam ThreadLocal adalah sebuah map, dengan dataSource sebagai key dan connectionHolder yang setara dengan connection sebagai value), kemudian saat mybatis mengeksekusi sql, pertama-tama akan melalui TransactionFactory Mendapatkan connection, karena pada saat ini yang diinjeksi TransactionFactory Ya SpringManagedTransactionFactory, sehingga connection untuk dataSource terkait akan diambil dari ThreadLocal.
Pengantar singkat prinsip kerja ThreadLocal:
ThreadLocalMap merupakan tabel hash yang menggunakan probing linear untuk menangani benturan; setiap thread membuat satu salinan, dan setiap objek ThreadLocal memiliki satu threadLocalHashCode digunakan untuk menghitung kunci hash.
Bandingkan dengan HashMap,ThreadLocalMap Pemilihan algoritma penanganan benturan berupa pencarian linear alih-alih metode rantai mungkin didasarkan pada pertimbangan berikut:
1、Pertimbangan memori, karena skenario penggunaannya berbeda, jumlah data ThreadLocalMap umumnya jauh lebih kecil daripada HashMap. Saat jumlah datanya sedikit, metode perantaian akan menimbulkan banyak konsumsi ruang tambahan untuk pointer
2、Efisiensi, dengan berlandaskan 1, ketika jumlahnya kecil, ruang yang dihemat dapat digunakan untuk membuat tabel hash dengan faktor muat lebih rendah, mengurangi tabrakan, dan meningkatkan kecepatan pencarian rata-rata

Masalah
A Dalam skenario tugas berbasis kumpulan utas, terdapat masalah penerusan konteks, terutama dalam model pemrograman web berbasis Tomcat. Karena Tomcat sendiri menggunakan kumpulan utas untuk menangani interaksi permintaan-respons antara server dan klien, setiap aplikasi web terpaksa menangani secara manual masalah penerusan konteks utas dengan benar.
B Masalah efisiensi,jika aplikasi menggunakan terlalu banyak ThreadLocal sehingga tabel hash membesar,kecepatan pencarian akan menurun
Untuk masalah A, cara penyelesaian yang umum relatif seragam. Saya langsung menyertakan kode dari sistem kami
Solusi ini memiliki dua masalah:
1、Jika kemudian ada ThreadLocal baru yang perlu diteruskan dengan benar, kode harus diubah untuk menambahkan ThreadLocal baru itu ke dalam logika wrap. Jika ada yang terlewat, bug akan muncul
2、Dalam beberapa skenario, tugas mungkin dijalankan oleh thread pengirim itu sendiri, misalnya:
Ketika pool thread sudah penuh dan milik pool thread
RejectedExecutionHandlermenggunakanCallerRunsPolicyketika itu, tugas yang dikirim ke kumpulan utas akan langsung dijalankan di utas pengirim, dan logika pembersihan di bagian akhir akan menyebabkan hilangnya konteks utas pengirimDemikian pula, menggunakan
ForkJoinPool(termasuk eksekusi paralelStreamdanCompletableFuture, pada lapisan dasarnya menggunakanForkJoinPool)dalam skenario tersebut, yang diperluasForkJoinTaskakan dijalankan langsung dalam thread pengirim tugas. Pembersihan terakhir juga akan menyebabkan hilangnya konteks thread pengirim
Solusi:
Masalah 1:jika kelas ThreadLocal menyediakan API untuk mengetahui jumlah seluruh objek ThreadLocal yang saat ini ada dalam aplikasi, masalah ini akan mudah teratasi. Sayangnya, API tersebut tidak tersedia. Jika tetap ingin melakukannya, refleksi dapat digunakan untuk mengambil bagian dalam objek thread berupa inheritableThreadLocals(yaitu sebuah ThreadLocalMap)properti, lalu telusuri tabel hash dan ambil semua entry untuk memperoleh seluruh pasangan kunci-nilai threadlocal. Pendekatan lain adalah membungkus ThreadLocal milik jdk dengan satu lapisan tambahan untuk melengkapi fungsi-fungsi di atas。
Masalah2:Dapat diatasi dengan menyempurnakan logika wrap:ketika thread mulai berjalan, ambil dahulu konteks thread saat ini dan simpan sementara, lalu ganti konteks thread ini dengan konteks thread pengirim tugas. Sebelum thread selesai berjalan, jangan hapus konteksnya, melainkan gunakan data yang disimpan sementara untuk memulihkan konteks thread ini。
transmittable-thread-local
Solusi siap pakainya sudah tersedia, prinsip implementasinya bertolak dari dua arah di atas, dan alur ini diberi nama CRR(capture/replay/restore) pola。
Ada tiga cara penggunaan:
1 ### Pemodifikasi Runnable dan Callable
TransmittableThreadLocal<String> context = new TransmittableThreadLocal<>();
// =====================================================
// 在父线程中设置
context.set("value-set-in-parent");
Runnable task = new RunnableTask();
// 额外的处理,生成修饰了的对象ttl
Runnable Runnable ttlRunnable = TtlRunnable.get(task);
executorService.submit(ttlRunnable);
// =====================================================
// Task中可以读取,值是"value-set-in-parent"
String value = context.get();2 Mendekorasi kumpulan utas
ExecutorService executorService = ...
// 额外的处理,生成修饰了的对象
executorService executorService = TtlExecutors.getTtlExecutorService(executorService);
TransmittableThreadLocal<String> context = new TransmittableThreadLocal<>();
// =====================================================
// 在父线程中设置
context.set("value-set-in-parent");
Runnable task = new RunnableTask();
Callable call = new CallableTask();
executorService.submit(task);
executorService.submit(call);
// =====================================================
// Task或是Call中可以读取,值是"value-set-in-parent"
String value = context.get();3 ### Penggunaan Java Agent untuk memodifikasi JDK Kelas implementasi kumpulan utas
// ## 1. 框架上层逻辑,后续流程框架调用业务 ##
TransmittableThreadLocal<String> context = new TransmittableThreadLocal<>();
context.set("value-set-in-parent");
// ## 2. 应用逻辑,后续流程业务调用框架下层逻辑 ##
ExecutorService executorService = Executors.newFixedThreadPool(3);
Runnable task = new RunnableTask();
Callable call = new CallableTask();
executorService.submit(task);
executorService.submit(call);
// ## 3. 框架下层逻辑 ##
// Task或是Call中可以读取,值是"value-set-in-parent"
String value = context.get();Keuntungan menggunakan pendekatan java agent adalah tidak perlu menyisipkan perubahan ke kode aplikasi. Untuk kekurangannya, menurut saya masalah terbesar adalah jika lupa menambahkan parameter saat menjalankan jvm, hal itu bisa menyebabkan insiden.
Mari kita lihat sekilas logika kode sumbernya
Untuk masalah B, persoalan ini biasanya tidak dipertimbangkan dalam pengembangan aplikasi karena kepekaan terhadap efisiensi waktu tidak begitu tinggi. Namun, dalam pengembangan beberapa middleware tingkat rendah, optimasi kinerja mungkin lebih diperhatikan. Misalnya, netty memilih untuk mengimplementasikan sendiri FastThreadLocal Menggantikan ThreadLocal dari jdk, sekaligus menyediakan pelengkap berupa FastThreadLocalThread,di FastThreadLocalThread dan FastThreadLocal Jika digunakan bersama, kompleksitas waktu pencarian O(1) dapat selalu dijamin, prinsip solusinya adalah menukar ruang penyimpanan dengan waktu, untuk menghindari penurunan efisiensi kueri akibat benturan hash dalam tabel hash, akan dibuat sebuah array yang cukup panjang, setiap FastThreadLocal Saat dibangun, masing-masing akan diberi indeks array yang unik, ibarat setiap orang mendapat satu tempat; saat melakukan pencarian, cukup gunakan index sendiri untuk langsung mengambilnya dari array.
Berikut sekilas kode sumbernya