java - 执行器暂停并且不处理待处理队列

标签 java android multithreading

我有一个长期运行的任务,即创建位图、保存它并重新创建更多位图,这是我在单个后台线程上执行的操作

ExecutorService executor = Executors.newSingleThreadExecutor();
Handler handler = new Handler(Looper.getMainLooper());
executor.execute(() -> {...bitmap work ...}

但是处理所有位图需要很长时间,因此我创建了一个线程池来使用多线程来加速任务。

private final int cores = Runtime.getRuntime().availableProcessors();
private final ExecutorService executor = Executors.newFixedThreadPool(cores + 1);
for (int i = 0; i < totalPage; i++) {    
    Runnable runnable = () -> {...bitmap work ...}
    executor.submit(runnable);
}

但是每当我使用超过 1 个线程时,它就会随机卡在某些任务上(例如 127 个任务中的 7 个),没有错误或任何其他任务,它只是不再处理任何任务。我可以查看执行程序队列中的待处理任务。但如果我将线程池更改为使用 1 个线程,它就可以正常工作并处理所有任务。

这是完整的实际代码

ExecutorService executor = Executors.newFixedThreadPool(cores + 1);
        List<Future<?>> futureList = new ArrayList<>();
        boolean allDone = false;

        try {
            //Convert pdf to Bitmap
            ParcelFileDescriptor parcelFileDescriptor = ParcelFileDescriptor.open(new File(pdfFileName), ParcelFileDescriptor.MODE_READ_ONLY);
            PdfRenderer pdfRenderer = new PdfRenderer(parcelFileDescriptor);
            int totalPage = pdfRenderer.getPageCount();

            final int[] counter = {1};
            for (int i = 0; i < totalPage; i++) {
                int finalI = i;
                String finalOriginalPdfName = originalPdfName;
                String finalGeneratedPdfName = generatedPdfName;
                Runnable runnable = () -> {
                    //pd.setMessage("Processing page " + (finalI + 1) + " of " + totalPage);
                    PdfRenderer.Page page = pdfRenderer.openPage(finalI);

                    Bitmap pageBitmap = Bitmap.createBitmap((300 * page.getWidth()) / 72, (300 * page.getHeight()) / 72, Bitmap.Config.ARGB_8888);
                    Canvas canvas = new Canvas(pageBitmap);
                    canvas.drawColor(Color.WHITE);
                    canvas.drawBitmap(pageBitmap, 0, 0, null);
                    page.render(pageBitmap, null, null, PdfRenderer.Page.RENDER_MODE_FOR_PRINT);
                    page.close();


                    //Crop bitmaps and temporarily store on app data directory

                    for (int k = 0; k < SlipBoundingBox.Y.length; k++) {
                        for (int j = 0; j < SlipBoundingBox.X.length; j++) {
                            Bitmap slipBitmap = Bitmap.createBitmap(pageBitmap, SlipBoundingBox.X[j], SlipBoundingBox.Y[k], SlipBoundingBox.WIDTH, SlipBoundingBox.HEIGHT);
                            //Filename formation originalPdfName_generatePdfName_pdfPageIndex_x_y.extension
                            File slip = new File(
                                    getExternalFilesDir("slips")
                                            + "/"
                                            + finalOriginalPdfName
                                            + "_"
                                            + finalGeneratedPdfName
                                            + "_"
                                            + finalI +
                                            "_"
                                            + SlipBoundingBox.X[j]
                                            + "_"
                                            + SlipBoundingBox.Y[k]
                                            + "_.jpg");
                            try (FileOutputStream out1 = new FileOutputStream(slip)) {
                                slipBitmap.compress(Bitmap.CompressFormat.JPEG, 100, out1);
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                            slipBitmap.recycle();
                        }
                    }
                    pageBitmap.recycle();
                    pd.setMessage("Processed " + counter[0] + " of " + totalPage + " pages");
                    counter[0]++;
                };
                Future<?> future = executor.submit(runnable);
                Log.d(TAG, "processPdf: " + future.isDone());
                futureList.add(future);

            }
            //Todo close pdfrender on all page processed
            //pdfRenderer.close();

        } catch (Exception e) {
            e.printStackTrace();
        }

最佳答案

非常抱歉我无法直接帮助您。我不知道 PdfRenderer 的内部工作原理。我认为三件事可能会造成问题:

  1. PdfRenderer 在其库内的某处出现死锁。不太可能。

  2. 您的文件 I/O 非常复杂,并行写入多个文件只需花费 100 倍的时间。还记得旧的光盘 (CD) 吗?当您以全速读取一个源时,一旦读取 2 个以上进程,它就会呈指数级缓慢(不是快一半,而是 2 个进程慢 20 倍)。我认为这是最有可能的原因。

  3. PdfRenderer 内部深处发生 I/O 错误。也许它打开了太多文件句柄或其他什么原因,导致意外崩溃,从而使执行停止在原处。

所以我唯一能给你的是:分析是否有任何进程有效(至少是缓慢地)。可能有一些框架,但我不知道它们。还有很多工具可以在运行时分析 JVM。这是一个更简单的解决方案:

检查每个线程的状态,以及它们是否更改了方法。现在这纯粹是视觉上的:

  • 查看/使用下面的代码。按原样运行
  • 您将看到每个线程不时更改任务/状态
  • 如果您喜欢,请将我的文件添加到您的项目中。
  • 调整您的代码(请参阅我的 main() 方法)
  • 将池大小减少/限制为 3 或 4
  • 控制台输出将直观地向您显示任何线程中是否有任何更改。
  • 如果根本没有任何更改,则 pdf 库会卡住
  • 如果有重复的更改,但长期没有任何进展,lib 就会陷入死亡循环

代码:

package stackoverflow;

import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class SimpleThreadpoolAnalysis {

    static private final long sStartMS = System.currentTimeMillis();

    static public long now() {
        return System.currentTimeMillis() - sStartMS;
    }

    static public void sleep(final long pMS) {
        try {
            Thread.sleep(pMS);
        } catch (final InterruptedException e) { /* */ }
    }



    private final Set<Thread> mWorkingThreads = Collections.synchronizedSet(new HashSet<>());

    private int mPrintStackDepth = 5;

    public void setPrintStackDepth(final int pPrintStackDepth) {
        mPrintStackDepth = pPrintStackDepth;
    }

    public void runChecked(final Runnable pLambda) {
        if (pLambda == null) return;

        final Thread currentThread = Thread.currentThread();
        try {
            System.out.println("SimpleThreadpoolAnalysis.runChecked() adding thread " + currentThread.getName());
            mWorkingThreads.add(currentThread);
            pLambda.run();
        } finally {
            System.out.println("SimpleThreadpoolAnalysis.runChecked() removing thread " + currentThread.getName());
            mWorkingThreads.remove(currentThread);
        }
    }

    public void printMiniStackTraces() {
        System.out.println("Working Threads at " + now());

        for (final Thread t : mWorkingThreads) {
            System.out.println("\tThread " + t.getId() + ": " + t.getName());
            final StackTraceElement[] st = t.getStackTrace();
            for (int i = 0; i < Math.min(st.length, mPrintStackDepth); i++) {
                System.out.println("\t\t" + st[i]);
            }
        }
    }

    public Thread runSupervisorThread(final int pUpdateEveryXMs, final long pDurationMS) {
        System.out.println("Supervisor Thread starting...");
        final Thread t = new Thread(() -> runSupervisorThread_(pUpdateEveryXMs, pDurationMS), "Pool Supervisor Thread");
        t.setDaemon(true);
        t.start();
        return t;
    }
    private void runSupervisorThread_(final int pUpdateEveryXMs, final long pDurationMS) {
        System.out.println("Supervisor Thread starting...");
        final int NUMBER_OF_RUNS = (int) (pDurationMS / pUpdateEveryXMs);
        for (int i = 0; i < NUMBER_OF_RUNS; i++) {
            System.out.flush();
            sleep(pUpdateEveryXMs);
            printMiniStackTraces();
        }
        System.out.println("Supervisor Thread ending...");
    }



    // this is my dummy class. you can remove this once you use it on your code
    static public class TestWorker implements Runnable {
        @Override public void run() {
            while (true) {
                //              final int no =
                subDelegator();
                //              System.out.println("Got  " + no);
            }
        }

        private int subDelegator() {
            SimpleThreadpoolAnalysis.sleep((long) (Math.random() * 1000));

            final int randomIndex = (int) (Math.random() * 10);
            switch (randomIndex) {
                case 0:
                    return run_0();
                case 1:
                    return run_1();
                case 2:
                    return run_2();
                case 3:
                    return run_3();
                case 4:
                    return run_4();
                case 5:
                    return run_5();
                default:
                    return -1;
            }
        }
        private int run_0() {
            SimpleThreadpoolAnalysis.sleep(500);
            return 0;
        }
        private int run_1() {
            SimpleThreadpoolAnalysis.sleep(1000);
            return 1;
        }
        private int run_2() {
            SimpleThreadpoolAnalysis.sleep(2000);
            return 2;
        }
        private int run_3() {
            SimpleThreadpoolAnalysis.sleep(3000);
            return 3;
        }
        private int run_4() {
            SimpleThreadpoolAnalysis.sleep(4000);
            return 4;
        }
        private int run_5() {
            SimpleThreadpoolAnalysis.sleep(5000);
            return 5;
        }
    }



    public static void main(final String[] args) {
        final SimpleThreadpoolAnalysis sta = new SimpleThreadpoolAnalysis();
        sta.runSupervisorThread(100, 60000); // will run for a minute, updating every 100ms
        // this will run a in background thread, so if other threads are done, this will end automatically, too

        final int cores = Runtime.getRuntime().availableProcessors();
        final ExecutorService executor = Executors.newFixedThreadPool(cores + 1);
        final int totalPages = 10;
        for (int i = 0; i < totalPages; i++) {
            // my code:
            //          final Runnable runnable = new TestWorker(); // this would be the normal call
            final Runnable runnable = new TestWorker(); // this is the checked version

            // your code: enable this, comment out my line above
            //final Runnable runnable = () -> { /* your bitmap work */ }; // use this, just like in your code

            final Runnable checkedRunnable = () -> sta.runChecked(runnable); // this is the checked version
            final Future<?> future = executor.submit(checkedRunnable);
            // ... some more of your code ...
        }

    }



}

希望这对你有一点帮助。

关于java - 执行器暂停并且不处理待处理队列,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/72241859/

相关文章:

java - 设置 zest 图形节点和连接的样式

java - 对 newSingleThreadScheduledExecutor 使用 ScheduleAtFixedRate 和 Schedule 方法

android - 如何将 android.util.Log 与 crashlytics 匹配?

android - 我的应用被 Google Play 拒绝 "APK REQUIRES PROMINENT DISCLOSURE"

multithreading - 重新启动在整个应用程序生命周期中存在的 Delphi TThread

java - java 8 是否有与antlr4 一起使用的编译器代码?

java - 如何使用多线程生成数据?

java - Android应用程序,如何登录网站并显示信息?

ios - 如何快速取消多个线程之一

c - 以下 C 函数是线程安全的吗?