Làm cách nào để tạo khối phương thức submit () của ThreadPoolExecutor nếu nó bị bão hòa?


102

Tôi muốn tạo một cái ThreadPoolExecutornhư vậy khi nó đã đạt đến kích thước tối đa và hàng đợi đã đầy, submit()phương thức sẽ chặn khi cố gắng thêm các tác vụ mới. Tôi có cần triển khai một tùy chỉnh RejectedExecutionHandlercho điều đó hay có cách nào hiện có để thực hiện việc này bằng cách sử dụng thư viện Java chuẩn không?


2
Bạn có muốn bất cứ thứ gì giống như phương thức offer () của hàng đợi Array không?
extraneon


2
@bacar Tôi không đồng ý. Câu hỏi & Đáp này trông có giá trị hơn (ngoài việc cũ hơn).
JasonMArcher

Câu trả lời:


47

Một trong những giải pháp khả thi mà tôi vừa tìm thấy:

public class BoundedExecutor {
    private final Executor exec;
    private final Semaphore semaphore;

    public BoundedExecutor(Executor exec, int bound) {
        this.exec = exec;
        this.semaphore = new Semaphore(bound);
    }

    public void submitTask(final Runnable command)
            throws InterruptedException, RejectedExecutionException {
        semaphore.acquire();
        try {
            exec.execute(new Runnable() {
                public void run() {
                    try {
                        command.run();
                    } finally {
                        semaphore.release();
                    }
                }
            });
        } catch (RejectedExecutionException e) {
            semaphore.release();
            throw e;
        }
    }
}

Có bất kỳ giải pháp khác? Tôi thích thứ gì đó dựa trên RejectedExecutionHandlervì nó có vẻ như là một cách tiêu chuẩn để xử lý những tình huống như vậy.


2
Có điều kiện chạy đua ở đây giữa thời điểm khi semaphore được phát hành trong mệnh đề cuối cùng và semaphore được mua không?
volni

2
Như đã đề cập ở trên, việc triển khai này là thiếu sót vì semaphore được phát hành trước khi tác vụ hoàn thành. Sẽ tốt hơn nếu sử dụng phương thức java.util.concurrent.ThreadPoolExecutor # afterExecute (Runnable, Throwable)
FelixM

2
@FelixM: sử dụng java.util.concurrent.ThreadPoolExecutor # afterExecute (Runnable, Throwable) sẽ không giải quyết được sự cố vì afterExecute được gọi ngay sau task.run () trong java.util.concurrent.ThreadPoolExecutor # runWorker (Worker w), trước lấy phần tử tiếp theo từ hàng đợi (xem mã nguồn của openjdk 1.7.0.6).
Jaan

1
Câu trả lời này là từ cuốn sách Java Concurrency in Practice bởi Brian Goetz
orangepips

11
Câu trả lời này không hoàn toàn chính xác, cũng là những ý kiến. Đoạn mã này thực sự đến từ Java Concurrency trong Thực tế, và nó đúng nếu bạn tính đến ngữ cảnh của nó . Cuốn sách nói rõ, theo nghĩa đen: "Trong cách tiếp cận như vậy, hãy sử dụng hàng đợi không bị ràng buộc (...) và đặt giới hạn trên semaphore bằng kích thước nhóm cộng với số nhiệm vụ được xếp hàng mà bạn muốn cho phép". Với một hàng đợi không bị ràng buộc, các nhiệm vụ sẽ không bao giờ bị từ chối, vì vậy việc lặp lại ngoại lệ là hoàn toàn vô ích! Đó là, tôi tin rằng, cũng là lý do tại sao throw e;KHÔNG trong cuốn sách. JCIP là chính xác!
Timmos

30

Bạn có thể sử dụng ThreadPoolExecutor và một BlockQueue:

public class ImageManager {
    BlockingQueue<Runnable> blockingQueue = new ArrayBlockingQueue<Runnable>(blockQueueSize);
    RejectedExecutionHandler rejectedExecutionHandler = new ThreadPoolExecutor.CallerRunsPolicy();
    private ExecutorService executorService =  new ThreadPoolExecutor(numOfThread, numOfThread, 
        0L, TimeUnit.MILLISECONDS, blockingQueue, rejectedExecutionHandler);

    private int downloadThumbnail(String fileListPath){
        executorService.submit(new yourRunnable());
    }
}

Tôi chỉ muốn nói rằng đây là một giải pháp cực kỳ nhanh chóng và dễ dàng để thực hiện và hoạt động rất hiệu quả!
Ivan

58
Thao tác này sẽ chạy các tác vụ bị từ chối trên chuỗi đang gửi. Mà về mặt chức năng không đáp ứng yêu cầu của OP.
Nhận thức

