Ushbu ma'ruzada biz java.lang.ThreadLocal<> klassining umuman qanday ishlashini va uni ko‘p oqimli muhitda qanday ishlatishni muhokama qilamiz.
ThreadLocal klassi o'zgaruvchilarni saqlash uchun ishlatiladi. Bu klassning o'ziga xosligi shundaki, u har bir foydalanayotgan oqim uchun alohida, mustaqil qiymat nusxasini saqlaydi.
Ushbu klassning ishlashini batafsilroq ko'rib chiqsak, biz buni oqim→qiymat ko‘rinishidagi Map deb tasavvur qilishimiz mumkin, u foydalanishda joriy oqim uchun qiymatga murojaat qiladi.
ThreadLocal klassining konstruktori
| Konstruktor | Harakat |
|---|---|
| ThreadLocal() | Java'da bo'sh o'zgaruvchi yaratadi |
Metodlar
| Metod | Harakat |
|---|---|
| get() | Joriy oqimning mahalliy o'zgaruvchisi qiymatini qaytaradi |
| set() | Joriy oqim uchun mahalliy o'zgaruvchi qiymatini belgilaydi |
| remove() | Joriy oqimning mahalliy o'zgaruvchisi qiymatini o'chiradi |
| ThreadLocal.withInitial() | Boshlang'ich qiymat belgilaydigan qo'shimcha fabrika metodi |
get() & set()
Misol yozamiz, ikkita hisoblagich yarataylik. Birinchi oddiy o'zgaruvchi oqimlar sonini hisoblash uchun, ikkinchisini esa ThreadLocal ichiga qo'yamiz va ularning birgalikda qanday ishlashini ko'ramiz. Avval ThreadDemo klassini yoshamizam, bu Runnable dan nasl olgan, unda ma'lumotlarimiz va asosiy run() metodi saqlanadi, shuningdek, hisoblagichlarni ekranga chiqarish uchun metod qo'shamiz:
class ThreadDemo implements Runnable {
int counter;
ThreadLocal<Integer> threadLocalCounter = new ThreadLocal<>();
public void run() {
counter++;
if(threadLocalCounter.get() != null) {
threadLocalCounter.set(threadLocalCounter.get() + 1);
} else {
threadLocalCounter.set(0);
}
printCounters();
}
public void printCounters(){
System.out.println("Counter: " + counter);
System.out.println("threadLocalCounter: " + threadLocalCounter.get());
}
}
Har bir ishga tushirishda counter o'zgaruvchisini oshirib boramiz, qo'shimcha ravishda get() metodini chaqiramiz, ThreadLocal o'zgaruvchisidan ma'lumot olish uchun. Agar yangi oqimda ma'lumotlar bo'lmasa, biz 0 ni qo'yamiz, agar ma'lumotlar mavjud bo'lsa — bitta oshiramiz. Endi asosiy klass main ni yozamiz:
public static void main(String[] args) {
ThreadDemo threadDemo = new ThreadDemo();
Thread t1 = new Thread(threadDemo);
Thread t2 = new Thread(threadDemo);
Thread t3 = new Thread(threadDemo);
t1.start();
t2.start();
t3.start();
}
Sinovdan so'ng ko‘rishimiz mumkinki, ThreadLocal o'zgaruvchisi ishlatiladigan oqimdan qat'i nazar o'zgarmaydi, ammo oqimlar soni esa oshmoqda.
Counter: 2
Counter: 3
threadLocalCounter: 0
threadLocalCounter: 0
threadLocalCounter: 0
Process finished with exit code 0
remove()
remove metodining ishlashini tushunish uchun ThreadDemo klassining kodini biroz o'zgartiramiz:
if(threadLocalCounter.get() != null) {
threadLocalCounter.set(threadLocalCounter.get() + 1);
} else {
if (counter % 2 == 0) {
threadLocalCounter.remove();
} else {
threadLocalCounter.set(0);
}
}
Bu kodda biz aniqlaymiz, agar bu oqim hisoblagichi — juft son bo'lsa, remove() metodini ThreadLocal o'zgaruvchimizda chaqiramiz. Kod natijasi:
threadLocalCounter: 0
Counter: 2
threadLocalCounter: null
Counter: 1
threadLocalCounter: 0
Process finished with exit code 0
Bu erda osonlik bilan ko'rishimiz mumkinki, ikkinchi oqimda ThreadLocal o'zgaruvchisi null ga teng.
ThreadLocal.withInitial()
Ushbu metod oqimning mahalliy o'zgaruvchisini yaratadi.
ThreadDemo klassining realizatsiyasi:
class ThreadDemo implements Runnable {
int counter;
ThreadLocal<Integer> threadLocalCounter = ThreadLocal.withInitial(() -> 1);
public void run() {
counter++;
printCounters();
}
public void printCounters(){
System.out.println("Counter: " + counter);
System.out.println("threadLocalCounter: " + threadLocalCounter.get());
}
}
Kodni natijasini ko'rishimiz mumkin:
Counter: 2
Counter: 3
threadLocalCounter: 1
threadLocalCounter: 1
threadLocalCounter: 1
Process finished with exit code 0
Bunday o'zgaruvchilarni foydalanish nima uchun kerak?
ThreadLocal java.lang.Thread ijro etish oqimiga nisbatan mahalliy o'zgaruvchilar ustida abstraktsiyani taqdim etadi.
ThreadLocal o'zgaruvchilari oddiylardan farq qiladi, chunki har bir oqim o'ziga xos, individual tashkillashtirilgan o'zgaruvchini foydalanadi, unga uchun get() yoki set() metodlari orqali qo‘shimcha qiladi.
Har bir oqim, ya'ni Thread klassining nusxasiga ThreadLocal o'zgaruvchilarni bog'langan jadvali bor. Jadvalning kalitlari ThreadLocal klassining obyektlariga ishoralar, qiymatlari esa ThreadLocal o'zgaruvchilar tomonidan "qo‘lga olingan" obyektlarga ishoralar.
Nima uchun Random orqali tasodifiy raqamlar generatsiyasi ko‘p qirrali ilovalar uchun mos emas?
Tasodifiy raqam olish uchun Random klassidan foydalanamiz. Ammo ko‘pqirrali muhitda hammasi yaxshi ishlaydimi? Aslida, yo'q. Random ko‘pqirrali muhit uchun mos emas, chunki bir nechta oqimlar shu sinfga bir vaqtning o'zida murojaat qilganda, u kamroq ishlab chiqaradi.
Buning uchun JDK 7 da ko‘pqirrali muhitda tasodifiy raqamlar yaratish uchun java.util.concurrent.ThreadLocalRandom klassi kiritildi. U ikkita sinfdan iborat: ThreadLocal va Random.
Bitta oqim tomonidan olingan tasodifiy raqam boshqa oqimdan mustaqil, java.util.Random esa global tasodifiy raqamlarni taqdim etadi. Shuningdek, Random dan farqli o'laroq, ThreadLocalRandom aniq boshlang'ich qiymatning aniq belgisini qo‘llab-quvvatlamaydi. Buning o‘rniga, u Random dan meros bo‘lgan setSeed() metodini qayta aniqlab, chaqirilganda har doim UnsupportedOperationException ni chiqaradi.
ThreadLocalRandom metodlarini ko'rib chiqaylik:
| Metod | Harakat |
|---|---|
| ThreadLocalRandom current() | Oqimning joriy ThreadLocalRandom ni qaytaradi. |
| int next(int bits) | Keyingi pseudotasodifiy raqam yaratadi. |
| double nextDouble(double least, double bound) | Berilgan minimum qiymat (least) (shu jumladan) va chegaraviy (bound) (istisno) o'rtasida pseudotasodifiy, bir xilda tarqalgan qiymatni qaytaradi. |
| int nextInt(int least, int bound) | Berilgan minimum qiymat (shu jumladan) va bog'langan (istisno) o'rtasida pseudotasodifiy, bir xilda tarqalgan qiymatni qaytaradi. |
| long nextLong(long n) | 0 (shu jumladan) va berilgan qiymat (istisno) o'rtasida pseudotasodifiy, bir xilda tarqalgan qiymatni qaytaradi. |
| long nextLong(long least, long bound) | Berilgan minimum qiymat (shu jumladan) va bog'langan (istisno) o'rtasida pseudotasodifiy, bir xilda tarqalgan qiymatni qaytaradi. |
| void setSeed(long seed) | UnsupportedOperationException ni chiqaradi. Bu generatorda boshlang'ich raqamni qo'yish qo'llab-quvvatlanmaydi. |
ThreadLocalRandom.current() yordamida tasodifiy ma'lumotlarni olish
ThreadLocalRandom ThreadLocal va Random sinflarining kombinatsiyasidir. Shu tariqa, u ko‘pqirrali muhitda yaxshiroq ishlashni ta'minlaydi, Random nusxalari uchun har qanday parallel kirishni oldini olish orqali.
Keling, bir nechta oqimlar uchun misolni amalga oshiramiz va qanday natija berishini ThreadLocalRandom klassida ko'rib chiqamiz:
import java.util.concurrent.ThreadLocalRandom;
class RandomNumbers extends Thread {
public void run() {
try {
int bound = 100;
int result = ThreadLocalRandom.current().nextInt(bound);
System.out.println("Thread " + Thread.currentThread().getId() + " generated " + result);
}
catch (Exception e) {
System.out.println("Exception");
}
}
public static void main(String[] args) {
long startTime = System.currentTimeMillis();
for (int i = 0; i < 10; i++) {
RandomNumbers randomNumbers = new RandomNumbers();
randomNumbers.start();
}
long endTime = System.currentTimeMillis();
System.out.println("Time taken: " + (endTime - startTime));
}
}
Dasturimiz natijasi:
Thread 17 generated 13
Thread 18 generated 41
Thread 16 generated 99
Thread 19 generated 25
Thread 23 generated 33
Thread 24 generated 21
Thread 15 generated 15
Thread 21 generated 28
Thread 22 generated 97
Thread 20 generated 33
Endi esa bizning RandomNumbers klassini o'zgartiramiz va unda Random ni ishlatamiz:
int result = new Random().nextInt(bound);
Thread 20 generated 48
Thread 19 generated 57
Thread 18 generated 90
Thread 22 generated 43
Thread 24 generated 7
Thread 23 generated 63
Thread 15 generated 2
Thread 16 generated 40
Thread 17 generated 29
Thread 21 generated 12
| Bu juda muhim! Hozirgi testlarda natijalar ba'zan mos keladi va ba'zan turli xil qiymatlar ko‘rsatadi. Ammo, agar biz ko'proq oqimlar haqida gapiradigan bo'lsak (masalan, 100), natija quyidagicha ko'rinadi:
Random - 19-25 mls
ThreadLocalRandom - 17-19 mls Shuni yetib olish kerakki, ilovadagi oqimlar soni qanchalik ko‘p bo‘lsa, ko‘pqirrali ishlatilganda Random klassining ishlashi shunchalik samaradorligini kamaytiradi. |
Keling, xulosa qilamiz va Random va ThreadLocalRandom klasslari orasidagi farqlarni yana bir bor qayta ko'rib chiqamiz:
| Random | ThreadLocalRandom |
|---|---|
| Agar turli oqimlar bir xil nusxani Random dan foydalansa, bu mojaro va samaradorlikka salbiy ta'sir qiladi. | Mojarolar yo'q va muammolar yuzaga kelmaydi, chunki generatsiya qilingan tasodifiy raqamlar joriy oqimga nisbatan mahalliy. |
| Boshlang'ich qiymatni o'zgartirish uchun chiziqli kongruent formulani ishlatadi. | Tasodifiy raqam generatori ichki yaratish yordamida boshlang'ich qiymatni boshlaydi. |
| Har bir oqim uchun o'z foydalanish to'plamiga ega bo'lgan Random nusxalarini ishlatadigan dasturlarda foydali. | Birinchi sinf, ikkinchi sinf tomonidan amalga oshirilgan. Oqim hovuzlarida bir nechta oqimlar bir vaqtning o'zida foydalanadigan ilovalarda foydali. |
| Bu ota-klass. | Bu farzand-klass. |
GO TO FULL VERSION