Một loại nhóm chủ đề khác là "được lưu trong bộ nhớ cache". Các nhóm luồng như vậy thường được sử dụng như các nhóm cố định.

Như được chỉ định bởi tên, loại nhóm luồng này lưu trữ các luồng. Nó giữ cho các luồng không sử dụng tồn tại trong một thời gian giới hạn để sử dụng lại các luồng đó để thực hiện các tác vụ mới. Một nhóm chủ đề như vậy là tốt nhất khi chúng ta có một số lượng công việc nhẹ hợp lý.

Ý nghĩa của "một số lượng hợp lý" khá rộng, nhưng bạn nên biết rằng một nhóm như vậy không phù hợp với mọi nhiệm vụ. Ví dụ: giả sử chúng tôi muốn tạo một triệu tác vụ. Ngay cả khi mỗi việc mất một lượng thời gian rất nhỏ, chúng tôi vẫn sẽ sử dụng một lượng tài nguyên không hợp lý và làm giảm hiệu suất. Chúng ta cũng nên tránh các nhóm như vậy khi thời gian thực hiện không thể đoán trước, chẳng hạn như với các tác vụ I/O.

Dưới mui xe, hàm tạo ThreadPoolExecutor được gọi với các đối số sau:

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS,
      new SynchronousQueue<Runnable>());
}

Các giá trị sau được truyền cho hàm tạo dưới dạng đối số:

Tham số Giá trị
corePoolSize (có bao nhiêu luồng sẽ sẵn sàng (bắt đầu) khi dịch vụ thực thi bắt đầu) 0
maxPoolSize (số luồng tối đa mà dịch vụ thực thi có thể tạo) Số nguyên.MAX_VALUE
keepAliveTime (thời gian mà một luồng được giải phóng sẽ tiếp tục tồn tại trước khi bị hủy nếu số lượng luồng lớn hơn corePoolSize ) 60L
đơn vị (đơn vị thời gian) TimeUnit.SECONDS
workQueue (triển khai hàng đợi) Đồng bộ mớiQueue<Runnable>()

Và chúng ta có thể vượt qua việc triển khai ThreadFactory của riêng mình theo cách chính xác như vậy.

Hãy nói về SynchronousQueue

Ý tưởng cơ bản của chuyển giao đồng bộ khá đơn giản nhưng phản trực giác (nghĩa là trực giác hoặc lẽ thường cho bạn biết rằng điều đó là sai): bạn có thể thêm một phần tử vào hàng đợi khi và chỉ khi một luồng khác nhận phần tử đó tại cùng một lúc. Nói cách khác, một hàng đợi đồng bộ không thể có các tác vụ trong đó, bởi vì ngay khi một tác vụ mới đến, luồng thực thi đã nhận tác vụ đó rồi .

Khi một tác vụ mới vào hàng đợi, nếu có một chuỗi đang hoạt động miễn phí trong nhóm, thì nó sẽ chọn tác vụ đó. Nếu tất cả các luồng đang bận, thì một luồng mới sẽ được tạo.

Nhóm được lưu trong bộ nhớ cache bắt đầu với chuỗi số 0 và có khả năng phát triển thành chuỗi số nguyên.MAX_VALUE . Về cơ bản, kích thước của nhóm luồng được lưu trong bộ nhớ cache chỉ bị giới hạn bởi tài nguyên hệ thống.

Để tiết kiệm tài nguyên hệ thống, các nhóm luồng được lưu trong bộ nhớ đệm sẽ loại bỏ các luồng không hoạt động trong một phút.

Hãy xem nó hoạt động như thế nào trong thực tế. Chúng tôi sẽ tạo một lớp nhiệm vụ mô hình hóa yêu cầu của người dùng:

public class Task implements Runnable {
   int taskNumber;

   public Task(int taskNumber) {
       this.taskNumber = taskNumber;
   }

   @Override
   public void run() {
       System.out.println("Processed user request #" + taskNumber + " on thread " + Thread.currentThread().getName());
   }
}

Trong phương thức chính , chúng tôi tạo newCachedThreadPool và sau đó thêm 3 tác vụ để thực thi. Ở đây chúng tôi in trạng thái dịch vụ của chúng tôi (1) .

Tiếp theo, chúng tôi tạm dừng trong 30 giây, bắt đầu một tác vụ khác và hiển thị trạng thái (2) .