4
Thao tác này chạy tác vụ "trong chuỗi đang gọi" thay vì chặn để đưa nó vào hàng đợi, điều này có thể có một số tác động bất lợi, chẳng hạn như nếu nhiều chuỗi gọi nó theo cách này, thì nhiều công việc "kích thước hàng đợi" sẽ được chạy và nếu nhiệm vụ xảy ra mất nhiều thời gian hơn dự kiến, luồng "sản xuất" của bạn có thể không giữ cho người thực thi bận rộn. Nhưng làm việc tuyệt vời ở đây!
rogerdpack

4
Không ủng hộ: điều này không chặn khi TPE bão hòa. Đây chỉ là một giải pháp thay thế, không phải là một giải pháp.
Timmos

1
Được ủng hộ: điều này khá phù hợp với 'thiết kế của TPE' và 'chặn tự nhiên' các luồng máy khách bằng cách cho họ thực hiện những việc thừa. Điều này sẽ bao gồm hầu hết các trường hợp sử dụng, nhưng không phải tất nhiên là tất nhiên và bạn nên hiểu những gì đang diễn ra.
Mike

12

Bạn nên sử dụng CallerRunsPolicy, thực thi tác vụ bị từ chối trong chuỗi gọi. Bằng cách này, nó không thể gửi bất kỳ tác vụ mới nào cho người thực thi cho đến khi tác vụ đó được hoàn thành, tại thời điểm đó sẽ có một số luồng hồ bơi miễn phí hoặc quá trình sẽ lặp lại.

http://java.sun.com/j2se/1.5.0/docs/api/java/util/concurrent/ThreadPoolExecutor.CallerRunsPolicy.html

Từ các tài liệu:

Công việc bị từ chối

Các tác vụ mới được gửi trong phương thức thực thi (java.lang.Runnable) sẽ bị từ chối khi Người thực thi đã bị tắt và cũng như khi Người thực thi sử dụng giới hạn hữu hạn cho cả luồng tối đa và dung lượng hàng đợi công việc và bị bão hòa. Trong cả hai trường hợp, phương thức thực thi gọi phương thức RejectedExecutionHandler.rejectedExecution (java.lang.Runnable, java.util.concurrent.ThreadPoolExecutor) của phương thức RejectedExecutionHandler của nó. Bốn chính sách xử lý xác định trước được cung cấp:

  1. Trong ThreadPoolExecutor.AbortPolicy mặc định, trình xử lý ném một RejectedExecutionException thời gian chạy khi bị từ chối.
  2. Trong ThreadPoolExecutor.CallerRunsPolicy, tiểu trình gọi thực thi chính nó sẽ chạy tác vụ. Điều này cung cấp một cơ chế kiểm soát phản hồi đơn giản sẽ làm chậm tốc độ các nhiệm vụ mới được gửi.
  3. Trong ThreadPoolExecutor.DiscardPolicy, một tác vụ không thể thực hiện được chỉ đơn giản là bỏ.
  4. Trong ThreadPoolExecutor.DiscardOldestPolicy, nếu trình thực thi không được tắt, tác vụ ở đầu hàng đợi công việc sẽ bị loại bỏ và sau đó việc thực thi được thử lại (có thể không thành công lần nữa, khiến việc này lặp lại.)

Ngoài ra, hãy đảm bảo sử dụng hàng đợi có giới hạn, chẳng hạn như ArrayBlockingQueue, khi gọi hàm ThreadPoolExecutortạo. Nếu không, sẽ không có gì bị từ chối.

Chỉnh sửa: để trả lời nhận xét của bạn, hãy đặt kích thước của ArrayBlockingQueue bằng với kích thước tối đa của nhóm luồng và sử dụng AbortPolicy.

Chỉnh sửa 2: Được rồi, tôi hiểu bạn đang nhận được gì. Điều này là gì: ghi đè beforeExecute()phương thức để kiểm tra getActiveCount()không vượt quá getMaximumPoolSize()và nếu có, hãy ngủ và thử lại?


3
Tôi muốn có số lượng tác vụ được thực thi đồng thời được giới hạn chặt chẽ (bởi số lượng các luồng trong Executor), đây là lý do tại sao tôi không thể cho phép các luồng người gọi tự thực thi các tác vụ này.
Fixpoint

1
AbortPolicy sẽ khiến luồng người gọi nhận được RejectedExecutionException, trong khi tôi cần nó chỉ chặn.
Fixpoint

2
Tôi đang yêu cầu chặn, không ngủ & thăm dò ý kiến;)
Fixpoint

@danben: Ý bạn không phải là CallerRunsPolicy ?
dùng359996,

