Kirish
Oqimlar qiziq narsa. Oldingi sharhlarda biz multithreadingni amalga oshirish uchun mavjud bo'lgan ba'zi vositalarni ko'rib chiqdik. Keling, yana qanday qiziqarli narsalarni qilishimiz mumkinligini ko'rib chiqaylik. Bu vaqtda biz ko'p narsani bilamiz. Masalan, “
Javani mavzu bilan buzib bo'lmaydi: I qism - mavzular ” dan biz ipning mavzu ekanligini bilamiz. Biz bilamizki, ip qandaydir vazifani bajarmoqda. Agar biz vazifamizni ishga tushirishni istasak (
run
), u holda ipni ma'lum bo'lishini ko'rsatishimiz kerak
Runnable
.
Esda tutish uchun biz Tutorialspoint Java Online Compiler dan foydalanishimiz mumkin :
public static void main(String []args){
Runnable task = () -> {
Thread thread = Thread.currentThread();
System.out.println("Hello from " + thread.getName());
};
Thread thread = new Thread(task);
thread.start();
}
Bizda qulf kabi tushuncha borligini ham bilamiz. Biz bu haqda "
Siz Java-ni mavzu bilan buzolmaysiz: II qism - Sinxronizatsiya " da o'qiymiz. Ip qulfni egallashi mumkin, keyin esa qulfni egallashga urinayotgan boshqa ip qulf bo'shashini kutishga majbur bo'ladi:
import java.util.concurrent.locks.*;
public class HelloWorld{
public static void main(String []args){
Lock lock = new ReentrantLock();
Runnable task = () -> {
lock.lock();
Thread thread = Thread.currentThread();
System.out.println("Hello from " + thread.getName());
lock.unlock();
};
Thread thread = new Thread(task);
thread.start();
}
}
Menimcha, yana nima qilishimiz mumkinligi haqida gapirish vaqti keldi.
Semaforlar
Bir vaqtning o'zida qancha ip ishlashi mumkinligini nazorat qilishning eng oddiy vositasi semafordir. Temir yo'lda bo'lgani kabi. Yashil chiroq yondi - mumkin. Qizil chiroq yondi - biz kutamiz. Semafordan nimani kutamiz? Ruxsatlar. Ingliz tilida ruxsatnoma - ruxsat. Ruxsat olish uchun siz uni olishingiz kerak, bu ingliz tilida olinadi. Va ruxsat kerak bo'lmaganda, biz uni berishimiz kerak, ya'ni uni qo'yib yuborishimiz yoki undan xalos bo'lishimiz kerak, bu ingliz tilida chiqariladi. Keling, bu qanday ishlashini ko'rib chiqaylik. Biz sinfni import qilishimiz kerak
java.util.concurrent.Semaphore
. Misol:
public static void main(String[] args) throws InterruptedException {
Semaphore semaphore = new Semaphore(0);
Runnable task = () -> {
try {
semaphore.acquire();
System.out.println("Finished");
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
};
new Thread(task).start();
Thread.sleep(5000);
semaphore.release(1);
}
Ko'rib turganimizdek, inglizcha so'zlarni yodlab, biz semafor qanday ishlashini tushunamiz. Qizig'i shundaki, asosiy shart - semafor "hisob"ida ijobiy ruxsatnomalar soni bo'lishi kerak. Shuning uchun siz uni minus bilan boshlashingiz mumkin. Va siz 1 dan ortiq so'rashingiz (sotib olishingiz) mumkin.
CountdownLatch
Keyingi mexanizm
CountDownLatch
. Ingliz tilidagi Countdown bu orqaga hisoblash, Latch esa murvat yoki mandaldir. Ya'ni, agar biz uni tarjima qilsak, bu orqaga hisoblash bilan mandal. Bu erda biz sinfning tegishli importiga muhtojmiz
java.util.concurrent.CountDownLatch
. Bu xuddi poyga yoki poygaga o'xshaydi, unda hamma boshlang'ich chiziqqa yig'iladi va hamma tayyor bo'lgach, ruxsat beriladi va hamma bir vaqtning o'zida boshlanadi. Misol:
public static void main(String[] args) {
CountDownLatch countDownLatch = new CountDownLatch(3);
Runnable task = () -> {
try {
countDownLatch.countDown();
System.out.println("Countdown: " + countDownLatch.getCount());
countDownLatch.await();
System.out.println("Finished");
} catch (InterruptedException e) {
e.printStackTrace();
}
};
for (int i = 0; i < 3; i++) {
new Thread(task).start();
}
}
ingliz tilida kutish - kutish. Ya'ni, biz birinchi navbatda gapiramiz
countDown
. Google Translator aytganidek, ortga hisoblash bu "raqamlarni nolga teskari tartibda sanash harakati", ya'ni teskari sanash amalini bajarish, uning maqsadi nolga qadar sanash. Va keyin biz aytamiz
await
- ya'ni hisoblagich qiymati nolga aylanguncha kuting. Qizig'i shundaki, bunday hisoblagich bir martalikdir. JavaDoc-da aytilganidek - "Agar mavzular shu tarzda qayta-qayta sanash kerak bo'lsa, o'rniga CyclicBarrier-dan foydalaning", ya'ni qayta foydalanish mumkin bo'lgan hisoblash kerak bo'lsa, siz boshqa variantdan foydalanishingiz kerak, bu
CyclicBarrier
.
CyclicBarrier
Nomidan ko'rinib turibdiki,
CyclicBarrier
bu tsiklik to'siqdir. Biz sinfni import qilishimiz kerak bo'ladi
java.util.concurrent.CyclicBarrier
. Keling, bir misolni ko'rib chiqaylik:
public static void main(String[] args) throws InterruptedException {
Runnable action = () -> System.out.println("На старт!");
CyclicBarrier berrier = new CyclicBarrier(3, action);
Runnable task = () -> {
try {
berrier.await();
System.out.println("Finished");
} catch (BrokenBarrierException | InterruptedException e) {
e.printStackTrace();
}
};
System.out.println("Limit: " + berrier.getParties());
for (int i = 0; i < 3; i++) {
new Thread(task).start();
}
}
Ko'rib turganingizdek, ip bajarilmoqda
await
, ya'ni kutmoqda. Bunday holda, to'siqning qiymati kamayadi.
berrier.isBroken()
Ortga hisoblash nolga yetganda to'siq buzilgan ( ) hisoblanadi . To'siqni qayta o'rnatish uchun siz qo'ng'iroq qilishingiz kerak
berrier.reset()
, ichida yo'q edi
CountDownLatch
.
almashtirgich
Keyingi chora - bu
Exchanger
. Ingliz tilidan almashish almashinuv yoki almashish deb tarjima qilinadi. A
Exchanger
- almashtirgich, ya'ni ular orqali almashinadigan narsa. Keling, oddiy misolni ko'rib chiqaylik:
public static void main(String[] args) {
Exchanger<String> exchanger = new Exchanger<>();
Runnable task = () -> {
try {
Thread thread = Thread.currentThread();
String withThreadName = exchanger.exchange(thread.getName());
System.out.println(thread.getName() + " обменялся с " + withThreadName);
} catch (InterruptedException e) {
e.printStackTrace();
}
};
new Thread(task).start();
new Thread(task).start();
}
Bu erda biz ikkita mavzuni ishga tushiramiz. Ularning har biri almashuv usulini bajaradi va almashuv usulini boshqa ish zarrachasi ham bajarishini kutadi. Shunday qilib, iplar o'tgan argumentlarni o'zaro almashadilar. Qiziqarli narsa. U sizga hech narsani eslatmaydimi? Va u eslatadi
SynchronousQueue
, qaysi 'a qalbida yotadi
cachedThreadPool
. Aniqlik uchun bu erda bir misol:
public static void main(String[] args) throws InterruptedException {
SynchronousQueue<String> queue = new SynchronousQueue<>();
Runnable task = () -> {
try {
System.out.println(queue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
};
new Thread(task).start();
queue.put("Message");
}
Misol shuni ko'rsatadiki, yangi ipni ishga tushirish orqali bu mavzu kutish rejimiga o'tadi, chunki navbat bo'sh bo'ladi. Va keyin
main
ip "Xabar" matnini navbatga qo'yadi. Shu bilan birga, u navbatdan ushbu matn elementini olguncha kerakli vaqt davomida to'xtaydi. Ushbu mavzu bo'yicha siz "
SynchronousQueue Vs Exchanger " ni ham o'qishingiz mumkin.
Fazali
Va nihoyat, eng shirin narsa -
Phaser
. Biz sinfni import qilishimiz kerak
java.util.concurrent.Phaser
. Keling, oddiy misolni ko'rib chiqaylik:
public static void main(String[] args) throws InterruptedException {
Phaser phaser = new Phaser();
phaser.register();
System.out.println("Phasecount is " + phaser.getPhase());
testPhaser(phaser);
testPhaser(phaser);
testPhaser(phaser);
Thread.sleep(3000);
phaser.arriveAndDeregister();
System.out.println("Phasecount is " + phaser.getPhase());
}
private static void testPhaser(final Phaser phaser) {
phaser.register();
new Thread(() -> {
String name = Thread.currentThread().getName();
System.out.println(name + " arrived");
phaser.arriveAndAwaitAdvance();
System.out.println(name + " after passing barrier");
}).start();
}
Misol shuni ko'rsatadiki,
Phaser
"a" dan foydalanilganda, ro'yxatga olishlar soni to'siqqa kelganlar soniga to'g'ri kelganda to'siq buziladi.
Qo'shimcha ma'lumotni " New Phaser synchronizerPhaser
" uyasidagi maqolada topishingiz mumkin .
Natijalar
Misollardan ko'rinib turibdiki, iplarni sinxronlashtirishning turli usullari mavjud. Ilgari men multithreading haqida biror narsani eslashga harakat qildim, umid qilamanki, oldingi qismlar foydali bo'ldi. Ularning ta'kidlashicha, multithreading yo'li "Java Concurrency in Practice" kitobidan boshlanadi. Garchi u 2006 yilda chiqqan bo'lsa-da, odamlar bu kitob juda fundamental va hali ham o'z o'rniga ega, deb javob berishadi. Misol uchun, muhokamalarni bu yerda o'qishingiz mumkin: "
Java Concurrency In Practice hali ham amal qiladimi? ". Muhokamadagi havolalarni o'qish ham foydalidir.
Masalan, " Yaxshi asoslangan Java dasturchisi " kitobiga havola mavjud bo'lib , unda "
4-bob. Zamonaviy parallellik " ga e'tibor qaratish lozim . Xuddi shu mavzu bo'yicha yana bir to'liq sharh mavjud: "
Java 8 davrida Java kovalyutasi amalda ham dolzarbmi ?". Shuningdek, mavzuni tushunish uchun yana nimani o'qish kerakligi haqida maslahatlar mavjud.
Shundan so'ng siz " OCA OCP JavaSE 8 dasturchi amaliyoti testlari " kabi ajoyib kitobni diqqat bilan ko'rib chiqishingiz mumkin . Bizni ikkinchi qism, ya'ni OCP qiziqtiradi. Va "∫" da testlar mavjud. Ushbu kitobda ham savollar, ham tushuntirishlar bilan javoblar mavjud. Masalan:
Ko'pchilik bu usullarning navbatdagi yodlanishi deb aytishi mumkin. Bir tomondan, ha. Boshqa tomondan, bu savolga
ExecutorService
bu o'ziga xos "yangilanish" ekanligini eslab javob berish mumkin
Executor
. Va
Executor
bu shunchaki iplarni yaratish usulini yashirish uchun mo'ljallangan, lekin ularni bajarishning asosiy usuli emas, ya'ni yangi ipda ishlash
Runnable
. Shuning uchun,
execute(Callable)
yo'q, chunki ular shunchaki qaytib kelishi mumkin bo'lgan
ExecutorService
usullarni qo'shdilar . Ko'rib turganingizdek, biz usullar ro'yxatini yodlashimiz mumkin, ammo sinflarning o'z tabiatini bilsak, taxmin qilish ancha oson. Xo'sh, mavzu bo'yicha ba'zi qo'shimcha materiallar:
Executor
submit
Future
#Viacheslav
GO TO FULL VERSION