เหตุใดคุณจึงต้องการส่วนต่อประสานผู้ดำเนินการ

ก่อน Java 5 คุณต้องเขียนการจัดการเธรดโค้ดของคุณเองในแอปพลิเคชันของคุณ นอกจากนี้ยังสร้างหัวข้อใหม่ออบเจกต์เป็นการดำเนินการที่ใช้ทรัพยากรมาก และไม่สมเหตุสมผลที่จะสร้างเธรดใหม่สำหรับทุกงานที่มีน้ำหนักเบา และเนื่องจากปัญหานี้เป็นที่คุ้นเคยกันดีสำหรับนักพัฒนาแอปพลิเคชันแบบมัลติเธรดทุกคน พวกเขาจึงตัดสินใจนำฟังก์ชันนี้มาไว้ใน Java เป็นเฟรมเวิร์กExecutor

ความคิดที่ยิ่งใหญ่คืออะไร? ง่ายมาก แทนที่จะสร้างเธรดใหม่สำหรับงานใหม่แต่ละงาน เธรดจะถูกเก็บไว้ในประเภท "ที่เก็บข้อมูล" และเมื่อมีงานใหม่เข้ามา เราจะดึงเธรดที่มีอยู่แทนที่จะสร้างเธรดใหม่

อินเทอร์เฟซหลักของเฟรมเวิร์กนี้คือExecutor , ExecutorServiceและScheduledExecutorServiceซึ่งแต่ละอินเทอร์เฟซจะขยายการทำงานของอินเทอร์เฟซก่อนหน้า

อินเทอร์เฟซ Executor เป็นอินเทอร์เฟซพื้นฐาน มันประกาศเป็นโมฆะ วิธี การดำเนินการ (คำสั่งที่รันได้) เดียว ที่นำไปใช้โดยวัตถุที่รันได้

อินเทอร์เฟ ซ ExecutorServiceน่าสนใจยิ่งขึ้น มีวิธีการจัดการการทำงานให้เสร็จสิ้นตลอดจนวิธีการส่งคืนผลลัพธ์บางประเภท มาดูวิธีการของมันให้ละเอียดยิ่งขึ้น:

วิธี คำอธิบาย
เป็นโมฆะปิด (); การเรียกวิธีนี้จะหยุดการทำงานของExecutorService งานทั้งหมดที่ส่งไปแล้วสำหรับการประมวลผลจะเสร็จสมบูรณ์ แต่งานใหม่จะไม่ได้รับการยอมรับ
รายการ <Runnable> ปิดเดี๋ยวนี้ ();

การเรียกวิธีนี้จะหยุดการทำงานของExecutorService Thread.interruptจะถูกเรียกสำหรับงานทั้งหมดที่ถูกส่งไปแล้วสำหรับการประมวลผล เมธอดนี้ส่งคืนรายการงานที่อยู่ในคิว

เมธอดไม่รอให้งานทั้งหมดที่ "กำลังดำเนินการ" เสร็จสิ้นในเวลาที่เรียกใช้เมธอด

คำเตือน: การเรียกวิธีนี้อาจทำให้ทรัพยากรรั่วไหล

บูลีน isShutdown(); ตรวจสอบว่าExecutorServiceหยุดทำงาน หรือไม่
บูลีน isTerminated(); ส่งคืนค่าจริงหาก งานทั้งหมดเสร็จสิ้นหลังจากปิดระบบExecutorService จนกว่าจะมีการเรียกshutdown()หรือshutdownNow() ก็จะคืนค่า เป็นเท็จ เสมอ
บูลีน waitTermination (การหมดเวลานาน, หน่วย TimeUnit) โยน InterruptedException;

หลังจาก เรียกเมธอด shutdown()เมธอดนี้จะบล็อกเธรดที่ถูกเรียก จนกว่าจะมีเงื่อนไขข้อใดข้อหนึ่งต่อไปนี้เป็นจริง:

  • งานตามกำหนดการทั้งหมดเสร็จสมบูรณ์
  • หมดเวลาที่ส่งผ่านไปยังเมธอดแล้ว
  • เธรดปัจจุบันถูกขัดจังหวะ

ส่งคืนค่าจริงหากงานทั้งหมดเสร็จสมบูรณ์ และค่าเท็จหากการหมดเวลาผ่านไปก่อนสิ้นสุด

<T> อนาคต<T> ส่ง (งานที่เรียกได้<T>);

เพิ่ม งาน Callableให้กับExecutorServiceและส่งคืนวัตถุที่ใช้อินเทอร์เฟซในอนาคต

<T>คือประเภทของผลลัพธ์ของงานที่ผ่านไป

<T> อนาคต<T> ส่ง (งานที่รันได้, ผลลัพธ์ T);

เพิ่ม งาน ที่รันได้ให้กับExecutorServiceและส่งคืนวัตถุที่ใช้อินเทอร์เฟซในอนาคต

