【问题标题】:How do I make a single-task FILO background thread?如何制作单任务 FILO 后台线程?
【发布时间】:2021-07-30 22:19:34
【问题描述】:

我有一堆有点随意产生的线程。当他们互相比赛时,只有最后产生的那个才是相关的。其他线程可以被丢弃或停止。但我不知道该怎么做,所以我实现了一个非常基本的计数器来检查线程是否是最新生成的线程。

编辑:我希望能够杀死花费太长时间的线程(因为它们不再需要);可能不是来自线程本身,因为它们正忙于做其他事情。

这个代码似乎有效。但是感觉不是很健壮。有人可以给我一个提示吗?

class Main {
    private static volatile int latestThread = 0;

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            spawnThread();
        }
    }

    private static void spawnThread() {
        latestThread++;
        int thisThread = latestThread;
        new Thread(() -> {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (latestThread == thisThread) {
                // only the latest "active" thread is relevant
                System.out.println("I am the latest thread! " + thisThread);
            }
        }).start();
    }
}

输出:

I am the latest thread! 10

code in replit.com

【问题讨论】:

  • 如果其中只有一个是相关的,为什么还要启动所有这些线程?尤其是对于这类问题——你正在寻找“正确”的方式来做某事——提供足够的信息让我们能够理解你真正想要做什么是很重要的,因为这将有助于确定“正确”的做法是什么。
  • 这不是线程安全的,除非您在 latestThread 变量上添加 volatile 关键字。
  • 顺便说一句,不能保证if (latestThread == thisThread) 行实际上会看到latestThread 的当前值,因为latestThread 将由不同的线程设置,不会发生-在大多数情况下,在关系之前。所以理论上有可能一个线程认为它是最新的线程,即使它真的不是。
  • @vakio:与其每次用户交互时都生成一个新线程,不如将任务提交给 ThreadPoolExecutor 更合适?
  • 首先,将对新线程的引用保存到 AtomicReference 或 ConcurrentHashMap 以便在必要的检查后中断它。

标签: java multithreading threadpool


【解决方案1】:

ThreadPoolExecutor 几乎是我所需要的,特别是 DiscardOldestPolicy。您可以将队列大小设置为 1,这样一个线程正在运行,一个线程在队列中,而队列中最旧的线程将被分流。干净!

但它完成了两个线程(不仅是最新的),这不是我想要的 100%。虽然可以说足够好:

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

public class DiscardOldest {

    private static int threadCounter = 1;

    public static void main(String[] args) throws InterruptedException {
        int poolSize = 0;
        int maxPoolSize = 1;
        int queueSize = 1;
        long aliveTime = 1000;
        ArrayBlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(queueSize);
        ThreadPoolExecutor executor = new ThreadPoolExecutor(poolSize, maxPoolSize, aliveTime, TimeUnit.MILLISECONDS, queue, new ThreadPoolExecutor.DiscardOldestPolicy());

        for (int i = 0; i < 4; i++) {
            spawnThread(executor);
        }
    }

    private static void spawnThread(ThreadPoolExecutor executor) {
      final int thisThread = threadCounter++;
      System.out.println(thisThread + " spawning");
        executor.execute(() -> {
            try { 
                Thread.sleep(100);
                System.out.println(thisThread + " finished!");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
    }
}

输出:

1 spawning
2 spawning
3 spawning
4 spawning
1 finished!
4 finished!

【讨论】:

    【解决方案2】:

    可以设置出生时间,而不是根据索引进行中继。如果有一个更年轻的线程(出生较晚),该线程应该终止它的执行。

    public class Last {
        private static volatile long latestThread = 0L;
    
        /**
         * @param args
         */
        public static void main(String[] args) {
            for (int i = 0; i < 3; i++) {
                spawnThread(System.nanoTime(), i);
            }
        }
    
        private static void spawnThread(long startTime, int index) {
            new Thread(() -> {
                latestThread = startTime;
                long thisThread = startTime;
                boolean die = false;
                try {
                    while (!die) {
                        Thread.sleep(1);
                        if (thisThread < latestThread) {
                            System.out.println(
                                    index + ": I am not the latest thread :-(\n\t" + thisThread + "\n\t" + latestThread);
                            die = true;
                        } else if (thisThread == latestThread) {
                            System.out.println(
                                    index + ": Yes! This is the latest thread!\n\t" + thisThread + "\n\t" + latestThread);
                            Thread.sleep(1);
                            System.out.println("Bye!");
                            die = true;
                        }
                    }
    
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
    

    结果:

    0: I am not the latest thread :-(
        39667589567880
        39667602317461
    2: Yes! This is the latest thread!
        39667602317461
        39667602317461
    1: I am not the latest thread :-(
        39667602257160
        39667602317461
    Bye!
    

    【讨论】:

    • 这绝对比我现在的要好。这真有趣。我没有在我的问题中明确说明它,但我想我也在寻找一种从外部杀死线程的方法。例如,如果它花费的时间太长(如果 Thread.sleep 更长),它不能杀死自己。
    【解决方案3】:

    我根据每个人的 cmets 做了一些研究(谢谢!),ThreadPoolExecutor几乎是我需要的,但我想要一个总大小为 1 的池(没有队列) 一旦新线程出现就会杀死活动线程,这在线程池中是不允许的,也不符合 ThreadPool 的用途。所以相反,我想出了一个对活动线程的引用,当一个新线程出现很长时间时,它会杀死旧线程,这似乎可以满足我的要求:

    import java.util.concurrent.atomic.AtomicInteger;
    
    public class Interrupt {
    
        private static final AtomicInteger CURRENT_THREAD = new AtomicInteger(0);
        private static Thread activeThread = new Thread(() -> {});
    
        public static void main(String[] args) throws InterruptedException {
            for (int i = 0; i < 4; i++) {
                spawnThread();
                Thread.sleep(3);
            }
        }
    
        private static void spawnThread() {
            if (activeThread.isAlive()) {
                activeThread.interrupt();
            }
            activeThread = new Thread(() -> {
                int thisThread = CURRENT_THREAD.incrementAndGet();
                System.out.println(thisThread + " working");
                try {
                    Thread.sleep(1000);
                    System.out.println(thisThread + " finished!");
                } catch (InterruptedException ignored) {}
            });
            activeThread.start();
        }
    }
    

    输出:

    3 working
    2 working
    1 working
    4 working
    4 finished!
    

    【讨论】:

    • 如果您只想要最多 1 个线程,您确定要每次都生成一个新线程吗?
    • 嗯,必须执行一个任务,而不是在主线程上。因为主线程是GUI。因此,如果我没有生成线程,我将不得不以某种方式将代码注入到一个后台线程中并继续运行......但这似乎更复杂(?)
    • 听起来你基本上想要的是一个任务队列和一个从队列中取出任务并执行它们的单个线程。阅读 Executor 框架并查看 Executors.newSingleThreadedExecutor() 方法。
    • 是的,这似乎是做同样事情的一种更简洁的方式.. 类似future = executor.submit(task) 然后当有新任务出现时:future.cancel(true) 并提交新任务。我认为这是要走的路。谢谢!
    • 我还有另一个问题是用户任务无法被中断(例如,如果用户错误地进行了无限循环),这就是为什么我首先使用多个线程的原因。但这是另一个问题,可能可以使用 ProcessBuilder 并强制终止进程来解决(只有 Windows 似乎不想终止进程)。
    猜你喜欢
    • 2020-11-07
    • 2012-07-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-02-05
    • 2011-06-17
    相关资源
    最近更新 更多