如何在退出前安全地关闭所有线程[重复]

Posted

技术标签:

【中文标题】如何在退出前安全地关闭所有线程[重复]【英文标题】:How to safely close all threads before exit [duplicate] 【发布时间】:2019-02-24 22:21:37 【问题描述】:

我有一个启动几个线程的应用程序,最终一个线程可能需要退出整个应用程序,但是其他线程可能正在执行任务,所以我想让它们在退出之前继续当前循环。

在下面的示例中,Thread2 不知道 Thread1 何时尝试退出,它只是强制一切立即完成。

如何让 Thread2、3 和 4 等在关闭之前完成它们的循环?

编辑:为了解决重复的问题问题:这与父类不能负责操纵关闭的典型情况不同,任何单独的线程都必须能够启动关闭。

Edit2:我还留下了我最终做的答案,这是已接受答案的实现。

class Scratch 
    public static void main(String[] args) 
        Thread Task1 = new Thread(new Task1());
        Task1.start();

        Thread Task2 = new Thread(new Task2());
        Task2.start();

        // ... more threads
    

    public class Task1 implements Runnable 
        public void run() 
            while (true) 
                // ...
                System.exit(0);
                // ...
            
        
    

    public class Task2 implements Runnable 
        public void run() 
            while (true) 
                // ...
                // I need to know about System.exit(0) to exit my loop
                // ...
            
        
    

【问题讨论】:

检查***.com/questions/10961714/… @Kevorkian 这不是我想要的,因为它具有“puppeteer”类型的关系,父类负责关闭应用程序,而我需要任何线程才能要求关闭。 【参考方案1】:

您可以使用所有线程都会不断检查的volatile boolean 变量。如果一个线程将该变量的值设置为false,则所有线程都会看到新值并离开while 循环。

说明volatile 变量中的读/写操作是原子的。除此之外,volatile 变量的值没有被缓存,所以所有线程看到的都是同一个值。

class Scratch 

    private static volatile boolean isRunning = true;

    public static void main(String[] args) 
        Thread Task1 = new Thread(new Task1());
        Task1.start();

        Thread Task2 = new Thread(new Task2());
        Task2.start();

        // ... more threads
    

    public class Task1 implements Runnable 
        public void run() 
            while (isRunning) 
                // ...
                isRunning = false; // let's stop all threads
                // ...
            
        
    

    public class Task2 implements Runnable 
        public void run() 
            while (isRunning) 
                // ...
                // I need to know about System.exit(0) to exit my loop
                // ...
            
        
    

【讨论】:

我认为这个解决方案适用于我的情况,但是由于每个任务可能需要不同的时间才能完成,有没有办法从 main() 检查所有线程是否已完成? 哦,也许我也可以有一个 volatile int,为我创建的每个新线程 +1,完成时为 -1。然后有一个单独的“控制”线程来监控计数和布尔值,当它为假和零时,我们可以调用 System.exit(0); 在这种情况下您必须小心,因为增加/减少 volatile int 变量不是原子的,因为您将有两个单独的操作(一个读取和一个写入)。在这种情况下,您可以使用AtomicInteger 谢谢,我刚刚了解了两者之间的区别。现在这对我来说是一个完整的解决方案。【参考方案2】:

自己创建线程被认为是“不好的做法”,您应该考虑使用 ExecutorService。同步应该通过中断线程来完成。

class Scratch 
    private final ExecutorService executorService;
    public Scratch(ExecutorService es) 
       this.executorService = es;
    
    /** Convience constructor */
    public Scratch() this(Executors.newCachedThreadPool());

    public class Task1 implements Callable<Void> 
        public Void call() throws Exception 
            while(true) 
                ...
                executorService.shutdownNow(); // interrupt all running threads
                                               // (including Task1) started by
                                               // given executor service
                if (Thread.interrupted())
                    throw new InterruptedException();
            
            return null;
        
     

     public class Task2 implements Callable<Void> 
         public Void call() throws Exception 
             while(true) 
                 // check if the thread was interrupted, if so throw Exception
                 if (Thread.interrupted())
                     throw new InterruptedException();
             
             return null;
         
     

     public static void main(String ... args) 
         Scratch s = new Scratch();
         s.executorService.submit(new Task1());
         s.executorService.submit(new Task2());
     
 

【讨论】:

这看起来像是立即杀死线程,是这样吗? 不,这不会杀死线程。根据文档,在 ExecutorService 上调用 shutdownNow 将中断所有当前正在运行的线程(至少在默认实现中),这将设置中断标志)。只有当线程自己检查中断然后抛出异常时,才会发生中断。【参考方案3】:

除了使用 volatile 布尔变量 实现的方法,这是一个很好的方法,您还可以考虑使用 listeners。它们通常在整个 android API 中使用。

首先,创建定义监听器的接口。这是一个例子:

public interface TaskListener 
    public void onFinish();

现在,在您希望收到任务完成通知的类上实现此接口。往下看:

public class Task2 implements Runnable, TaskListener  
    public void run()  
        while (!Thread.currentThread().isInterrupted())  
            //... 
         
    

    public void onFinish() 
         //perform your exit operations here.
         Thread.currentThread().interrupt();
     

现在,准备你的主要任务的类来接收听众。看看:

