JavaRush /Java blogi /Random-UZ /Siz Java-ni mavzu bilan buzolmaysiz: VI qism - To'siq uch...
Viacheslav
Daraja

Siz Java-ni mavzu bilan buzolmaysiz: VI qism - To'siq uchun!

Guruhda nashr etilgan

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 danSiz Java-ni mavzu bilan buzolmaysiz: VI qism - To'siq uchun!  - 1 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, CyclicBarrierbu 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 mainip "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();
        // Вызывая метод register, мы регистрируем текущий поток (main) How участника
        phaser.register();
        System.out.println("Phasecount is " + phaser.getPhase());
        testPhaser(phaser);
        testPhaser(phaser);
        testPhaser(phaser);
        // Через 3 секунды прибываем к барьеру и снимаемся регистрацию. Кол-во прибывших = кол-во регистраций = пуск
        Thread.sleep(3000);
        phaser.arriveAndDeregister();
        System.out.println("Phasecount is " + phaser.getPhase());
    }

    private static void testPhaser(final Phaser phaser) {
        // Говорим, что будет +1 участник на Phaser
        phaser.register();
        // Запускаем новый поток
        new Thread(() -> {
            String name = Thread.currentThread().getName();
            System.out.println(name + " arrived");
            phaser.arriveAndAwaitAdvance(); //threads register arrival to the phaser.
            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: Siz Java-ni mavzu bilan buzolmaysiz: VI qism - To'siq uchun!  - 3Ko'pchilik bu usullarning navbatdagi yodlanishi deb aytishi mumkin. Bir tomondan, ha. Boshqa tomondan, bu savolga ExecutorServicebu o'ziga xos "yangilanish" ekanligini eslab javob berish mumkin Executor. Va Executorbu 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 ExecutorServiceusullarni 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: ExecutorsubmitFuture #Viacheslav
Izohlar
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION