JavaRush /จาวาบล็อก /Random-TH /คุณไม่สามารถสปอย Java ด้วย Thread: ตอนที่ VI - สู่อุปสรรค...
Viacheslav
ระดับ

คุณไม่สามารถสปอย Java ด้วย Thread: ตอนที่ VI - สู่อุปสรรค!

เผยแพร่ในกลุ่ม

การแนะนำ

สตรีมเป็นสิ่งที่น่าสนใจ ในการตรวจสอบครั้งก่อน เราได้ดูเครื่องมือบางส่วนที่พร้อมใช้งานสำหรับการใช้งานแบบมัลติเธรด มาดูกันว่ามีอะไรน่าสนใจอีกบ้างที่เราสามารถทำได้ ณ จุดนี้เรารู้มาก ตัวอย่างเช่น จาก “ You Can't Spoil Java with a Thread: Part I - Threads ” เรารู้ว่าเธรดก็คือเธรด เรารู้ว่าเธรดกำลังทำงานบางอย่างอยู่ หากเราต้องการให้งานของเรารันได้ ( run) เราต้องระบุเธรดให้เป็นค่าที่Runnableแน่นอน คุณไม่สามารถสปอย Java ด้วย Thread: ตอนที่ VI - สู่อุปสรรค!  - 1เพื่อจำไว้ว่าเราสามารถใช้Tutorialspoint Java Online Compiler :
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();
}
เรายังรู้ด้วยว่าเรามีแนวคิดเช่นล็อค เราอ่านเกี่ยวกับเรื่องนี้ใน “ คุณไม่สามารถสปอย Java ด้วยเธรด: ตอนที่ II - การซิงโครไนซ์ ” เธรดสามารถครอบครองล็อคได้ และเธรดอื่นที่พยายามครอบครองล็อคจะถูกบังคับให้รอให้ล็อคเป็นอิสระ:
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();
	}
}
ฉันคิดว่าถึงเวลาที่จะพูดถึงสิ่งอื่นที่เราสามารถทำได้ซึ่งน่าสนใจ

เซมาฟอร์ส

วิธีที่ง่ายที่สุดในการควบคุมจำนวนเธรดที่สามารถทำงานได้พร้อมกันคือเซมาฟอร์ เหมือนอยู่บนทางรถไฟ ไฟเขียวติด - คุณทำได้ ไฟสีแดงติด - เรากำลังรออยู่ เราคาดหวังอะไรจากสัญญาณ? สิทธิ์ การอนุญาตเป็นภาษาอังกฤษ - ใบอนุญาต หากต้องการขออนุญาตคุณต้องได้รับซึ่งจะได้รับเป็นภาษาอังกฤษ และเมื่อไม่ต้องการการอนุญาตอีกต่อไปแล้วเราก็ต้องให้มันออกไป กล่าวคือ ปล่อยหรือกำจัด ซึ่งจะเป็นภาษาอังกฤษจะออก มาดูกันว่ามันทำงานอย่างไร เราจะต้องนำเข้าชั้นjava.util.concurrent.Semaphoreเรียน ตัวอย่าง:
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);
}
ดังที่เราเห็นเมื่อจำคำศัพท์ภาษาอังกฤษได้ เราก็จะเข้าใจว่าเซมาฟอร์ทำงานอย่างไร สิ่งที่น่าสนใจคือเงื่อนไขหลักคือ "บัญชี" ของเซมาฟอร์จะต้องมีใบอนุญาตจำนวนบวก ดังนั้น คุณสามารถเริ่มต้นด้วยเครื่องหมายลบได้ และสามารถขอ (รับ) มากกว่า 1 รายการได้

นับถอยหลังสลัก