public class Task1 implements Runnable  
    private ArrayList<TaskListener> listeners = new ArrayList<>();

    public void run()  
        while (true)  
            // ... 
            this.finish();
            // ... 
         
     

    public void addListener (TaskListener listener) 
        this.listeners.add(listener);
     

    private void finish() 
        for(TaskListener listener: this.listeners) 
             listener.onFinish();
         
     
 

设置完所有内容后,现在就可以轻松收听任务的完成情况了。往下看:

public static void main(String[] args)  
    Thread task1 = new Thread(new Task1()); 
    Thread task2 = new Thread(new Task2()); 

    task1.addListener(task2);
    task1.start();
    task2.start();
 

完成!现在,每次调用task1.finish(),它的所有监听器(在这个例子中只有task2)都会被通知(他们的onFinish 方法被调用)。

注意:如果您不想在所有任务中实现TaskListener,则没有必要。下面是一个使用 lambda 表达式的例子:

public static void main(String[] args)  
    Thread task1 = new Thread(new Task1()); 
    Thread task2 = new Thread(new Task2()); 

    task1.addListener(() -> 
        //this entire block will be called when task1 finishes
        task2.interrupt();
    );

    task1.start();
    task2.start();
 

注意2:我在手机上写了这个答案。虽然我修改了几次代码,看起来还不错,但我回家之前无法测试它。

【讨论】:

这个花了我一点时间来消化,我喜欢这种方法,但可能不适合我的用例 - 因为每个新任务都需要我在每个现有任务上回顾性地注册一个新的侦听器。但是,我会记住这一点,因为我可以看到它在我的项目中有很多用途。谢谢。 很高兴你喜欢它!请注意,通过使用 lambda 表达式 示例,您不必在类上实现 TaskListener。因此,只需一个侦听器,您就可以处理所有任务的“完成操作”。干杯!【参考方案4】:

你不能轻易做到这一点。

首先System.exit(0) 退出JVM,它不关心线程。

你需要做什么:

定义您的自己的框架来管理线程 还为您的线程派生“架构”。任何应该由您的框架控制的东西都需要 A) 表示查询“您将运行多长时间?”和 B) “准备下山”

当然:在现实世界中,您的应用程序中可能有许多线程在运行,而您甚至都不知道(例如,当您在某处使用 someCollection.parallelStream() 时:使用 JVM具有不受您控制的线程的宽线程池)。

一些中间立场是“线程管理器”组件。不想被杀死的线程必须向该管理器注册。

但仍然:即使这样也很难做到正确。例如,当某些线程处于死锁状态时会发生什么?或者等待JVM“外部”的事情发生?需要 大量 努力才能确定线程也真正在进行中。

【讨论】:

我的用例比这要少得多,但我接受你提出的观点,并会在我的应用程序增长时考虑它们。目前,它比一个成熟的应用程序更接近我提供的准系统示例。每个线程的时间间隔也比较短,大约3s就可以完成一个循环。【参考方案5】:

正如@HugoTeixeira 所建议的,我最终使用了 volatile 和 atomic-integers 来跟踪线程。我还创建了一个控制线程来定期检查这些值并决定是否该退出。

class Scratch 
    public static volatile boolean isRunning = true;
    public static AtomicInteger threadCount = new AtomicInteger();

    public static void main(String[] args) 
        Thread controlThread = new Thread(new ControlThread());
        controlThread.start();

        Thread Task1 = new Thread(new Task1());
        Task1.start();
        if(Task1.isAlive())  threadCount.incrementAndGet(); 

        Thread Task2 = new Thread(new Task2());
        Task2.start();
        if(Task2.isAlive())  threadCount.incrementAndGet(); 

        // ... more threads
    

任务线程:

public class Task1 implements Runnable 
    public void run() 
        while (isRunning) 
            // ...
            isRunning = false; // let's stop all threads
            // ...
        
     
  

控制线程:

import static Scratch.isRunning;
import static Scratch.threadCount;

public class ControlThread implements Runnable 
    public void run() 
        while(true) 
            try 
                // Check if we have stopped running the app AND all threads have finished up.
                if(!isRunning && threadCount.get() == 0) 
                    // Safely close the app
                    System.exit(0);
                
                Thread.sleep(5000);
             catch (InterruptedException e) 
                // Ignore exceptions
            
        
    

【讨论】:

很高兴你能成功!您是否使用侦听器检查过我的方法?它也可以派上用场。【参考方案6】:

如果打算等待所有Thread 完成执行。考虑使用ExecutorServiceFuture。下面的 Sketch 代表了实现它的总体思路。

final ExecutorService pool = Executors.newCachedThreadPool();

Future<?> task1 = pool.submit(() -> 
    //do loop task1
);

Future<?> task2 = pool.submit(() -> 
    //do loop task2
);

while(!task1.isDone() && !task2.isDone())
   //waits to complete

所有任务完成后,使用pool.shutdown() 关闭执行器。然而,这只是一个简单的例子。更多详情请参考ExecutorServicejavadoc。

https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ExecutorService.html

【讨论】:

以上是关于如何在退出前安全地关闭所有线程[重复]的主要内容,如果未能解决你的问题,请参考以下文章

如何在退出前重新附加线程或等待线程完成

主线程创建了子线程,怎么让主线程退出,而子线程仍然运行

Android Studio - 如何(关闭,退出,退出,杀死等)应用程序[重复]

如何退出所有正在运行的线程?

c#关闭窗口怎么强制退出所有运行的线程

C# 使用多线程,在关闭窗体时 怎么关闭窗体的所有线程,使程序退出