7
Vấn đề với CallerRunPolicy là nếu bạn có một trình tạo luồng đơn lẻ, bạn sẽ thường có các luồng không được sử dụng nếu một tác vụ đang chạy lâu xảy ra bị từ chối (vì các tác vụ khác trong nhóm luồng sẽ hoàn thành trong khi tác vụ chạy dài vẫn còn đang chạy).
Adam Gent

6

Hibernate có một BlockPolicyđiều đó là đơn giản và có thể làm những gì bạn muốn:

Xem: Executor.java

/**
 * A handler for rejected tasks that will have the caller block until
 * space is available.
 */
public static class BlockPolicy implements RejectedExecutionHandler {

    /**
     * Creates a <tt>BlockPolicy</tt>.
     */
    public BlockPolicy() { }

    /**
     * Puts the Runnable to the blocking queue, effectively blocking
     * the delegating thread until space is available.
     * @param r the runnable task requested to be executed
     * @param e the executor attempting to execute this task
     */
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        try {
            e.getQueue().put( r );
        }
        catch (InterruptedException e1) {
            log.error( "Work discarded, thread was interrupted while waiting for space to schedule: {}", r );
        }
    }
}

4
Suy nghĩ thứ hai, đây là một ý tưởng khá tồi. Tôi không khuyên bạn nên sử dụng nó. Đối với lý do chính đáng xem tại đây: stackoverflow.com/questions/3446011/...
Nate Murray

Ngoài ra, điều này không sử dụng "thư viện Java tiêu chuẩn", theo yêu cầu của OP. Xóa bỏ?
dùng359996,

1
Woah, thật là xấu xí. Về cơ bản, giải pháp này can thiệp vào nội bộ của TPE. Các javadoc cho ThreadPoolExecutordù nói theo nghĩa đen: "Phương pháp getQueue () cho phép truy cập vào hàng đợi công việc cho các mục đích giám sát và gỡ lỗi Sử dụng phương pháp này cho bất kỳ mục đích nào khác không được khuyến khích mạnh mẽ..". Điều này có sẵn trong một thư viện được biết đến rộng rãi, thật đáng buồn khi thấy.
Timmos

1
com.amazonaws.services.simpleworkflow.flow.worker.BlockCallerPolicy cũng tương tự.
Adrian Baker

6

Câu BoundedExecutortrả lời được trích dẫn ở trên từ Java Concurrency in Practice chỉ hoạt động chính xác nếu bạn sử dụng hàng đợi không bị ràng buộc cho Người thực thi hoặc giới hạn semaphore không lớn hơn kích thước hàng đợi. Semaphore là trạng thái được chia sẻ giữa luồng gửi và các luồng trong nhóm, làm cho nó có thể bão hòa trình thực thi ngay cả khi kích thước hàng đợi <ràng buộc <= (kích thước hàng đợi + kích thước nhóm).

Việc sử dụng CallerRunsPolicychỉ hợp lệ nếu các nhiệm vụ của bạn không chạy mãi mãi, trong trường hợp đó, chuỗi gửi của bạn sẽ vẫn tồn tại rejectedExecutionmãi mãi và một ý tưởng tồi nếu các nhiệm vụ của bạn mất nhiều thời gian để chạy, vì chuỗi đang gửi không thể gửi bất kỳ tác vụ mới nào hoặc làm bất cứ điều gì khác nếu bản thân nó đang chạy một tác vụ.

Nếu điều đó không được chấp nhận thì tôi khuyên bạn nên kiểm tra kích thước của hàng đợi bị ràng buộc của trình thực thi trước khi gửi tác vụ. Nếu hàng đợi đã đầy, hãy đợi một khoảng thời gian ngắn trước khi thử gửi lại. Thông lượng sẽ bị ảnh hưởng, nhưng tôi đề xuất đó là một giải pháp đơn giản hơn nhiều giải pháp được đề xuất khác và bạn được đảm bảo sẽ không có nhiệm vụ nào bị từ chối.


Tôi không chắc cách kiểm tra độ dài hàng đợi trước khi gửi đảm bảo không có tác vụ bị từ chối nào trong môi trường đa luồng với nhiều trình tạo tác vụ. Điều đó có vẻ không an toàn.
Tim

5

Tôi biết, đó là một vụ hack, nhưng theo ý kiến ​​của tôi, hầu hết các vụ hack sạch sẽ giữa những thứ được cung cấp ở đây ;-)

Vì ThreadPoolExecutor sử dụng hàng đợi chặn "ưu đãi" thay vì "đặt", cho phép ghi đè hành vi của "đề nghị" của hàng đợi chặn:

class BlockingQueueHack<T> extends ArrayBlockingQueue<T> {

    BlockingQueueHack(int size) {
        super(size);
    }

