Kata pengantar
Sebagai kerangka kerja berkinerja tinggi, Netty merangkum dan mengoptimalkan banyak kelas di JDK, seperti kelas Thread. Netty menggunakan FastThreadLocalRunnable untuk menggabungkan semua Runnable yang dibuat oleh DefaultThreadFactory. Tujuan pengemasan adalah perbedaan dari metode yang dijalankan. Lihat kode:
public void run () { coba { runnable.run (); } akhirnya { FastThreadLocal.removeAll (); } }Seperti yang Anda lihat, ada baris ekstra FastThreadLocal.removeAll (). Seperti yang kita semua tahu, ThreadLocal yang disertakan dengan JDK memiliki risiko kebocoran memori di lingkungan kumpulan thread. Jelas, untuk menghindari bug ini, Netty mengemasnya kembali, dan ini Nama utas yang dienkapsulasi adalah FastThreadLocalRunnable, yang memiliki semantik jelas: ThreadLocal cepat! Berarti JDK hadir dengan lambat? Jadi mari kita lihat hari ini di mana cepat? Jika Anda tidak jelas tentang kebocoran memori ThreadLocal atau tentang ThreadLoca, Anda dapat beralih ke analisis kode sumber ThreadLocal dari pemrograman bersamaan.
1. Bagaimana cara menggunakannya?
Kasus cobaan
hasil operasi
2. Analisis metode konstruksi
Metode konstruksi
Dua variabel ditentukan dalam metode konstruksi. index dan cleanerFlagIndex, kedua variabel ini adalah int final. Dan semuanya berlalu
Dari metode InternalThreadLocalMap.nextVariableIndex ().
InternalThreadLocalMap.nextVariableIndex ()
variabel nextIndex
Metode ini diperoleh dengan auto-incrementing variabel int atom. Dengan kata lain, variabel cleanerFlagIndex lebih besar 1 daripada indeks, dan peran kedua variabel ini akan dilihat nanti bagaimana penggunaannya. Tidak di sini untuk saat ini.
3. Tetapkan analisis metode
set () metode
Langkah-langkah metodenya adalah sebagai berikut:
Metode set kecil sangat rumit di dalam. Non-pejuang harap evakuasi secepat mungkin!
Faktanya, 4 metode dipanggil di sini:
Mari kita bicara perlahan.
1. InternalThreadLocalMap.get ();
kode acara seperti di bawah ini:
public static InternalThreadLocalMap get () { Benang utas = Thread.currentThread (); if (instance utas FastThreadLocalThread) { return fastGet (utas (FastThreadLocalThread)); } lain { kembali slowGet (); } }Yang pertama adalah metode statis InternalThreadLocalMap, logika metode sangat sederhana, terutama didasarkan pada apakah benang saat ini adalah FastThreadLocalThread Netty untuk memanggil metode yang berbeda, satu cepat, yang lain lambat (bukan benang Netty lambat). Hahaha, penulis yang bernama Netty itu sangat jeli. Lalu mari kita lihat apa itu metode fastGet?
private static InternalThreadLocalMap fastGet (utas FastThreadLocalThread) { InternalThreadLocalMap threadLocalMap = thread.threadLocalMap (); if (threadLocalMap == null) { thread.setThreadLocalMap (threadLocalMap = baru InternalThreadLocalMap ()); } return threadLocalMap; }Logikanya sangat sederhana, dapatkan InternalThreadLocalMap dari utas saat ini, jika tidak, buat satu. Mari kita lihat metode konstruksinya.
Objek akhir public static UNSET = new Object (); private InternalThreadLocalMap () { super (newIndexedVariableTable ()); } private static Object newIndexedVariableTable () { Larik objek = Objek baru; Arrays.fill (array, UNSET); kembali array; } UnpaddedInternalThreadLocalMap (Object indexedVariables) { this.indexedVariables = indexedVariables; }Pemilik rumah menyatukan tiga metode terkait agar mudah dilihat. Pertama, metode konstruksi kelas induk UnpaddedInternalThreadLocalMap dipanggil oleh InternalThreadLocalMap, dan diteruskan dalam sebuah larik, dan ukuran default larik ini adalah 32, yang diisi dengan 32 objek kosong Referensi.
Jadi seperti apa metode slowGet itu? kode acara seperti di bawah ini:
ThreadLocal akhir statis < InternalThreadLocalMap > slowThreadLocalMap = ThreadLocal baru < InternalThreadLocalMap > (); private static InternalThreadLocalMap slowGet () { ThreadLocal < InternalThreadLocalMap > slowThreadLocalMap = UnpaddedInternalThreadLocalMap.slowThreadLocalMap; InternalThreadLocalMap ret = slowThreadLocalMap.get (); if (ret == null) { ret = new InternalThreadLocalMap (); slowThreadLocalMap.set (ret); } ret kembali; }Kodenya masih sangat sederhana, mari kita analisis: pertama-tama gunakan ThreadLocal JDK untuk mendapatkan InternalThreadLocalMap Netty, jika tidak, buat satu, dan setel InternalThreadLocalMap ini ke ThreadLocal JDK, lalu kembalikan InternalThreadLocalMap ini. Terlihat dari hal ini bahwa untuk meningkatkan performa, Netty masih menghindari penggunaan threadLocalMap JDK. Metodenya adalah menyelamatkan negara dengan kurva: setel InternalThreadLocalMap Netty di threadLocal JDK, lalu setel FastThreadLcoal Netty di InternalThreadLocalMap ini.
Nah, pada titik ini, kita telah selesai membaca metode InternalThreadLocalMap.get (), terutama untuk mendapatkan InternalThreadLocalMap dari utas saat ini. Jika tidak, buat satu. Peta ini secara internal memelihara larik, yang berbeda dari JDK.
Yang dipertahankan adalah peta yang menggunakan metode deteksi linier, terlihat dari struktur data yang mendasarinya, JDK sudah hilang, dan kecepatan pembacaannya sangat bervariasi, terutama bila jumlah datanya besar, kecepatan struktur data Netty masih Tidak ada perubahan, dan kecepatan JDK akan turun sesuai dengan metode deteksi linier.
2. setKnownNotUnset (threadLocalMap, nilai);
Jika InternalThreadLocalMap.get () mengembalikan InternalThreadLocalMap, panggil setKnownNotUnset (threadLocalMap, value); saat ini untuk beroperasi. kode acara seperti di bawah ini:
private boolean setKnownNotUnset (InternalThreadLocalMap threadLocalMap, nilai V) { if (threadLocalMap.setIndexedVariable (indeks, nilai)) { addToVariablesToRemove (threadLocalMap, this); kembali benar; } return false; }Melihat nama metode, itu menetapkan nilai, tetapi tidak tidak disetel, yang merupakan objek kosong. Ditetapkan oleh threadLocalMap.setIndexedVariable (indeks, nilai). Jika mengembalikan nilai true, panggil addToVariablesToRemove (threadLocalMap, this). Mari kita lihat kedua metode ini bersama-sama. Lihat yang pertama:
setIndexedVariable
public boolean setIndexedVariable (indeks int, nilai Objek) { Pencarian objek = indexedVariables; if (index < lookup.length) { Objek oldValue = pencarian; pencarian = nilai; return oldValue == UNSET; } lain { expandIndexedVariableTableAndSet (indeks, nilai); kembali benar; } }Pertama, dapatkan array 32-panjang, dan jika properti indeks FastThreadLocal kurang dari panjang array, atur nilainya ke slot yang ditentukan. Setel nilai slot asli ke objek kosong. Jika objek asli juga merupakan objek kosong, ia mengembalikan nilai true, jika tidak ia mengembalikan salah.
Bagaimana jika tidak cukup? Panggil metode expandIndexedVariableTableAndSet (indeks, nilai). Masukkan metode ini untuk melihat. Lihatlah nama metode untuk memperluas indeks dan mengatur nilainya.
private void expandIndexedVariableTableAndSet (indeks int, nilai Objek) { Objek oldArray = indexedVariables; final int oldCapacity = oldArray.length; int newCapacity = index; newCapacity | = newCapacity > > > 1; newCapacity | = newCapacity > > > 2; newCapacity | = newCapacity > > > 4; newCapacity | = newCapacity > > > 8; newCapacity | = newCapacity > > > 16; newCapacity ++; Objek newArray = Arrays.copyOf (oldArray, newCapacity); Arrays.fill (newArray, oldCapacity, newArray.length, UNSET); newArray = nilai; indexedVariables = newArray; }Kode di sini sangat familiar, dan ada juga kode seperti itu di HashMap, mari kita lihat:
Metode TableSizeFor di HashMap
Fungsi kode ini adalah menggandakan kapasitas aslinya. Dan pastikan bahwa hasilnya adalah pangkat 2. Di sini pendekatan Netty sama dengan HashMap. Perluas kapasitas ke pangkat 2 terdekat sesuai kapasitas aslinya. Misalnya jika kapasitas asli 32, perbesar kapasitas menjadi 64. Kemudian, isi isi array asli ke dalam array baru, dan isi objek kosong yang tersisa. , Dan kemudian tetapkan array baru ke variabel anggota indexedVariables. Menyelesaikan perluasan.
Kembali ke metode setKnownNotUnset, dalam situasi apa metode setIndexedVariable akan mengembalikan nilai true? Kapasitas diperbesar, atau kapasitas tidak ditambah, tetapi objek yang disisipkan tidak menggantikan objek lain, yaitu, slot asli adalah objek kosong. Dengan kata lain, ini akan mengembalikan nilai salah hanya jika objek diperbarui.
Dengan kata lain, ketika sebuah objek ditambahkan, metode addToVariablesToRemove akan dipanggil, seperti nama metode, menambahkan variabel dan kemudian menghapusnya. Mari kita lihat logika metode addToVariablesToRemove (threadLocalMap, this):
private static void addToVariablesToRemove (InternalThreadLocalMap threadLocalMap, FastThreadLocal < ? > variabel) { // Variabelnya adalah final statis, jadi biasanya 0 Objek v = threadLocalMap.indexedVariable (variableToRemoveIndex); Set < FastThreadLocal < ? > > variableToRemove; if (v == InternalThreadLocalMap.UNSET || v == null) { // Buat Set berdasarkan IdentityHashMap, generiknya adalah FastThreadLocal variableToRemove = Collections.newSetFromMap (IdentityHashMap baru < FastThreadLocal < ? > , Boolean > ()); // Tempatkan Set ini pada subskrip 0 dari larik Peta ini threadLocalMap.setIndexedVariable (variabelToRemoveIndex, variabelToRemove); } lain { // Jika bukan UNSET, artinya ini adalah operasi kedua, jadi bisa dipaksa ke Set variableToRemove = (Set < FastThreadLocal < ? > > ) v; } // Tujuan akhirnya adalah menempatkan FastThreadLocal di Set variableToRemove.add (variabel); }Tujuan dari metode ini adalah untuk menyimpan objek FastThreadLocal dalam Set Metode statis removeAll perlu menggunakan Set ini, yang dapat dengan cepat menghapus semua Nilai yang sesuai dengan FTL di thread Map. Jika Anda tidak menggunakan Set, Anda perlu melintasi InternalThreadLocalMap, dan kinerjanya tidak tinggi.
Setelah berbicara tentang metode setKnownNotUnset, mari kita bicara tentang metode registerCleaner.
3. registerCleaner (threadLocalMap);
Cara ini bisa dibilang agak ribet harap bersabar, berikut inti dari ftl (FastThreadLocal).
Pertama, izinkan saya berbicara tentang fungsi metode ini: daftarkan ftl ini ke thread pembersihan. Jika objek thread adalah gc, maka secara otomatis akan membersihkan ftl untuk mencegah kebocoran memori di JDK.
Mari masuk ke metode untuk melihat:
private void registerCleaner (final InternalThreadLocalMap threadLocalMap) { Arus benang = Thread.currentThread (); if (FastThreadLocalThread.willCleanupFastThreadLocals (current) || threadLocalMap.indexedVariable (cleanerFlagIndex)! = InternalThreadLocalMap.UNSET) { kembali; } threadLocalMap.setIndexedVariable (cleanerFlagIndex, Boolean.TRUE); ObjectCleaner.register (saat ini, Runnable baru () { public void run () { hapus (threadLocalMap); } }); }Tuan rumah menghapus komentar di kode sumber, mari kita bicara tentang metode ini:
Pertanyaannya adalah, bagaimana cara mendaftar? Mengapa masih membawa utas saat ini?
Mari kita lihat kode sumbernya:
public static void register (Objek objek, cleanupTask Runnable) { Referensi AutomaticCleanerReference = new AutomaticCleanerReference (objek, ObjectUtil.checkNotNull (cleanupTask, "cleanupTask")); LIVE_SET.add (referensi); // Periksa apakah sudah ada pembersih yang berjalan. if (CLEANER_RUNNING.compareAndSet (false, true)) { Thread terakhir pembersihanThread = FastThreadLocalThread baru (CLEANER_TASK); cleanupThread.setPriority (Thread.MIN_PRIORITY); AccessController.doPrivileged (PrivilegedAction baru < Kosong > () { public Void run () { cleanupThread.setContextClassLoader (null); kembali nol; } }); cleanupThread.setName (CLEANER_THREAD_NAME); cleanupThread.setDaemon (true); cleanupThread.start (); } }Pertama, buat objek pembersihan otomatis AutomaticCleanerReference, yang mewarisi WeakReference, jangan melihat metode konstruksinya, pertama-tama lihat yang berikut ini, letakkan instance yang dibuat ini ke dalam LIVE_SET, sebenarnya, ini adalah ConcurrentSet yang dienkapsulasi oleh Netty, lalu putuskan untuk menghapus Apakah utas sedang berjalan. Jika tidak, dan status perubahan CAS berhasil. Buat saja utas, tugas didefinisikan CLEANER_TASK, prioritas utas adalah yang terendah, pemuat kelas atas dan bawah adalah nol, namanya adalah objectCleanerThread, dan itu adalah utas latar belakang. Kemudian mulai utas ini. Jalankan CLEANER_TASK.
Lihat langkah demi langkah.
Pertama, metode konstruksi AutomaticCleanerReference adalah sebagai berikut:
ReferenceQueue akhir statis pribadi < Obyek > REFERENCE_QUEUE = ReferenceQueue baru < Obyek > (); AutomaticCleanerReference (Referensi objek, Runnable cleanupTask) { super (rujukan, REFERENCE_QUEUE); this.cleanupTask = cleanupTask; } void cleanup () { cleanupTask.run (); }Fungsi dari ReferenceQueue adalah ketika suatu objek didaur ulang maka objek tersebut akan ditambahkan ke antrian sehingga objek tersebut dapat dilacak. Pengaturan dapat menghidupkan kembali objek ini. Artinya, saat objek Thread didaur ulang, objek tersebut akan dimasukkan ke antrean referensi ini, dan mengapa? Kapan itu akan dibawa keluar? Mari kita lihat kapan harus mengeluarkannya:
kode acara seperti di bawah ini:
private static final Runnable CLEANER_TASK = new Runnable () { @Mengesampingkan public void run () { untuk (;;) { while (! LIVE_SET.isEmpty ()) { Referensi akhir AutomaticCleanerReference = (AutomaticCleanerReference) REFERENCE_QUEUE.remove (REFERENCE_QUEUE_POLL_TIMEOUT_MS); if (referensi! = null) { coba { reference.cleanup (); } tangkapan (Dapat dilempar diabaikan) { } LIVE_SET.remove (referensi); } } CLEANER_RUNNING.set (salah); if (LIVE_SET.isEmpty () ||! CLEANER_RUNNING.compareAndSet (false, true)) { istirahat; } } } };Kebetulan sekali! ! ! ! CLEANER_TASK yang menggunakan ReferenceQueue ini! ! ! ! Jangan bersemangat, mari kita lihat lebih dekat apa yang dilakukan tugas ini:
Sedikit bingung? Kemudian mari kita rangkum mengapa kami melakukannya di sini:
Saat kita menggunakan ftl di utas yang dibuat oleh kumpulan utas non-Netty, Netty akan mendaftarkan utas pembersih sampah (karena utas yang dibuat oleh kumpulan utas Netty pada akhirnya akan menjalankan metode removeAll, tidak akan ada kebocoran memori) untuk membersihkan utas ini Variabel ftl ini, dari kode di atas, kita tahu bahwa jika thread non-Netty menggunakan ftl, Netty akan tetap menggunakan ThreadLocal dari JDK, tetapi hanya meminjam satu slot untuk menempatkan Peta Netty, dan kemudian menempatkan Netty ftl di Peta. Oleh karena itu, kebocoran memori dapat terjadi saat menggunakan kumpulan benang. Untuk mengatasi masalah ini, Netty mendaftarkan ftl ini ke referensi GC dan mengikat objek thread ke referensi GC setiap kali ftl baru digunakan. Saat objek thread didaur ulang, ia juga akan membersihkan Map-nya. Semua ftl dalam ini memecahkan masalah, sama seperti memecahkan bug JDK Nio.
Nah, di sini, pada dasarnya kami telah menyerap semua esensi dari FastThreadLocal Netty. ftl tidak hanya cepat, tetapi juga aman. Ini cepat untuk menggunakan array sebagai ganti Peta dari metode deteksi linier, dan aman untuk membersihkan ftl setiap kali utas didaur ulang, tanpa mengkhawatirkan kebocoran memori.
Metode selanjutnya sangat sederhana. Mari kita selesaikan menonton
4. hapus ();
Setiap kali objek kosong disetel, metode hapus dipanggil. Mari kita lihat metode tersebut. Kode sumbernya adalah sebagai berikut:
public final void remove () { hapus (InternalThreadLocalMap.getIfSet ()); } public static InternalThreadLocalMap getIfSet () { Benang utas = Thread.currentThread (); if (instance utas FastThreadLocalThread) { return ((FastThreadLocalThread) thread) .threadLocalMap (); } return slowThreadLocalMap.get (); } public final void hapus (InternalThreadLocalMap threadLocalMap) { if (threadLocalMap == null) { kembali; } // Hapus dan kembalikan nilai yang sesuai dengan indeks ThreadLocal saat ini dalam larik Peta Objek v = threadLocalMap.removeIndexedVariable (indeks); // Keluarkan Set dari posisi subskrip 0 dari larik Peta dan hapus ThreadLocal saat ini removeFromVariablesToRemove (threadLocalMap, this); if (v! = InternalThreadLocalMap.UNSET) { coba { // Tidak melakukan apa-apa secara default, pengguna dapat mewarisi FastThreadLocal dan mendefinisikan ulang metode ini. onRemoval ((V) v); } catch (Exception e) { PlatformDependent.throwException (e); } } }Pemilik rumah menggabungkan ketiga metode ini bersama-sama, pertama-tama dapatkan threadLocalMap dari utas saat ini, dan kemudian seperti yang tertulis di komentar: hapus nilai peta di subskrip yang sesuai dengan ftl, lalu hapus ftl di Set di subskrip 0 peta. Cegah kesalahan penilaian dengan metode isSet. Akhirnya, jika pengguna mengganti metode onRemoval, itu akan dipanggil, yang merupakan metode kosong secara default. Pengguna dapat mengganti metode onRemoval dan metode inisialisasi.
4. Dapatkan analisis metode
Metode get bahkan lebih sederhana, kodenya adalah sebagai berikut:
public final V get () { InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.get (); Objek v = threadLocalMap.indexedVariable (indeks); if (v! = InternalThreadLocalMap.UNSET) { kembali (V) v; } Nilai V = menginisialisasi (threadLocalMap); registerCleaner (threadLocalMap); nilai kembali; }Pertama dapatkan peta utas saat ini, kemudian dapatkan nilai sesuai dengan indeks ftl, dan kemudian kembalikan. Jika itu adalah objek kosong, yaitu, itu tidak disetel, itu akan dikembalikan melalui inisialisasi. Metode inisialisasi akan mengatur nilai kembali ke slot peta dan memasukkannya ke dalam Set. Terakhir, coba daftarkan pembersih.
5. Analisis metode hapus Semua
Metode ini dipanggil di blok terakhir dari utas default Netty. kode acara seperti di bawah ini:
public static void removeAll () { InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.getIfSet (); if (threadLocalMap == null) { kembali; } coba { Objek v = threadLocalMap.indexedVariable (variableToRemoveIndex); if (v! = null v! = InternalThreadLocalMap.UNSET) { @Suppressings ("tidak ada") Set < FastThreadLocal < ? > > variableToRemove = (Set < FastThreadLocal < ? > > ) v; FastThreadLocal < ? > variableToRemoveArray = variableToRemove.toArray (FastThreadLocal baru); untuk (FastThreadLocal < ? > tlv: variableToRemoveArray) { tlv.remove (threadLocalMap); } } } akhirnya { InternalThreadLocalMap.remove (); } }Ini sangat sederhana: Pertama dapatkan peta utas saat ini, kemudian dapatkan Set, ubah Set menjadi larik, lintasi larik, dan panggil metode hapus ftl. Terakhir, hapus atribut map di utas.
Untuk menyimpulkan
Sekarang mari kita rangkum FastThreadLocal.
Disebut Cepat karena tidak menggunakan Peta JDK yang menggunakan metode deteksi linier. Jika Anda menggunakan utas yang dibuat oleh pabrik kumpulan utas Netty dan cocok dengan ftl Netty, kinerjanya sangat baik. Jika Anda menggunakan utas khusus, cocokkan Kinerja ftl akan lebih baik daripada JDK Catatan: ftl tidak memiliki risiko kebocoran memori JDK.
Tetapi melakukan ini bukan tanpa biaya, karena setiap ftl adalah subskrip unik, dan subskrip ini bertambah 2 setiap kali Anda membuat objek ftl. Jika subskrip Anda sangat besar, utas Anda Peta juga harus meningkat sesuai, dapat dibayangkan bahwa jika sejumlah besar objek ftl dibuat, pemborosan array ini sangat objektif. Jelas sekali, ini adalah cara untuk mengubah ruang ke waktu.
Biasanya, ftl adalah objek statis, jadi tidak akan sebanyak yang kita asumsikan. Jika digunakan secara tidak benar, banyak memori yang akan terbuang percuma.
Tetapi keuntungan dari resiko ini jelas. Diuji pada mesin host, kinerja pembacaan ftl adalah sekitar 5 kali lipat dari JDK, dan kecepatan tulis sekitar 20% lebih cepat.
FastThreadLocal memenuhi namanya, cepat dan aman!
- Morning Post: Liverpool dan Origi memperbarui kontrak jangka panjang mereka, harga total Inter Milan 70 juta euro dikutip Lukaku
- Dia pernah menjadi siswa terbaik dalam ujian masuk perguruan tinggi dan memenangkan beasiswa dari Universitas Peking, tetapi kemudian dia berlari kembali ke pedesaan: hanya untuk pastoral di hatinya
- BBC mengungkap kebenaran tentang olahraga, dan itu merongrong akal sehat: olahraga oleh ratusan juta orang tidak berguna