กลไกต่อไปคือCountDownLatch. CountDown ในภาษาอังกฤษคือการนับถอยหลัง และ Latch คือสลักเกลียวหรือสลัก นั่นคือถ้าเราแปลนี่คือสลักที่มีการนับถอยหลัง ที่นี่เราต้องการการนำเข้าคลาสที่java.util.concurrent.CountDownLatchเหมาะสม มันเหมือนกับการแข่งขันหรือการแข่งที่ทุกคนมารวมตัวกันที่เส้นสตาร์ทและเมื่อทุกคนพร้อมก็ได้รับอนุญาตและทุกคนก็ออกสตาร์ทพร้อมๆ กัน ตัวอย่าง:
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();
 	}
}
รอเป็นภาษาอังกฤษ - คาดหวัง นั่นคือเราพูดcountDownก่อน ดังที่ Google Translator กล่าวไว้ การนับถอยหลังคือ "การกระทำของการนับตัวเลขในลำดับย้อนกลับไปยังศูนย์" นั่นคือดำเนินการนับถอยหลัง โดยมีจุดประสงค์เพื่อนับถึงศูนย์ แล้วเราก็พูดว่าawait- นั่นคือรอจนกว่าค่าตัวนับจะกลายเป็นศูนย์ ที่น่าสนใจคือเคาน์เตอร์ดังกล่าวเป็นแบบใช้แล้วทิ้ง ดังที่กล่าวไว้ใน JavaDoc - "เมื่อเธรดต้องนับถอยหลังซ้ำๆ ด้วยวิธีนี้ ให้ใช้ CyclicBarrier แทน" นั่นคือ หากคุณต้องการนับซ้ำ คุณจะต้องใช้ตัวเลือกอื่นซึ่งเรียกว่าCyclicBarrier.

CyclicBarrier

ดังที่ชื่อบ่งบอกCyclicBarrierมันเป็นสิ่งกีดขวางแบบวัฏจักร เราจะต้องนำเข้าชั้นjava.util.concurrent.CyclicBarrierเรียน ลองดูตัวอย่าง:
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();
	}
}
อย่างที่คุณเห็น เธรดกำลังดำเนินการawaitนั่นคือ กำลังรอ ในกรณีนี้ ค่าของสิ่งกีดขวางจะลดลง สิ่งกีดขวางจะถือว่าพัง ( berrier.isBroken()) เมื่อการนับถอยหลังถึงศูนย์ หากต้องการรีเซ็ตแผงกั้น คุณต้องโทรberrier.reset()ซึ่งหายไปCountDownLatchใน

ผู้แลกเปลี่ยน

วิธีแก้ไขต่อไปคือExchanger. การแลกเปลี่ยนจากภาษาอังกฤษแปลว่าการแลกเปลี่ยนหรือการแลกเปลี่ยน A Exchangerคือผู้แลกเปลี่ยน นั่นคือบางสิ่งที่พวกเขาแลกเปลี่ยนกัน ลองดูตัวอย่างง่ายๆ:
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();
}
ที่นี่เราเปิดตัวสองเธรด แต่ละคนดำเนินการวิธีแลกเปลี่ยนและรอให้เธรดอื่นดำเนินการวิธีแลกเปลี่ยนด้วย ดังนั้นเธรดจะแลกเปลี่ยนข้อโต้แย้งที่ส่งผ่านกันเอง สิ่งที่น่าสนใจ เธอไม่เตือนคุณถึงอะไรเลยเหรอ? และเขาก็นึกถึงSynchronousQueueซึ่งอยู่ในหัวใจของcachedThreadPool'a เพื่อความชัดเจน นี่คือตัวอย่าง:
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");
}
ตัวอย่างแสดงให้เห็นว่าเมื่อเปิดเธรดใหม่ เธรดนี้จะเข้าสู่โหมดรอ เนื่องจาก คิวจะว่างเปล่า จากนั้นmainเธรดจะจัดคิวข้อความ "ข้อความ" ในเวลาเดียวกันจะหยุดตามเวลาที่กำหนดจนกว่าจะได้รับองค์ประกอบข้อความนี้จากคิว ในหัวข้อนี้ คุณยังสามารถอ่าน " SynchronousQueue Vs Exchanger "

เฟสเซอร์