    public boolean offer(T task) {
        try {
            this.put(task);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        return true;
    }
}

ThreadPoolExecutor tp = new ThreadPoolExecutor(1, 2, 1, TimeUnit.MINUTES, new BlockingQueueHack(5));

Tôi đã thử nghiệm nó và nó có vẻ hoạt động. Việc thực hiện một số chính sách thời gian chờ được coi là bài tập của người đọc.


Xem stackoverflow.com/a/4522411/2601671 để biết phiên bản đã được làm sạch của nó. Tôi đồng ý, đó là cách sạch sẽ nhất để làm điều đó.
Trenton

3

Lớp sau bao bọc xung quanh ThreadPoolExecutor và sử dụng Semaphore để chặn sau đó hàng đợi công việc đầy:

public final class BlockingExecutor { 

    private final Executor executor;
    private final Semaphore semaphore;

    public BlockingExecutor(int queueSize, int corePoolSize, int maxPoolSize, int keepAliveTime, TimeUnit unit, ThreadFactory factory) {
        BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>();
        this.executor = new ThreadPoolExecutor(corePoolSize, maxPoolSize, keepAliveTime, unit, queue, factory);
        this.semaphore = new Semaphore(queueSize + maxPoolSize);
    }

    private void execImpl (final Runnable command) throws InterruptedException {
        semaphore.acquire();
        try {
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        command.run();
                    } finally {
                        semaphore.release();
                    }
                }
            });
        } catch (RejectedExecutionException e) {
            // will never be thrown with an unbounded buffer (LinkedBlockingQueue)
            semaphore.release();
            throw e;
        }
    }

    public void execute (Runnable command) throws InterruptedException {
        execImpl(command);
    }
}

Lớp wrapper này dựa trên một giải pháp được đưa ra trong cuốn sách Java Concurrency in Practice của Brian Goetz. Lời giải trong cuốn sách chỉ nhận hai tham số hàm tạo: an Executorvà một giới hạn được sử dụng cho semaphore. Điều này được thể hiện trong câu trả lời do Fixpoint đưa ra. Có một vấn đề với cách tiếp cận đó: nó có thể rơi vào trạng thái mà các luồng hồ bơi đang bận, hàng đợi đã đầy, nhưng semaphore vừa phát hành giấy phép. ( semaphore.release()trong khối cuối cùng). Ở trạng thái này, một nhiệm vụ mới có thể lấy giấy phép vừa phát hành, nhưng bị từ chối vì hàng đợi nhiệm vụ đã đầy. Tất nhiên đây không phải là điều bạn muốn; bạn muốn chặn trong trường hợp này.

Để giải quyết điều này, chúng ta phải sử dụng một hàng đợi không bị ràng buộc , như JCiP đã đề cập rõ ràng. Semaphore hoạt động như một người bảo vệ, tạo ra hiệu ứng của kích thước hàng đợi ảo. Điều này có tác dụng phụ là đơn vị có thể chứa các maxPoolSize + virtualQueueSize + maxPoolSizenhiệm vụ. Tại sao vậy? Bởi vì semaphore.release()trong khối cuối cùng. Nếu tất cả các luồng nhóm gọi câu lệnh này cùng một lúc, thì các maxPoolSizegiấy phép sẽ được giải phóng, cho phép cùng một số tác vụ vào đơn vị. Nếu chúng ta đang sử dụng một hàng đợi bị giới hạn, nó sẽ vẫn đầy, dẫn đến nhiệm vụ bị từ chối. Bây giờ, bởi vì chúng ta biết rằng điều này chỉ xảy ra khi một luồng hồ bơi gần như hoàn thành, nên đây không phải là vấn đề. Chúng tôi biết rằng luồng hồ bơi sẽ không chặn, vì vậy một tác vụ sẽ sớm được thực hiện từ hàng đợi.

Tuy nhiên, bạn có thể sử dụng một hàng đợi có giới hạn. Chỉ cần đảm bảo rằng kích thước của nó bằng nhau virtualQueueSize + maxPoolSize. Kích thước lớn hơn là vô dụng, semaphore sẽ ngăn không cho nhiều mục hơn vào. Kích thước nhỏ hơn sẽ dẫn đến các tác vụ bị từ chối. Cơ hội các nhiệm vụ bị từ chối tăng lên khi kích thước giảm. Ví dụ: giả sử bạn muốn một trình thực thi có giới hạn với maxPoolSize = 2 và virtualQueueSize = 5. Sau đó, lấy một semaphore với 5 + 2 = 7 giấy phép và kích thước hàng đợi thực tế là 5 + 2 = 7. Số nhiệm vụ thực có thể có trong đơn vị khi đó là 2 + 5 + 2 = 9. Khi trình thực thi đã đầy (5 tác vụ trong hàng đợi, 2 trong nhóm luồng, do đó 0 giấy phép khả dụng) và TẤT CẢ các luồng nhóm giải phóng giấy phép của chúng, thì chính xác 2 giấy phép có thể được thực hiện bởi các tác vụ đến.