Sau đó, chúng tôi tạm dừng luồng chính của mình trong 70 giây, in trạng thái (3) , sau đó thêm lại 3 tác vụ và in lại trạng thái (4) .

Ở những nơi chúng tôi hiển thị trạng thái ngay sau khi thêm tác vụ, trước tiên chúng tôi thêm chế độ ngủ 1 giây để có kết quả cập nhật.

ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < 3; i++) {
            executorService.submit(new Task(i));
        }

        TimeUnit.SECONDS.sleep(1);
            System.out.println(executorService);	//(1)

        TimeUnit.SECONDS.sleep(30);

        executorService.submit(new Task(3));
        TimeUnit.SECONDS.sleep(1);
            System.out.println(executorService);	//(2)

        TimeUnit.SECONDS.sleep(70);

            System.out.println(executorService);	//(3)

        for (int i = 4; i < 7; i++) {
            executorService.submit(new Task(i));
        }

        TimeUnit.SECONDS.sleep(1);
            System.out.println(executorService);	//(4)
        executorService.shutdown();

Và đây là kết quả:

Yêu cầu người dùng đã xử lý #0 trên luồng pool-1-thread-1
Yêu cầu người dùng đã xử lý #1 trên luồng pool-1-thread-2
Yêu cầu người dùng đã xử lý #2 trên luồng pool-1-thread-3
(1) java.util.concurrent .ThreadPoolExecutor@f6f4d33[Đang chạy, kích thước nhóm = 3, luồng hoạt động = 0, tác vụ được xếp hàng đợi = 0, tác vụ đã hoàn thành = 3] Đã
xử lý yêu cầu người dùng số 3 trên luồng nhóm-1-thread-2
(2) java.util.concurrent. ThreadPoolExecutor@f6f4d33[Đang chạy, kích thước nhóm = 3, các luồng hoạt động = 0, các tác vụ được xếp hàng đợi = 0, các tác vụ đã hoàn thành = 4] (3) java.util.concurrent.ThreadPoolExecutor@f6f4d33[Đang chạy,
kích thước nhóm = 0, các luồng hoạt động = 0 , các tác vụ đã xếp hàng = 0, các tác vụ đã hoàn thành = 4]
Yêu cầu người dùng đã xử lý số 4 trên chuỗi nhóm-1-thread-4
Yêu cầu người dùng đã xử lý số 5 trên chuỗi nhóm-1-thread-5
Đã xử lý yêu cầu người dùng số 6 trên chuỗi nhóm-1-thread-4
(4) java.util.concurrent.ThreadPoolExecutor@f6f4d33[Đang chạy, kích thước nhóm = 2, chuỗi hoạt động = 0, tác vụ được xếp hàng đợi = 0, tác vụ đã hoàn thành = 7]

Hãy đi qua từng bước:

Bước chân Giải trình
1 (sau khi hoàn thành 3 nhiệm vụ) Chúng tôi đã tạo 3 luồng và 3 tác vụ đã được thực hiện trên ba luồng này.
Khi trạng thái được hiển thị, cả 3 tác vụ đã được thực hiện và các luồng đã sẵn sàng để thực hiện các tác vụ khác.
2 (sau 30 giây tạm dừng và thực hiện tác vụ khác) Sau 30 giây không hoạt động, các chủ đề vẫn còn sống và đang chờ tác vụ.
Một tác vụ khác được thêm vào và thực thi trên một luồng được lấy từ nhóm các luồng trực tiếp còn lại.
Không có chủ đề mới nào được thêm vào nhóm.
3 (sau 70 giây tạm dừng) Các chủ đề đã được gỡ bỏ khỏi nhóm.
Không có chủ đề nào sẵn sàng nhận nhiệm vụ.
4 (sau khi thực hiện thêm 3 nhiệm vụ) Sau khi nhận được nhiều nhiệm vụ hơn, các chủ đề mới đã được tạo. Lần này chỉ có hai luồng quản lý để xử lý 3 tác vụ.

Chà, bây giờ bạn đã quen với logic của một loại dịch vụ thực thi khác.

Tương tự với các phương thức khác của lớp tiện ích Executor , phương thức newCachedThreadPool cũng có một phiên bản quá tải lấy một đối tượng ThreadFactory làm đối số.