Chuyển một ExecutorService thành daemon trong Java


77

Tôi đang sử dụng ExecutoreService trong Java 1.6, bắt đầu đơn giản bằng

ExecutorService pool = Executors.newFixedThreadPool(THREADS). 

Khi luồng chính của tôi kết thúc (cùng với tất cả các tác vụ được xử lý bởi nhóm luồng), nhóm này sẽ ngăn chương trình của tôi tắt cho đến khi tôi gọi rõ ràng

pool.shutdown();

Tôi có thể tránh phải gọi điều này bằng cách nào đó chuyển quản lý luồng nội bộ được sử dụng bởi nhóm này thành một luồng ngừng hoạt động không? Hoặc tôi đang thiếu một cái gì đó ở đây.


1
Tôi với tư cách là tác giả của câu trả lời được chấp nhận hiện tại sẽ đề xuất phương pháp đọc được đăng bởi Marco13: stackoverflow.com/a/29453160/1393766 và thay đổi dấu chấp nhận từ bài đăng của tôi thành của anh ấy, vì giải pháp được mô tả có lẽ là đơn giản nhất và gần với những gì bạn muốn đạt được ban đầu .
Pshemo

1
@Pshemo Hãy để tôi không đồng ý với bạn ... Vui lòng kiểm tra bình luận của tôi dưới câu trả lời của Marco13.
Vladimir

@Pshemo Theo gợi ý của Vladimirs, bạn có thể cân nhắc chỉ ra thực tế rằng giải pháp "đơn giản" có thể không phải lúc nào cũng được ưu tiên.
Marco13

@ Marco13 Tôi đã đặt "có lẽ" ở đầu câu trả lời của mình vì chính xác lý do đó. Nhưng có thể là ý kiến ​​hay khi thêm vào câu trả lời của bạn thêm thông tin về ý tưởng đằng sau nó, và có thể một số lời khuyên chung như giá trị hợp lý có thể là gì keepAliveTimetùy thuộc vào tần suất thực thi nhiệm vụ.
Pshemo

Câu trả lời:


102

Có lẽ giải pháp đơn giản nhất và được ưu tiên là câu trả lời của Marco13, vì vậy đừng để bị lừa bởi sự khác biệt về phiếu bầu (câu trả lời của tôi cũ hơn vài năm) hoặc dấu chấp nhận (nó chỉ có nghĩa là giải pháp của tôi phù hợp với hoàn cảnh OP chứ không phải là tốt nhất).


Bạn có thể sử dụng ThreadFactoryđể đặt các luồng bên trong Executor thành daemon. Điều này sẽ ảnh hưởng đến dịch vụ thực thi theo cách mà nó cũng sẽ trở thành luồng daemon nên nó (và các luồng được xử lý bởi nó) sẽ dừng nếu không có luồng không phải là daemon nào khác. Đây là ví dụ đơn giản:

ExecutorService exec = Executors.newFixedThreadPool(4,
        new ThreadFactory() {
            public Thread newThread(Runnable r) {
                Thread t = Executors.defaultThreadFactory().newThread(r);
                t.setDaemon(true);
                return t;
            }
        });

exec.execute(YourTaskNowWillBeDaemon);

Nhưng nếu bạn muốn nhận trình thực thi sẽ cho phép tác vụ của nó kết thúc và đồng thời sẽ tự động gọi shutdown()phương thức của nó khi ứng dụng hoàn tất, bạn có thể muốn bọc trình thực thi của mình bằng Guava MoreExecutors.getExitingExecutorService .