Giờ đây, giải pháp từ JCiP hơi cồng kềnh để sử dụng vì nó không thực thi tất cả các ràng buộc này (hàng đợi không bị ràng buộc hoặc bị ràng buộc với các hạn chế toán học đó, v.v.). Tôi nghĩ rằng đây chỉ là một ví dụ điển hình để chứng minh cách bạn có thể xây dựng các lớp an toàn luồng mới dựa trên các phần đã có sẵn, nhưng không phải là một lớp hoàn chỉnh, có thể tái sử dụng. Tôi không nghĩ rằng cái sau là chủ ý của tác giả.


2

bạn có thể sử dụng RejectedExecutionHandler tùy chỉnh như thế này

ThreadPoolExecutor tp= new ThreadPoolExecutor(core_size, // core size
                max_handlers, // max size 
                timeout_in_seconds, // idle timeout 
                TimeUnit.SECONDS, queue, new RejectedExecutionHandler() {
                    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
                        // This will block if the queue is full
                        try {
                            executor.getQueue().put(r);
                        } catch (InterruptedException e) {
                            System.err.println(e.getMessage());
                        }

                    }
                });

1
Các tài liệu cho getQueue () đề cập rõ ràng rằng Quyền truy cập vào hàng đợi tác vụ được thiết kế chủ yếu để gỡ lỗi và giám sát.
Chadi

0

Tạo hàng đợi chặn của riêng bạn để Người thực thi sử dụng, với hành vi chặn mà bạn đang tìm kiếm, trong khi luôn trả về dung lượng còn lại khả dụng (đảm bảo người thực thi sẽ không cố gắng tạo nhiều luồng hơn nhóm lõi của nó hoặc kích hoạt trình xử lý từ chối).

Tôi tin rằng điều này sẽ giúp bạn có được hành vi chặn mà bạn đang tìm kiếm. Một trình xử lý từ chối sẽ không bao giờ phù hợp với hóa đơn, vì điều đó cho thấy người thực thi không thể thực hiện nhiệm vụ. Những gì tôi có thể hình dung ở đó là bạn nhận được một số dạng 'bận chờ đợi' trong trình xử lý. Đó không phải là những gì bạn muốn, bạn muốn một hàng đợi cho trình thực thi chặn người gọi ...


2
ThreadPoolExecutorsử dụng offerphương thức để thêm nhiệm vụ vào hàng đợi. Nếu tôi tạo một tùy chỉnh BlockingQueuechặn trên offer, điều đó sẽ phá vỡ BlockingQueuehợp đồng của tôi.
Fixpoint

@Shooshpanchick, điều đó sẽ phá vỡ hợp đồng BlockingQueues. Vậy thì sao? nếu bạn đang rất quan tâm bạn có thể bật một cách rõ ràng hành vi trong thời gian nộp () chỉ (nó sẽ mất một ThreadLocal)
bestsss

Xem thêm câu trả lời này cho một câu hỏi khác giải thích sự thay thế này.
Robert Tupelo-Schneck,

có lý do tại sao ThreadPoolExecutorđược triển khai để sử dụng offervà không put(phiên bản chặn)? Ngoài ra, nếu có một cách để mã khách hàng cho biết sẽ sử dụng cái nào khi nào, thì rất nhiều người đang cố gắng cuộn các giải pháp tùy chỉnh theo cách thủ công sẽ cảm thấy nhẹ nhõm
asgs

0

Để tránh sự cố với giải pháp @FixPoint. Người ta có thể sử dụng ListeningExecutorService và phát hành semaphore onSuccess và onFailure bên trong FutureCallback.


Điều đó có cùng các vấn đề cố hữu là chỉ gói Runnablenhư những phương pháp đó vẫn được gọi trước khi công nhân dọn dẹp bình thường ThreadPoolExecutor. Đó là bạn vẫn sẽ cần phải xử lý các ngoại lệ từ chối.
Adam Gent

0

Gần đây tôi thấy câu hỏi này có cùng một vấn đề. OP không nói rõ ràng như vậy, nhưng chúng tôi không muốn sử dụng RejectedExecutionHandlernó thực thi một tác vụ trên luồng của trình đệ trình, bởi vì điều này sẽ không sử dụng hết các luồng công nhân nếu tác vụ này chạy lâu.