และสุดท้ายที่หอมหวานที่สุด - Phaser. เราจะต้องนำเข้าชั้นjava.util.concurrent.Phaserเรียน ลองดูตัวอย่างง่ายๆ:
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();
    }
ตัวอย่างแสดงให้เห็นว่า เมื่อใช้Phaser'a อุปสรรคจะพังเมื่อจำนวนการลงทะเบียนตรงกับจำนวนมาถึงที่อุปสรรค คุณสามารถหาข้อมูลเพิ่มเติมได้Phaserในบทความจากฮับ " New Phaser synchronizer "

ผลลัพธ์

ดังที่คุณเห็นจากตัวอย่าง มีวิธีต่างๆ ในการซิงโครไนซ์เธรด ก่อนหน้านี้ฉันพยายามจำบางอย่างเกี่ยวกับมัลติเธรด ฉันหวังว่าส่วนก่อนหน้านี้จะมีประโยชน์ พวกเขากล่าวว่าเส้นทางสู่มัลติเธรดเริ่มต้นด้วยหนังสือ "Java Concurrency in Practice" แม้ว่าจะออกในปี 2549 แต่ผู้คนก็ตอบว่าหนังสือเล่มนี้ค่อนข้างเป็นพื้นฐานและยังคงได้รับความนิยมอยู่ ตัวอย่างเช่น คุณสามารถอ่านการสนทนาได้ที่นี่: " Java Concurrency In Practice ยังคงใช้ได้อยู่หรือไม่ " การอ่านลิงก์จากการสนทนาก็เป็นประโยชน์เช่นกัน ตัวอย่างเช่นมีลิงก์ไปยังหนังสือ " The Well-Grounded Java Developer " ซึ่งควรค่าแก่การใส่ใจกับ " บทที่ 4. การทำงานพร้อมกันสมัยใหม่ " มีบทวิจารณ์ทั้งหมดในหัวข้อเดียวกัน: “ Java cocurrency ในทางปฏิบัติยังคงมีความเกี่ยวข้องในยุคของ java 8 หรือไม่ ” นอกจากนี้ยังมีเคล็ดลับเกี่ยวกับสิ่งอื่นๆ ที่คุณควรอ่านเพื่อทำความเข้าใจหัวข้อนี้จริงๆ หลังจากนั้น คุณสามารถดูหนังสือที่ยอดเยี่ยมเช่น " OCA OCP JavaSE 8 Programmer Practice Tests " ได้อย่างใกล้ชิดยิ่งขึ้น เราสนใจส่วนที่สองนั่นคือ OCP และมีการทดสอบใน "∫" หนังสือเล่มนี้มีทั้งคำถามและคำตอบพร้อมคำอธิบาย ตัวอย่างเช่น: คุณไม่สามารถสปอย Java ด้วย Thread: ตอนที่ VI - สู่อุปสรรค!  - 3หลายคนอาจเริ่มบอกว่านี่เป็นเพียงการท่องจำวิธีการอื่น ในด้านหนึ่งใช่ ในทางกลับกัน คำถามนี้สามารถตอบได้โดยจำไว้ว่านี่ExecutorServiceคือ "การอัพเกรด" ประเภทหนึ่ง ExecutorและExecutorมีวัตถุประสงค์เพียงเพื่อซ่อนวิธีการสร้างเธรด แต่ไม่ใช่วิธีหลักในการดำเนินการนั่นคือ ทำงานในเธรดใหม่Runnable. ดังนั้นexecute(Callable)ไม่ใช่ เพราะว่า พวกเขาเพียงแค่เพิ่มวิธีการExecutorServiceที่สามารถส่งคืนได้ อย่างที่คุณเห็น เราสามารถจดจำรายการวิธีการต่างๆ ได้ แต่จะง่ายกว่ามากที่จะเดาว่าเรารู้ลักษณะของคลาสนั้นหรือไม่ มีเนื้อหาเพิ่มเติมบางส่วนในหัวข้อ: ExecutorsubmitFuture #เวียเชสลาฟ
ความคิดเห็น
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION