การแนะนำ
สตรีมเป็นสิ่งที่น่าสนใจ ในการตรวจสอบครั้งก่อน เราได้ดูเครื่องมือบางส่วนที่พร้อมใช้งานสำหรับการใช้งานแบบมัลติเธรด มาดูกันว่ามีอะไรน่าสนใจอีกบ้างที่เราสามารถทำได้ ณ จุดนี้เรารู้มาก ตัวอย่างเช่น จาก “
You Can't Spoil Java with a Thread: Part I - Threads ” เรารู้ว่าเธรดก็คือเธรด เรารู้ว่าเธรดกำลังทำงานบางอย่างอยู่ หากเราต้องการให้งานของเรารันได้ (
run
) เราต้องระบุเธรดให้เป็นค่าที่
Runnable
แน่นอน
เพื่อจำไว้ว่าเราสามารถใช้
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();
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();
}
ตัวอย่างแสดงให้เห็นว่า เมื่อใช้
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 และมีการทดสอบใน "∫" หนังสือเล่มนี้มีทั้งคำถามและคำตอบพร้อมคำอธิบาย ตัวอย่างเช่น:
หลายคนอาจเริ่มบอกว่านี่เป็นเพียงการท่องจำวิธีการอื่น ในด้านหนึ่งใช่ ในทางกลับกัน คำถามนี้สามารถตอบได้โดยจำไว้ว่านี่
ExecutorService
คือ "การอัพเกรด" ประเภทหนึ่ง
Executor
และ
Executor
มีวัตถุประสงค์เพียงเพื่อซ่อนวิธีการสร้างเธรด แต่ไม่ใช่วิธีหลักในการดำเนินการนั่นคือ ทำงานในเธรดใหม่
Runnable
. ดังนั้น
execute(Callable)
ไม่ใช่ เพราะว่า พวกเขาเพียงแค่เพิ่มวิธีการ
ExecutorService
ที่สามารถส่งคืนได้ อย่างที่คุณเห็น เราสามารถจดจำรายการวิธีการต่างๆ ได้ แต่จะง่ายกว่ามากที่จะเดาว่าเรารู้ลักษณะของคลาสนั้นหรือไม่ มีเนื้อหาเพิ่มเติมบางส่วนในหัวข้อ:
Executor
submit
Future
#เวียเชสลาฟ
GO TO FULL VERSION