Đọc tất cả các câu trả lời và nhận xét, đặc biệt là giải pháp thiếu sót với semaphore hoặc bằng cách sử dụng, afterExecutetôi đã xem xét kỹ hơn mã của ThreadPoolExecutor để xem có cách nào thoát ra không. Tôi đã rất ngạc nhiên khi thấy có hơn 2000 dòng mã (được bình luận), một số trong số đó khiến tôi cảm thấy chóng mặt . Với yêu cầu khá đơn giản mà tôi thực sự có --- một nhà sản xuất, một số người tiêu dùng, hãy để nhà sản xuất chặn khi không có người tiêu dùng nào có thể làm việc --- tôi quyết định đưa ra giải pháp của riêng mình. Nó không phải là một ExecutorServicemà chỉ là một Executor. Và nó không điều chỉnh số lượng luồng theo tải công việc, mà chỉ giữ một số luồng cố định, cũng phù hợp với yêu cầu của tôi. Đây là mã. Cảm thấy tự do để nói về nó :-)

package x;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executor;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.SynchronousQueue;

/**
 * distributes {@code Runnable}s to a fixed number of threads. To keep the
 * code lean, this is not an {@code ExecutorService}. In particular there is
 * only very simple support to shut this executor down.
 */
public class ParallelExecutor implements Executor {
  // other bounded queues work as well and are useful to buffer peak loads
  private final BlockingQueue<Runnable> workQueue =
      new SynchronousQueue<Runnable>();
  private final Thread[] threads;

  /*+**********************************************************************/
  /**
   * creates the requested number of threads and starts them to wait for
   * incoming work
   */
  public ParallelExecutor(int numThreads) {
    this.threads = new Thread[numThreads];
    for(int i=0; i<numThreads; i++) {
      // could reuse the same Runner all over, but keep it simple
      Thread t = new Thread(new Runner());
      this.threads[i] = t;
      t.start();
    }
  }
  /*+**********************************************************************/
  /**
   * returns immediately without waiting for the task to be finished, but may
   * block if all worker threads are busy.
   * 
   * @throws RejectedExecutionException if we got interrupted while waiting
   *         for a free worker
   */
  @Override
  public void execute(Runnable task)  {
    try {
      workQueue.put(task);
    } catch (InterruptedException e) {
      Thread.currentThread().interrupt();
      throw new RejectedExecutionException("interrupt while waiting for a free "
          + "worker.", e);
    }
  }
  /*+**********************************************************************/
  /**
   * Interrupts all workers and joins them. Tasks susceptible to an interrupt
   * will preempt their work. Blocks until the last thread surrendered.
   */
  public void interruptAndJoinAll() throws InterruptedException {
    for(Thread t : threads) {
      t.interrupt();
    }
    for(Thread t : threads) {
      t.join();
    }
  }
  /*+**********************************************************************/
  private final class Runner implements Runnable {
    @Override
    public void run() {
      while (!Thread.currentThread().isInterrupted()) {
        Runnable task;
        try {
          task = workQueue.take();
        } catch (InterruptedException e) {
          // canonical handling despite exiting right away
          Thread.currentThread().interrupt(); 
          return;
        }
        try {
          task.run();
        } catch (RuntimeException e) {
          // production code to use a logging framework
          e.printStackTrace();
        }
      }
    }
  }
}

0

Tôi tin rằng có một cách khá hữu ích để giải quyết vấn đề này bằng cách sử dụng java.util.concurrent.Semaphorevà hành vi ủy quyền của Executor.newFixedThreadPool. Dịch vụ thực thi mới sẽ chỉ thực thi tác vụ mới khi có một luồng để thực hiện việc đó. Việc chặn được quản lý bởi Semaphore với số lượng giấy phép bằng số luồng. Khi một nhiệm vụ hoàn thành, nó sẽ trả về một giấy phép.

public class FixedThreadBlockingExecutorService extends AbstractExecutorService {

private final ExecutorService executor;
private final Semaphore blockExecution;

public FixedThreadBlockingExecutorService(int nTreads) {
    this.executor = Executors.newFixedThreadPool(nTreads);
    blockExecution = new Semaphore(nTreads);
}

@Override
public void shutdown() {
    executor.shutdown();
}

@Override
public List<Runnable> shutdownNow() {
    return executor.shutdownNow();
}

@Override
public boolean isShutdown() {
    return executor.isShutdown();
}

@Override
public boolean isTerminated() {
    return executor.isTerminated();
}

@Override
public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
    return executor.awaitTermination(timeout, unit);
}

@Override
public void execute(Runnable command) {
    blockExecution.acquireUninterruptibly();
    executor.execute(() -> {
        try {
            command.run();
        } finally {
            blockExecution.release();
        }
    });
}