ExecutorService exec = MoreExecutors.getExitingExecutorService(
        (ThreadPoolExecutor) Executors.newFixedThreadPool(4), 
        100_000, TimeUnit.DAYS//period after which executor will be automatically closed
                             //I assume that 100_000 days is enough to simulate infinity
);
//exec.execute(YourTask);
exec.execute(() -> {
    for (int i = 0; i < 3; i++) {
        System.out.println("daemon");
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
});

1
Mặc dù câu trả lời này đã được chấp nhận, tôi không nghĩ đây là những gì OP dự định: Tôi nghĩ chương trình nên tắt khi tất cả các tác vụ đã được xử lý. Khi đặt luồng của trình thực thi thành 'daemon', chương trình sẽ thoát ngay cả khi vẫn còn các tác vụ cần chạy.
Arnout Engelen

@ArnoutEngelen Tôi đã cập nhật câu trả lời của mình để đưa ra cách tiếp cận thay thế cũng giải quyết được vấn đề này nhưng tôi tin rằng bài đăng của Marco13 chứa cách tiếp cận tốt nhất.
Pshemo

53

Đã có một chức năng tích hợp để tạo một ExecutorServicekết thúc tất cả các chuỗi sau một thời gian không hoạt động nhất định: Bạn có thể tạo một ThreadPoolExecutor, chuyển cho nó thông tin thời gian mong muốn, sau đó gọi allowCoreThreadTimeout(true)dịch vụ thực thi này:

/**
 * Creates an executor service with a fixed pool size, that will time 
 * out after a certain period of inactivity.
 * 
 * @param poolSize The core- and maximum pool size
 * @param keepAliveTime The keep alive time
 * @param timeUnit The time unit
 * @return The executor service
 */
public static ExecutorService createFixedTimeoutExecutorService(
    int poolSize, long keepAliveTime, TimeUnit timeUnit)
{
    ThreadPoolExecutor e = 
        new ThreadPoolExecutor(poolSize, poolSize,
            keepAliveTime, timeUnit, new LinkedBlockingQueue<Runnable>());
    e.allowCoreThreadTimeOut(true);
    return e;
}

CHỈNH SỬA Tham khảo các chú thích trong nhận xét: Lưu ý rằng trình thực thi nhóm luồng này sẽ không tự động tắt khi ứng dụng thoát. Trình thực thi sẽ tiếp tục chạy sau khi ứng dụng thoát, nhưng không lâu hơn keepAliveTime. Nếu, tùy thuộc vào các yêu cầu ứng dụng chính xác, keepAliveTimephải dài hơn vài giây, thì giải pháp trong câu trả lời của Pshemo có thể phù hợp hơn: Khi các chủ đề được đặt thành các chủ đề daemon, thì chúng sẽ kết thúc ngay lập tức khi ứng dụng thoát .


1
Đó là thông tin rất thú vị. +1. Vì vậy, nếu tôi hiểu nó một cách chính xác, chúng ta có thể đơn giản làm cho các tài nguyên luồng miễn phí của Executor nhanh hơn, bao gồm cả những tài nguyên cốt lõi. Nhưng chỉ thiếu tài nguyên không có nghĩa là trình thực thi sẽ bị tắt khi tài nguyên cuối cùng bị xóa. Việc tắt máy sẽ tự động được gọi khi Người thực thi không có tài nguyên (và các tác vụ mới để chạy) và ứng dụng đã hoàn tất.
Pshemo

1
@Pshemo Đúng, đây không phải là "tắt máy" theo nghĩa như trong shutdownphương thức. Tôi đã sửa lại điều này một chút, hy vọng rằng nó ít mơ hồ hơn bây giờ.
Marco 13

3
@ Marco13 Đây là một tính năng thú vị, tôi không biết nó, nhưng tôi không nghĩ rằng tôi giải quyết vấn đề đúng cách. Giả sử ai đó tạo một ExecutorService bằng phương pháp của bạn và gửi một số nhiệm vụ cho nó, sau đó main()phương thức kết thúc. Sau khi hoàn thành tác vụ , ExecutorService sẽ tồn tại trong keepAliveTime , đợi cho đến khi tất cả các luồng lõi chết. Điều đó có nghĩa là, khi bạn cố gắng dừng ứng dụng java của mình một cách dễ dàng, bạn cần phải đợi một khoảng thời gian (có thể lớn). Tôi không nghĩ nó tốt. @Pshemo Tôi tin rằng câu trả lời của bạn là tốt nhất hiện tại.
Vladimir

1
@VladimirS. Bạn đúng. Hành vi này có được chấp nhận hay không cũng có thể phụ thuộc vào mẫu ứng dụng. Thông thường, tôi cho họ một keepAliveTimevài giây (và phải thừa nhận rằng, khó có thể tưởng tượng khi nào thời gian lớn hơn vài giây là thích hợp hoặc thậm chí là cần thiết). Nếu yêu cầu phải có một lớn hơn keepAliveTimevà vẫn thoát ngay lập tức, giải pháp luồng daemon sẽ thích hợp hơn.
Marco13

1
@ Marco13 Vâng, bạn cũng đúng, đó là một câu hỏi về yêu cầu ứng dụng. Đến lượt tôi, tôi không thể tưởng tượng làm thế nào người ta có thể sử dụng một ExecutorServicevới allowCoreThreadTimeOut(true)keepAliveTimeđủ nhỏ để khiến nó chết nhanh hơn hoặc ít hơn. Lợi ích của việc này là ExecutorServicegì? Sau một thời gian ngắn không hoạt động, tất cả các luồng lõi sẽ chết và việc gửi một nhiệm vụ mới sẽ dẫn đến việc tạo luồng mới, với tất cả chi phí của nó, vì vậy nhiệm vụ của tôi sẽ không bắt đầu ngay lập tức. Nó sẽ không còn là một hồ bơi nữa.
Vladimir

22

Tôi sẽ sử dụng lớp ThreadFactoryBuilder của Guava.

ExecutorService threadPool = Executors.newFixedThreadPool(THREADS, new ThreadFactoryBuilder().setDaemon(true).build());

Nếu bạn chưa sử dụng Guava, tôi sẽ sử dụng lớp con ThreadFactory như được mô tả ở đầu câu trả lời của Pshemo


1
Bạn có thể giải thích sự khác biệt giữa cách tiếp cận từ câu trả lời của bạn và câu trả lời được chấp nhận là gì không? Từ những gì tôi đã kiểm tra trong mã nguồn của ThreadFactoryBuilder, nó sẽ tạo ThreadFactory với hành vi chính xác như được trình bày trong câu trả lời của tôi. Tôi đã bỏ lỡ điều gì đó hay câu trả lời của bạn là cho thấy rằng với Guava, chúng ta có thể rút ngắn mã này một chút? BTW Tôi không nói câu trả lời của bạn là sai, tôi chỉ đang cố gắng hiểu nó tốt hơn.
Pshemo

4
Nó ngắn gọn hơn. Nếu bạn đã sử dụng Guava, tại sao phải viết lại mã tồn tại trong thư viện? Bạn đúng rằng nó làm chính xác những điều tương tự.
Phil Hayward

7

Nếu bạn chỉ muốn sử dụng nó ở một nơi, thì bạn có thể nội dòng java.util.concurrent.ThreadFactorytriển khai, ví dụ: đối với một nhóm có 4 luồng bạn sẽ viết (ví dụ được hiển thị dưới dạng lambda giả sử Java 1.8 hoặc mới hơn):

ExecutorService pool = Executors.newFixedThreadPool(4,
        (Runnable r) -> {
            Thread t = new Thread(r);
            t.setDaemon(true);
            return t;
        }
);

Nhưng tôi thường muốn tất cả các nhà máy Thread của mình tạo ra các luồng daemon, vì vậy tôi thêm một lớp tiện ích như sau:

import java.util.concurrent.ThreadFactory;

public class DaemonThreadFactory implements ThreadFactory {

    public final static ThreadFactory instance = 
                    new DaemonThreadFactory();

    @Override
    public Thread newThread(Runnable r) {
        Thread t = new Thread(r);
        t.setDaemon(true);
        return t;
    }
}

Điều đó cho phép tôi dễ dàng chuyển DaemonThreadFactory.instanceđến ExecutorService, ví dụ:

ExecutorService pool = Executors.newFixedThreadPool(
    4, DaemonThreadFactory.instance
);

hoặc sử dụng nó để dễ dàng bắt đầu một Thread daemon từ một Runnable, ví dụ:

DaemonThreadFactory.instance.newThread(
    () -> { doSomething(); }
).start();

5

Đúng.

Bạn chỉ cần tạo một ThreadFactorylớp của riêng mình để tạo các luồng daemon thay vì các luồng thông thường.


0

Giải pháp này tương tự như @ Marco13 của nhưng thay vì tạo ThreadPoolExecutor , chúng ta có thể sửa đổi giải pháp được trả về Executors#newFixedThreadPool(int nThreads). Đây là cách thực hiện:

ExecutorService ex = Executors.newFixedThreadPool(nThreads);
 if(ex instanceof ThreadPoolExecutor){
    ThreadPoolExecutor tp = (ThreadPoolExecutor) ex;
    tp.setKeepAliveTime(time, timeUnit);
    tp.allowCoreThreadTimeOut(true);
}

1
Một nitpick ở đây là: Bạn không thể chắc chắn rằng newFixedThreadPoolsẽ trả về a ThreadPoolExecutor. (Nó, và gần như chắc chắn sẽ, mãi mãi, nhưng bạn không biết đó)
Marco13

0

Bạn có thể sử dụng ThreadFactoryBuilder của Guava. Tôi không muốn thêm phần phụ thuộc và tôi muốn chức năng từ Executor.DefaultThreadFactory, vì vậy tôi đã sử dụng thành phần:

class DaemonThreadFactory implements ThreadFactory {
    final ThreadFactory delegate;

    DaemonThreadFactory() {
        this(Executors.defaultThreadFactory());
    }

    DaemonThreadFactory(ThreadFactory delegate) {
        this.delegate = delegate;
    }

    @Override
    public Thread newThread(Runnable r) {
        Thread thread = delegate.newThread(r);
        thread.setDaemon(true);
        return thread;
    }
}

-1

Nếu bạn có một danh sách các nhiệm vụ đã biết, bạn không cần các luồng daemon. Bạn có thể chỉ cần gọi shutdown () trên ExecutorService sau khi gửi tất cả các nhiệm vụ của mình.

Khi luồng chính của bạn hoàn tất, hãy sử dụng phương thức awaitTermination () để cho phép thời gian hoàn thành các tác vụ đã gửi. Các tác vụ đã gửi hiện tại sẽ được thực thi và nhóm luồng sẽ chấm dứt luồng điều khiển sau khi chúng đã hoàn thành.

for (Runnable task : tasks) {
  threadPool.submit(task);
}
threadPool.shutdown();
/*... do other stuff ...*/
//All done, ready to exit
while (!threadPool.isTerminated()) {
  //this can throw InterruptedException, you'll need to decide how to deal with that.
  threadPool.awaitTermination(1,TimeUnit.SECOND); 
}

1
Chiến lược này sẽ không làm gián đoạn / dừng các tác vụ theo mô hình 'chạy cho đến khi bị gián đoạn', trong đó các luồng sẽ tiếp tục chạy (vì chúng không phải là daemon và không bị gián đoạn). An toàn hơn nhiều để đặt các luồng thành daemon nếu điều quan trọng là phải thực sự dừng chúng.
Krease
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.