พารามิเตอร์ผลลัพธ์ Tคือสิ่งที่ส่งคืนโดยการเรียกใช้เมธอดget()ในผลลัพธ์วัตถุในอนาคต

อนาคต<?> ส่ง (งานที่รันได้);

เพิ่ม งาน ที่รันได้ให้กับExecutorServiceและส่งคืนวัตถุที่ใช้อินเทอร์เฟซในอนาคต

หากเราเรียก เมธอด get()บนผลลัพธ์ ของออบเจกต์ ในอนาคตเราจะได้ค่าว่าง

<T> รายการ <Future<T>> เรียกใช้ทั้งหมด (Collection <? ขยายงาน Callable<T>>) พ่น InterruptedException;

ส่งรายการงานที่เรียกได้ไปยังExecutorService ส่งคืนรายการฟิวเจอร์สซึ่งเราสามารถรับผลงานได้ รายการนี้จะถูกส่งกลับเมื่องานที่ส่งทั้งหมดเสร็จสิ้น

หาก การรวบรวม งานถูกแก้ไขในขณะที่เมธอดกำลังรันอยู่ ผลลัพธ์ของเมธอดนี้จะไม่ถูกกำหนด

<T> รายการ <Future<T>> เรียกใช้ทั้งหมด (Collection <? ขยายงาน Callable<T>>, หมดเวลานาน, หน่วย TimeUnit) โยน InterruptedException;

ส่งรายการงานที่เรียกได้ไปยังExecutorService ส่งคืนรายการฟิวเจอร์สซึ่งเราสามารถรับผลงานได้ รายการนี้จะถูกส่งกลับเมื่องานที่ผ่านไปทั้งหมดเสร็จสิ้น หรือหลังจากหมดเวลาที่ส่งผ่านไปยังเมธอด แล้วแต่ว่าเหตุการณ์ใดจะเกิดขึ้นก่อน

หากหมดเวลา งานที่ยังไม่เสร็จจะถูกยกเลิก

หมายเหตุ: เป็นไปได้ว่างานที่ยกเลิกจะไม่หยุดทำงาน (เราจะเห็นผลข้างเคียงนี้ในตัวอย่าง)

หาก การรวบรวม งานถูกแก้ไขในขณะที่เมธอดกำลังรันอยู่ ผลลัพธ์ของเมธอดนี้จะไม่ถูกกำหนด

<T> T เรียกใช้ใดๆ (Collection <? ขยายงาน Callable<T>>) โยน InterruptedException, ExecutionException;

ส่งรายการงานที่เรียกได้ไปยังExecutorService ส่งกลับผลลัพธ์ของงานอย่างใดอย่างหนึ่ง (ถ้ามี) ที่เสร็จสิ้นโดยไม่มีข้อยกเว้น (ถ้ามี)

หาก การรวบรวม งานถูกแก้ไขในขณะที่เมธอดกำลังรันอยู่ ผลลัพธ์ของเมธอดนี้จะไม่ถูกกำหนด

<T> T เรียกใช้ใดๆ (Collection <? ขยายงาน Callable<T>>, หมดเวลานาน, หน่วย TimeUnit) โยน InterruptedException, ExecutionException, TimeoutException;

ส่งรายการงานที่เรียกได้ไปยังExecutorService ส่งกลับผลลัพธ์ของงานใดงานหนึ่ง (ถ้ามี) ที่เสร็จสิ้นโดยไม่ส่งข้อยกเว้นก่อนที่การหมดเวลาจะส่งผ่านไปยังเมธอดที่ผ่านไป

หาก การรวบรวม งานถูกแก้ไขในขณะที่เมธอดกำลังรันอยู่ ผลลัพธ์ของเมธอดนี้จะไม่ถูกกำหนด

มาดูตัวอย่างการทำงานกับExecutorServiceกัน


import java.util.List;
import java.util.concurrent.*;

public class ExecutorServiceTest {
   public static void main(String[] args) throws InterruptedException, ExecutionException, TimeoutException {
//Create an ExecutorService for 2 threads
       java.util.concurrent.ExecutorService executorService = new ThreadPoolExecutor(2, 2, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(10));
// Create 5 tasks
       MyRunnable task1 = new MyRunnable();
       MyRunnable task2 = new MyRunnable();
       MyRunnable task3 = new MyRunnable();
       MyRunnable task4 = new MyRunnable();
       MyRunnable task5 = new MyRunnable();

       final List<MyRunnable> tasks = List.of(task1, task2, task3, task4, task5);
// Pass a list that contains the 5 tasks we created
       final List<Future<Void>> futures = executorService.invokeAll(tasks, 6, TimeUnit.SECONDS);
       System.out.println("Futures received");

// Stop the ExecutorService
       executorService.shutdown();

       try {
           TimeUnit.SECONDS.sleep(3);
       } catch (InterruptedException e) {
           e.printStackTrace();
       }

       System.out.println(executorService.isShutdown());
       System.out.println(executorService.isTerminated());
   }

   public static class MyRunnable implements Callable<Void> {