Tôi đã triển khai BoundExecutor được mô tả trong Java Concurrency trong Thực tế và phát hiện ra rằng Semaphore phải được khởi tạo với cờ công bằng được đặt thành true để đảm bảo rằng các giấy phép Semaphore được cung cấp theo yêu cầu đặt hàng. Tham khảo docs.oracle.com/javase/7/docs/api/java/util/concurrent/… . để biết chi tiết
Prahalad Deshpande

0

Trước đây tôi cũng có nhu cầu tương tự: một loại hàng đợi chặn có kích thước cố định cho mỗi khách hàng được hỗ trợ bởi một nhóm luồng chia sẻ. Tôi đã viết xong loại ThreadPoolExecutor của riêng mình:

UserThreadPoolExecutor (hàng đợi chặn (mỗi máy khách) + luồng luồng (được chia sẻ giữa tất cả các máy khách))

Xem: https://github.com/d4rxh4wx/UserThreadPoolExecutor

Mỗi UserThreadPoolExecutor được cung cấp số lượng tối đa các luồng từ một ThreadPoolExecutor được chia sẻ

Mỗi UserThreadPoolExecutor có thể:

  • gửi một nhiệm vụ cho người thực thi nhóm luồng được chia sẻ nếu không đạt đến hạn ngạch của nó. Nếu đạt đến hạn ngạch, công việc sẽ được xếp hàng đợi (chặn không tiêu thụ khi chờ CPU). Khi một trong các nhiệm vụ đã gửi của nó được hoàn thành, hạn ngạch sẽ giảm xuống, cho phép một nhiệm vụ khác đang chờ được gửi đến ThreadPoolExecutor
  • đợi các nhiệm vụ còn lại hoàn thành

0

Tôi đã tìm thấy chính sách từ chối này trong ứng dụng khách tìm kiếm đàn hồi. Nó chặn luồng người gọi trên hàng đợi chặn. Mã bên dưới-

 static class ForceQueuePolicy implements XRejectedExecutionHandler 
 {
        public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) 
        {
            try 
            {
                executor.getQueue().put(r);
            } 
            catch (InterruptedException e) 
            {
                //should never happen since we never wait
                throw new EsRejectedExecutionException(e);
            }
        }

        @Override
        public long rejected() 
        {
            return 0;
        }
}

0

Gần đây tôi có nhu cầu đạt được điều gì đó tương tự, nhưng trên a ScheduledExecutorService.

Tôi cũng phải đảm bảo rằng tôi xử lý độ trễ được truyền trên phương thức và đảm bảo rằng tác vụ được gửi để thực thi tại thời điểm như người gọi mong đợi hoặc chỉ không thành công do đó ném a RejectedExecutionException.

Các phương thức khác ScheduledThreadPoolExecutorđể thực thi hoặc gửi một lệnh gọi nội bộ #schedulesẽ vẫn gọi các phương thức bị ghi đè.

import java.util.concurrent.*;

public class BlockingScheduler extends ScheduledThreadPoolExecutor {
    private final Semaphore maxQueueSize;

    public BlockingScheduler(int corePoolSize,
                             ThreadFactory threadFactory,
                             int maxQueueSize) {
        super(corePoolSize, threadFactory, new AbortPolicy());
        this.maxQueueSize = new Semaphore(maxQueueSize);
    }

    @Override
    public ScheduledFuture<?> schedule(Runnable command,
                                       long delay,
                                       TimeUnit unit) {
        final long newDelayInMs = beforeSchedule(command, unit.toMillis(delay));
        return super.schedule(command, newDelayInMs, TimeUnit.MILLISECONDS);
    }

    @Override
    public <V> ScheduledFuture<V> schedule(Callable<V> callable,
                                           long delay,
                                           TimeUnit unit) {
        final long newDelayInMs = beforeSchedule(callable, unit.toMillis(delay));
        return super.schedule(callable, newDelayInMs, TimeUnit.MILLISECONDS);
    }