       @Override
       public void call() {
// Add 2 delays. When the ExecutorService is stopped, we will see which delay is in progress when the attempt is made to stop execution of the task
           try {
               TimeUnit.SECONDS.sleep(3);
           } catch (InterruptedException e) {
               System.out.println("sleep 1: " + e.getMessage());
           }
           try {
               TimeUnit.SECONDS.sleep(2);
           } catch (InterruptedException e) {
               System.out.println("sleep 2: " + e.getMessage());
           }
           System.out.println("done");
           return null;
       }
   }
}

เอาท์พุต:

เสร็จ
สิ้น เสร็จ
สิ้น ฟิวเจอร์สได้รับ
สลีป 1: สลีปถูกขัดจังหวะ
สลีป 1: สลีปถูกขัดจังหวะ
เสร็จแล้ว
เสร็จแล้ว จริง
จริง

แต่ละงานทำงานเป็นเวลา 5 วินาที เราสร้างพูลสำหรับสองเธรด ดังนั้นเอาต์พุตสองบรรทัดแรกจึงเหมาะสมอย่างยิ่ง

หกวินาทีหลังจากเริ่มโปรแกรม เมธอด invokeAllจะหมดเวลาและผลลัพธ์จะถูกส่งกลับเป็นรายการFutures สามารถดูได้จากสตริงเอาต์พุตFutures ที่ได้รับ

หลังจากงานสองงานแรกเสร็จสิ้น งานอีกสองงานก็เริ่มต้นขึ้น แต่เนื่องจากการหมดเวลาที่ตั้งค่าไว้ใน เมธอด invokeAllผ่านไป งานทั้งสองนี้จึงไม่มีเวลาดำเนินการให้เสร็จ พวกเขาได้รับคำสั่ง"ยกเลิก" นั่นเป็นสาเหตุที่เอาต์พุตมีสองบรรทัดที่มีโหมดสลีป 1: โหมดสลีปถูกขัดจังหวะ

จากนั้นคุณจะเห็นอีกสองบรรทัดด้วยdone นี่คือผลข้างเคียงที่ฉันกล่าวถึงเมื่ออธิบายเมธอดinvokeAll

งานที่ห้าและสุดท้ายไม่เคยเริ่มต้นด้วยซ้ำ ดังนั้นเราจึงไม่เห็นอะไรเกี่ยวกับมันในผลลัพธ์

สองบรรทัดสุดท้ายเป็นผลมาจากการเรียกใช้เมธอดisShutdownและisTerminated

นอกจากนี้ยังน่าสนใจที่จะเรียกใช้ตัวอย่างนี้ในโหมดดีบักและดูสถานะงานหลังจากหมดเวลา (ตั้งค่าเบรกพอยต์ในบรรทัดด้วยexecutorService.shutdown(); ):

เราเห็นว่าสองงาน เสร็จ สมบูรณ์ตามปกติและสามงานถูก"ยกเลิก"

บริการผู้ดำเนินการตามกำหนดเวลา

เพื่อสรุปการสนทนาของเราเกี่ยวกับตัว ดำเนินการ ลองมาดูที่ScheduledExecutorService

มันมี 4 วิธี:

วิธี คำอธิบาย
สาธารณะ ScheduledFuture<?> กำหนดการ (คำสั่งที่รันได้, หน่วงเวลานาน, หน่วย TimeUnit); กำหนดเวลา งาน Runnable ที่ผ่านไป ให้ทำงานหนึ่งครั้งหลังจากการหน่วงเวลาที่ระบุเป็นอาร์กิวเมนต์
กำหนดการสาธารณะ <V> ScheduledFuture<V> (โทรได้ <V> โทรได้, หน่วงเวลานาน, หน่วย TimeUnit); กำหนดเวลา งาน Callable ที่ผ่านไป ให้ทำงานหนึ่งครั้งหลังจากการหน่วงเวลาที่ระบุเป็นอาร์กิวเมนต์
สาธารณะ ScheduledFuture<?> scheduleAtFixedRate (คำสั่งที่รันได้, initialdelay ยาว, ระยะเวลานาน, หน่วย TimeUnit); กำหนดการดำเนินการเป็นระยะของงานที่ผ่านไป ซึ่งจะดำเนินการเป็นครั้งแรกหลังจากinitialDelayและการรันแต่ละครั้งที่ตามมาจะเริ่มหลังจากช่วงเวลา
สาธารณะ ScheduledFuture<?> scheduleWithFixedDelay (คำสั่งที่รันได้, initialdelay ที่ยาวนาน, ความล่าช้าที่ยาวนาน, หน่วย TimeUnit); กำหนดการดำเนินการเป็นระยะของงานที่ผ่านไป ซึ่งจะดำเนินการเป็นครั้งแรกหลังจากinitialDelayและการรันแต่ละครั้งที่ตามมาจะเริ่มหลังจากการหน่วงเวลา (ระยะเวลาระหว่างการรันก่อนหน้าเสร็จสิ้นและการเริ่มต้นของรันปัจจุบัน)