    @Override
    public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
                                                  long initialDelay,
                                                  long period,
                                                  TimeUnit unit) {
        final long newDelayInMs = beforeSchedule(command, unit.toMillis(initialDelay));
        return super.scheduleAtFixedRate(command, newDelayInMs, unit.toMillis(period), TimeUnit.MILLISECONDS);
    }

    @Override
    public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
                                                     long initialDelay,
                                                     long period,
                                                     TimeUnit unit) {
        final long newDelayInMs = beforeSchedule(command, unit.toMillis(initialDelay));
        return super.scheduleWithFixedDelay(command, newDelayInMs, unit.toMillis(period), TimeUnit.MILLISECONDS);
    }

    @Override
    protected void afterExecute(Runnable runnable, Throwable t) {
        super.afterExecute(runnable, t);
        try {
            if (t == null && runnable instanceof Future<?>) {
                try {
                    ((Future<?>) runnable).get();
                } catch (CancellationException | ExecutionException e) {
                    t = e;
                } catch (InterruptedException ie) {
                    Thread.currentThread().interrupt(); // ignore/reset
                }
            }
            if (t != null) {
                System.err.println(t);
            }
        } finally {
            releaseQueueUsage();
        }
    }

    private long beforeSchedule(Runnable runnable, long delay) {
        try {
            return getQueuePermitAndModifiedDelay(delay);
        } catch (InterruptedException e) {
            getRejectedExecutionHandler().rejectedExecution(runnable, this);
            return 0;
        }
    }

    private long beforeSchedule(Callable callable, long delay) {
        try {
            return getQueuePermitAndModifiedDelay(delay);
        } catch (InterruptedException e) {
            getRejectedExecutionHandler().rejectedExecution(new FutureTask(callable), this);
            return 0;
        }
    }

    private long getQueuePermitAndModifiedDelay(long delay) throws InterruptedException {
        final long beforeAcquireTimeStamp = System.currentTimeMillis();
        maxQueueSize.tryAcquire(delay, TimeUnit.MILLISECONDS);
        final long afterAcquireTimeStamp = System.currentTimeMillis();
        return afterAcquireTimeStamp - beforeAcquireTimeStamp;
    }

    private void releaseQueueUsage() {
        maxQueueSize.release();
    }
}

Tôi có mã ở đây, sẽ đánh giá cao bất kỳ phản hồi nào. https://github.com/AmitabhAwasthi/BlockingScheduler


Câu trả lời này hoàn toàn dựa vào nội dung của các liên kết bên ngoài. Nếu chúng trở nên vô hiệu, câu trả lời của bạn sẽ vô dụng. Vì vậy, vui lòng chỉnh sửa câu trả lời của bạn và thêm ít nhất một bản tóm tắt về những gì có thể tìm thấy ở đó. Cảm ơn bạn!
Fabio nói Hãy phục hồi Monica vào

@fabio: cảm ơn bạn đã chỉ ra. Tôi đã thêm mã vào đó để bây giờ nó có ý nghĩa hơn cho người đọc. Đánh giá cao nhận xét của bạn :)
Dev Amitabh


0

Tôi không phải lúc nào cũng thích CallerRunsPolicy, đặc biệt là vì nó cho phép tác vụ bị từ chối 'bỏ qua hàng đợi' và được thực thi trước các tác vụ đã được gửi trước đó. Hơn nữa, việc thực thi tác vụ trên chuỗi đang gọi có thể mất nhiều thời gian hơn so với việc chờ đợi vùng đầu tiên khả dụng.

Tôi đã giải quyết sự cố này bằng cách sử dụng RejectedExecutionHandler tùy chỉnh, chỉ đơn giản là chặn chuỗi gọi trong một thời gian ngắn và sau đó cố gắng gửi lại tác vụ:

public class BlockWhenQueueFull implements RejectedExecutionHandler {

    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {

        // The pool is full. Wait, then try again.
        try {
            long waitMs = 250;
            Thread.sleep(waitMs);
        } catch (InterruptedException interruptedException) {}

        executor.execute(r);
    }
}

Lớp này chỉ có thể được sử dụng trong trình thực thi thread-pool như một RejectedExecutinHandler giống như bất kỳ lớp nào khác, ví dụ:

executorPool = new ThreadPoolExecutor(1, 1, 10,
                                      TimeUnit.SECONDS, new SynchronousQueue<Runnable>(),
                                      new BlockWhenQueueFull());

Nhược điểm duy nhất mà tôi thấy là luồng cuộc gọi có thể bị khóa lâu hơn mức cần thiết một chút (lên đến 250ms). Hơn nữa, vì trình thực thi này đang được gọi đệ quy một cách hiệu quả, việc chờ đợi rất lâu cho một luồng khả dụng (giờ) có thể dẫn đến tràn ngăn xếp.

Tuy nhiên, cá nhân tôi thích phương pháp này. Nó nhỏ gọn, dễ hiểu và hoạt động tốt.


1
Như bạn tự nói: điều này có thể tạo ra một luồng chồng chéo. Không phải là thứ tôi muốn có trong mã sản xuất.
Harald

Mọi người nên đưa ra quyết định của riêng mình. Đối với khối lượng công việc của tôi, đây không phải là một vấn đề. Các nhiệm vụ chạy trong vài giây thay vì hàng giờ cần thiết để làm nổ ngăn xếp. Hơn nữa, điều tương tự cũng có thể nói đối với hầu như bất kỳ thuật toán đệ quy nào. Đó có phải là lý do để không bao giờ sử dụng bất kỳ thuật toán đệ quy nào trong sản xuất không?
TinkerTank
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.