กลุ่มเธรดที่กำหนดเองใน Java 8 กระแสขนาน


398

เป็นไปได้หรือไม่ที่จะระบุเธรดพูลที่กำหนดเองสำหรับ Java 8 stream แบบขนาน ? ฉันไม่สามารถหาได้ทุกที่

ลองนึกภาพว่าฉันมีเซิร์ฟเวอร์แอปพลิเคชันและฉันต้องการใช้กระแสข้อมูลแบบขนาน แต่แอปพลิเคชั่นนั้นมีขนาดใหญ่และมีหลายเธรดดังนั้นฉันจึงต้องการแบ่งย่อยมัน ฉันไม่ต้องการให้งานที่รันช้าในหนึ่งโมดูลของงาน applicationblock จากโมดูลอื่น

ถ้าฉันไม่สามารถใช้เธรดพูลที่แตกต่างกันสำหรับโมดูลที่แตกต่างกันก็หมายความว่าฉันไม่สามารถใช้สตรีมแบบขนานได้อย่างปลอดภัยในสถานการณ์จริงส่วนใหญ่

ลองตัวอย่างต่อไปนี้ มีงานที่ใช้งาน CPU มากในหัวข้อแยกต่างหาก งานยกระดับกระแสขนาน ภารกิจแรกถูกทำลายดังนั้นแต่ละขั้นตอนใช้เวลา 1 วินาที (จำลองโดยการนอนหลับของเธรด) ปัญหาคือเธรดอื่นค้างและรอให้งานที่เสียหายเสร็จสิ้น นี่เป็นตัวอย่างที่วางแผนไว้ แต่ลองนึกภาพแอป servlet และมีคนส่งงานที่ใช้เวลานานไปยังกลุ่มการเข้าร่วมส้อมที่ใช้ร่วมกัน

public class ParallelTest {
    public static void main(String[] args) throws InterruptedException {
        ExecutorService es = Executors.newCachedThreadPool();

        es.execute(() -> runTask(1000)); //incorrect task
        es.execute(() -> runTask(0));
        es.execute(() -> runTask(0));
        es.execute(() -> runTask(0));
        es.execute(() -> runTask(0));
        es.execute(() -> runTask(0));


        es.shutdown();
        es.awaitTermination(60, TimeUnit.SECONDS);
    }

    private static void runTask(int delay) {
        range(1, 1_000_000).parallel().filter(ParallelTest::isPrime).peek(i -> Utils.sleep(delay)).max()
                .ifPresent(max -> System.out.println(Thread.currentThread() + " " + max));
    }

    public static boolean isPrime(long n) {
        return n > 1 && rangeClosed(2, (long) sqrt(n)).noneMatch(divisor -> n % divisor == 0);
    }
}

3
คุณหมายถึงอะไรโดยกลุ่มเธรดที่กำหนดเอง? มี ForkJoinPool ที่พบบ่อยครั้งเดียว แต่คุณสามารถสร้าง ForkJoinPool ของคุณเองและส่งคำขอไปยังมันได้เสมอ
ปรับปรุงเมื่อ

7
คำแนะนำ: Java Champion Heinz Kabutz ตรวจสอบปัญหาเดียวกัน แต่มีผลกระทบที่เลวร้ายยิ่งขึ้น: เธรดที่ล็อกของกลุ่มร่วมส้อมทั่วไป ดูjavaspecialists.eu/archive/Issue223.html
Peti

คำตอบ:


395

มีเคล็ดลับวิธีการดำเนินการขนานในพูล fork-join เฉพาะ หากคุณเรียกใช้งานเป็นงานในกลุ่ม fork-join มันจะอยู่ที่นั่นและไม่ได้ใช้งานทั่วไป

final int parallelism = 4;
ForkJoinPool forkJoinPool = null;
try {
    forkJoinPool = new ForkJoinPool(parallelism);
    final List<Integer> primes = forkJoinPool.submit(() ->
        // Parallel task here, for example
        IntStream.range(1, 1_000_000).parallel()
                .filter(PrimesPrint::isPrime)
                .boxed().collect(Collectors.toList())
    ).get();
    System.out.println(primes);
} catch (InterruptedException | ExecutionException e) {
    throw new RuntimeException(e);
} finally {
    if (forkJoinPool != null) {
        forkJoinPool.shutdown();
    }
}

เคล็ดลับขึ้นอยู่กับForkJoinTask.forkซึ่งระบุ: "จัดเรียงแบบอะซิงโครนัสเพื่อเรียกใช้งานนี้ในพูลงานปัจจุบันที่กำลังทำงานอยู่หากเหมาะสมหรือใช้ ForkJoinPool.commonPool () หากไม่ใช่ inForkJoinPool ()"


20
รายละเอียดเกี่ยวกับการแก้ปัญหามีอธิบายไว้ที่นี่blog.krecan.net/2014/03/18/…
Lukas

3
แต่มันยังระบุด้วยว่าสตรีมใช้ForkJoinPoolหรือเป็นรายละเอียดการนำไปปฏิบัติหรือไม่ ลิงค์ไปยังเอกสารจะดี
Nicolai

6
@ Lukas ขอบคุณสำหรับตัวอย่าง ฉันจะเพิ่มว่าForkJoinPoolอินสแตนซ์ควรshutdown()เมื่อไม่จำเป็นอีกต่อไปเพื่อหลีกเลี่ยงการรั่วไหลของเธรด (ตัวอย่าง)
jck

5
โปรดทราบว่ามีข้อบกพร่องใน Java 8 ที่แม้ว่างานจะทำงานบนอินสแตนซ์พูลที่กำหนดเองพวกเขายังคงเชื่อมต่อกับพูลที่ใช้ร่วมกัน: ขนาดของการคำนวณยังคงเป็นสัดส่วนกับพูลทั่วไปและไม่ใช่พูลที่กำหนดเอง ได้รับการแก้ไขใน Java 10: JDK-8190974
Terran

3
@terran ปัญหานี้ได้รับการแก้ไขแล้วสำหรับ Java 8 bugs.openjdk.java.net/browse/JDK-8224620
Cutberto Ocampo

192

สตรีมขนานใช้ค่าเริ่มต้นForkJoinPool.commonPoolซึ่งมีค่าเริ่มต้นน้อยกว่าหนึ่งเธรดในขณะที่คุณมีตัวประมวลผลตามที่ส่งคืนโดยRuntime.getRuntime().availableProcessors()(หมายความว่าสตรีมแบบขนานใช้ตัวประมวลผลทั้งหมดของคุณเพราะพวกเขาใช้เธรดหลักด้วย):

สำหรับแอปพลิเคชั่นที่ต้องการพูลแยกหรือกำหนดเองอาจสร้าง ForkJoinPool ด้วยระดับความขนานเป้าหมายที่กำหนด โดยค่าเริ่มต้นเท่ากับจำนวนตัวประมวลผลที่มีอยู่

นี่หมายความว่าหากคุณมีสตรีมแบบขนานที่ซ้อนกันหรือสตรีมแบบขนานหลายรายการเริ่มพร้อมกันพวกเขาทั้งหมดจะแชร์พูลเดียวกัน ข้อได้เปรียบ: คุณจะไม่ใช้เกินกว่าค่าเริ่มต้น (จำนวนโปรเซสเซอร์ที่มีอยู่) ข้อเสีย: คุณอาจไม่ได้รับ "ตัวประมวลผลทั้งหมด" ที่กำหนดให้กับสตรีมแบบขนานแต่ละรายการที่คุณเริ่มต้น (หากคุณมีมากกว่าหนึ่งรายการ) (เห็นได้ชัดว่าคุณสามารถใช้ManagedBlockerเพื่อหลีกเลี่ยงปัญหานี้)

หากต้องการเปลี่ยนวิธีการดำเนินการสตรีมแบบขนานคุณสามารถทำได้

  • ส่งการดำเนินการสตรีมขนานไปยัง ForkJoinPool ของคุณ: yourFJP.submit(() -> stream.parallel().forEach(soSomething)).get();หรือ
  • คุณสามารถเปลี่ยนขนาดของพูลทั่วไปโดยใช้คุณสมบัติของระบบ: System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism", "20")สำหรับการขนานเป้าหมายของ 20 เธรด แต่นี้ไม่ได้หลังจากที่แพทช์ backported https://bugs.openjdk.java.net/browse/JDK-8190974

ตัวอย่างของหลังบนเครื่องของฉันซึ่งมีโปรเซสเซอร์ 8 ตัว ถ้าฉันรันโปรแกรมต่อไปนี้:

long start = System.currentTimeMillis();
IntStream s = IntStream.range(0, 20);
//System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism", "20");
s.parallel().forEach(i -> {
    try { Thread.sleep(100); } catch (Exception ignore) {}
    System.out.print((System.currentTimeMillis() - start) + " ");
});

ผลลัพธ์คือ:

215 216 216 216 216 216 216 216 316 316 316 316 316 316 316 415 416 416 416

ดังนั้นคุณจะเห็นได้ว่ากระแสข้อมูลแบบขนานประมวลผล 8 รายการต่อครั้งนั่นคือใช้ 8 เธรด อย่างไรก็ตามถ้าฉันไม่ใส่เครื่องหมายข้อคิดเห็นบรรทัดจะแสดงผล:

215 215 215 215 215 216 216 216 216 216 216 216 216 216 216 216 216 216 216 216

เวลานี้สตรีมขนานได้ใช้ 20 เธรดและองค์ประกอบทั้งหมด 20 รายการในสตรีมได้รับการประมวลผลพร้อมกัน


30
commonPoolมีจริงหนึ่งน้อยกว่าavailableProcessorsที่เกิดในความเท่าเทียมรวมเท่ากับavailableProcessorsเพราะโทรนับเป็นหนึ่งในหัวข้อ
Marko Topolnik

2
ForkJoinTaskส่งกลับมา เพื่อเลียนแบบparallel() get()เป็นสิ่งจำเป็น:stream.parallel().forEach(soSomething)).get();
กริกอ Kislin

5
ผมไม่เชื่อว่าจะทำงานการกระทำของฉันกับกระแสที่ได้รับForkJoinPool.submit(() -> stream.forEach(...)) ForkJoinPoolฉันคาดหวังว่าการดำเนินการทั้งหมดของสตรีมจะดำเนินการใน ForJoinPool เป็นการกระทำเดียว แต่ภายในยังคงใช้ ForkJoinPool ที่เป็นค่าเริ่มต้น / ทั่วไป คุณเห็นที่ไหนว่า ForkJoinPool.submit () จะทำในสิ่งที่คุณพูด
Frederic Leitenberger

@FredericLeitenberger คุณอาจต้องการแสดงความคิดเห็นของคุณด้านล่างคำตอบของ Lukas
assylias

2
ฉันเห็นตอนนี้stackoverflow.com/a/34930831/1520422แสดงให้เห็นว่ามันใช้งานได้จริงตามที่ประกาศไว้ แต่ฉันก็ยังไม่เข้าใจว่ามันใช้งานได้อย่างไร แต่ฉันสบายดีกับ "ใช้งานได้" ขอบคุณ!
Frederic Leitenberger

39

อีกทางเลือกหนึ่งในการหลอกลวงการคำนวณแบบขนานภายใน forkJoinPool ของคุณเองคุณยังสามารถส่งพูลนั้นไปยังเมธอด CompletableFuture.supplyAsync เหมือนใน:

ForkJoinPool forkJoinPool = new ForkJoinPool(2);
CompletableFuture<List<Integer>> primes = CompletableFuture.supplyAsync(() ->
    //parallel task here, for example
    range(1, 1_000_000).parallel().filter(PrimesPrint::isPrime).collect(toList()), 
    forkJoinPool
);

22

โซลูชันดั้งเดิม (การตั้งค่าคุณสมบัติการขนานทั่วไปของ ForkJoinPool) ไม่ทำงานอีกต่อไป เมื่อมองไปที่ลิงก์ในคำตอบดั้งเดิมการอัปเดตที่ส่งกลับไปยัง Java 8 นั้นดังที่กล่าวไว้ในเธรดที่เชื่อมโยงโซลูชันนี้ไม่ได้รับประกันว่าจะทำงานได้ตลอดไป ตามนั้นการแก้ปัญหาคือ forkjoinpool.submit กับการแก้ปัญหา. get กล่าวถึงในคำตอบที่ได้รับการยอมรับ ฉันคิดว่า backport แก้ไขความไม่น่าเชื่อถือของโซลูชันนี้ด้วย

ForkJoinPool fjpool = new ForkJoinPool(10);
System.out.println("stream.parallel");
IntStream range = IntStream.range(0, 20);
fjpool.submit(() -> range.parallel()
        .forEach((int theInt) ->
        {
            try { Thread.sleep(100); } catch (Exception ignore) {}
            System.out.println(Thread.currentThread().getName() + " -- " + theInt);
        })).get();
System.out.println("list.parallelStream");
int [] array = IntStream.range(0, 20).toArray();
List<Integer> list = new ArrayList<>();
for (int theInt: array)
{
    list.add(theInt);
}
fjpool.submit(() -> list.parallelStream()
        .forEach((theInt) ->
        {
            try { Thread.sleep(100); } catch (Exception ignore) {}
            System.out.println(Thread.currentThread().getName() + " -- " + theInt);
        })).get();

ฉันไม่เห็นการเปลี่ยนแปลงแบบขนานเมื่อฉันทำForkJoinPool.commonPool().getParallelism()ในโหมดดีบัก
d-coder

ขอบคุณ ฉันทำการทดสอบ / วิจัยและอัปเดตคำตอบ ดูเหมือนว่าการอัปเดตจะเปลี่ยนไปเพราะทำงานในเวอร์ชั่นที่เก่ากว่า
Tod Casasent

ทำไมฉันถึงได้รับสิ่งนี้: unreported exception InterruptedException; must be caught or declared to be thrownแม้จะมีcatchข้อยกเว้นทั้งหมดในลูป
Rocky Li

ร็อคกี้ฉันไม่เห็นข้อผิดพลาดใด ๆ การรู้จักเวอร์ชัน Java และบรรทัดที่แม่นยำจะช่วยได้ "InterruptedException" แสดงว่าการลอง / จับรอบ ๆ โหมดสลีปนั้นไม่ได้ปิดอย่างถูกต้องในเวอร์ชั่นของคุณ
Tod Casasent

13

เราสามารถเปลี่ยนขนานเริ่มต้นโดยใช้คุณสมบัติดังต่อไปนี้:

-Djava.util.concurrent.ForkJoinPool.common.parallelism=16

ซึ่งสามารถตั้งค่าให้ใช้การขนานมากขึ้น


แม้ว่าจะเป็นการตั้งค่าระดับโลก แต่ก็ทำงานเพื่อเพิ่ม parallelStream
meadlai

สิ่งนี้ใช้ได้กับฉันในเวอร์ชัน openjdk "1.8.0_222"
abbas

คนเดียวกับข้างต้นนี่ไม่ทำงานสำหรับฉันใน openjdk "11.0.6"
abbas

8

ในการวัดจำนวนเธรดที่ใช้จริงคุณสามารถตรวจสอบThread.activeCount():

    Runnable r = () -> IntStream
            .range(-42, +42)
            .parallel()
            .map(i -> Thread.activeCount())
            .max()
            .ifPresent(System.out::println);

    ForkJoinPool.commonPool().submit(r).join();
    new ForkJoinPool(42).submit(r).join();

สิ่งนี้สามารถผลิตบน CPU แบบ 4 คอร์และเอาต์พุตเช่น:

5 // common pool
23 // custom pool

โดยไม่.parallel()ได้ให้:

3 // common pool
4 // custom pool

6
Thread.activeCount () ไม่ได้บอกคุณว่าเธรดกำลังประมวลผลสตรีมของคุณ แผนที่เพื่อ Thread.currentThread (). getName () แทนตามด้วยชัดเจน () จากนั้นคุณจะรู้ว่าจะไม่ใช้ทุกเธรดในพูล ... เพิ่มความล่าช้าในการประมวลผลของคุณและเธรดทั้งหมดในพูลจะถูกใช้
keyoxy

7

จนถึงตอนนี้ฉันใช้วิธีแก้ปัญหาที่อธิบายไว้ในคำตอบของคำถามนี้ ตอนนี้ฉันมาพร้อมกับห้องสมุดเล็ก ๆ ชื่อว่าParallel Stream Supportสำหรับสิ่งนั้น:

ForkJoinPool pool = new ForkJoinPool(NR_OF_THREADS);
ParallelIntStreamSupport.range(1, 1_000_000, pool)
    .filter(PrimesPrint::isPrime)
    .collect(toList())

แต่เนื่องจาก @PabloMatiasGomez ชี้ให้เห็นในความคิดเห็นมีข้อบกพร่องเกี่ยวกับกลไกการแยกของลำธารขนานซึ่งขึ้นอยู่กับขนาดของสระว่ายน้ำทั่วไป ดูกระแสขนานจาก HashSet ไม่ได้ทำงานแบบขนาน

ฉันใช้โซลูชันนี้เพื่อแยกพูลสำหรับงานประเภทต่าง ๆ เท่านั้น แต่ฉันไม่สามารถกำหนดขนาดของพูลทั่วไปเป็น 1 แม้ว่าฉันจะไม่ใช้ก็ตาม


4

หมายเหตุ: ดูเหมือนจะมีการแก้ไขที่นำมาใช้ใน JDK 10 ซึ่งทำให้มั่นใจได้ว่า Custom Thread Pool จะใช้จำนวนเธรดที่ต้องการ

การดำเนินการสตรีมแบบขนานภายใน ForkJoinPool ที่กำหนดเองควรเป็นไปตามการขนานกัน https://bugs.openjdk.java.net/browse/JDK-8190974


1

ฉันลองใช้ForkJoinPool ที่กำหนดเองดังต่อไปนี้เพื่อปรับขนาดพูล:

private static Set<String> ThreadNameSet = new HashSet<>();
private static Callable<Long> getSum() {
    List<Long> aList = LongStream.rangeClosed(0, 10_000_000).boxed().collect(Collectors.toList());
    return () -> aList.parallelStream()
            .peek((i) -> {
                String threadName = Thread.currentThread().getName();
                ThreadNameSet.add(threadName);
            })
            .reduce(0L, Long::sum);
}

private static void testForkJoinPool() {
    final int parallelism = 10;

    ForkJoinPool forkJoinPool = null;
    Long result = 0L;
    try {
        forkJoinPool = new ForkJoinPool(parallelism);
        result = forkJoinPool.submit(getSum()).get(); //this makes it an overall blocking call

    } catch (InterruptedException | ExecutionException e) {
        e.printStackTrace();
    } finally {
        if (forkJoinPool != null) {
            forkJoinPool.shutdown(); //always remember to shutdown the pool
        }
    }
    out.println(result);
    out.println(ThreadNameSet);
}

นี่คือการส่งออกกล่าวว่าสระว่ายน้ำที่มีการใช้หัวข้อมากกว่าเริ่มต้น4

50000005000000
[ForkJoinPool-1-worker-8, ForkJoinPool-1-worker-9, ForkJoinPool-1-worker-6, ForkJoinPool-1-worker-11, ForkJoinPool-1-worker-10, ForkJoinPool-1-worker-1, ForkJoinPool-1-worker-15, ForkJoinPool-1-worker-13, ForkJoinPool-1-worker-4, ForkJoinPool-1-worker-2]

แต่จริงๆแล้วมันมีสิ่งแปลกประหลาดเมื่อฉันพยายามที่จะบรรลุผลลัพธ์เดียวกันโดยใช้ThreadPoolExecutorดังนี้:

BlockingDeque blockingDeque = new LinkedBlockingDeque(1000);
ThreadPoolExecutor fixedSizePool = new ThreadPoolExecutor(10, 20, 60, TimeUnit.SECONDS, blockingDeque, new MyThreadFactory("my-thread"));

แต่ฉันล้มเหลว

มันจะเริ่มต้นparallelStreamในเธรดใหม่จากนั้นทุกอย่างอื่นจะเหมือนกันซึ่งพิสูจน์ให้เห็นอีกครั้งว่าparallelStreamจะใช้ForkJoinPoolเพื่อเริ่มเธรดลูก


อะไรคือเหตุผลที่เป็นไปได้ที่จะไม่อนุญาตผู้ดำเนินการรายอื่น
omjego

@omjego นั่นเป็นคำถามที่ดีบางทีคุณอาจเริ่มต้นคำถามใหม่และให้รายละเอียดเพิ่มเติมเพื่ออธิบายความคิดของคุณ;)
20556

1

ไปรับAbacusUtil หมายเลขเธรดสามารถระบุได้สำหรับสตรีมขนาน นี่คือตัวอย่างรหัส:

LongStream.range(4, 1_000_000).parallel(threadNum)...

การเปิดเผยข้อมูล: ฉันเป็นผู้พัฒนา AbacusUtil


1

หากคุณไม่ต้องการพึ่งแฮ็กติดตั้งใช้งานมีวิธีหนึ่งในการทำให้สำเร็จโดยการใช้ตัวสะสมแบบกำหนดเองที่จะรวมmapและcollectความหมาย ... และคุณจะไม่ถูก จำกัด ที่ ForkJoinPool:

list.stream()
  .collect(parallelToList(i -> fetchFromDb(i), executor))
  .join()

โชคดีที่มันทำเสร็จแล้วที่นี่และมีให้ที่ Maven Central: http://github.com/pivovarit/parallel-collectors

คำเตือน: ฉันเขียนมันและรับผิดชอบมัน


0

หากคุณไม่สนใจการใช้ไลบรารี่ของบุคคลที่สามด้วยไซคลอปตอบสนองคุณสามารถผสมลำธารเรียงลำดับและขนานในท่อเดียวกันและกำหนด ForkJoinPools แบบกำหนดเอง ตัวอย่างเช่น

 ReactiveSeq.range(1, 1_000_000)
            .foldParallel(new ForkJoinPool(10),
                          s->s.filter(i->true)
                              .peek(i->System.out.println("Thread " + Thread.currentThread().getId()))
                              .max(Comparator.naturalOrder()));

หรือถ้าเราต้องการดำเนินการต่อภายในสตรีมลำดับ

 ReactiveSeq.range(1, 1_000_000)
            .parallel(new ForkJoinPool(10),
                      s->s.filter(i->true)
                          .peek(i->System.out.println("Thread " + Thread.currentThread().getId())))
            .map(this::processSequentially)
            .forEach(System.out::println);

[การเปิดเผยข้อมูลฉันเป็นผู้นำการพัฒนาไซคลอปตอบสนอง]


0

หากคุณไม่ต้องการ ThreadPool ที่กำหนดเอง แต่คุณต้องการ จำกัด จำนวนงานที่เกิดขึ้นพร้อมกันคุณสามารถใช้:

List<Path> paths = List.of("/path/file1.csv", "/path/file2.csv", "/path/file3.csv").stream().map(e -> Paths.get(e)).collect(toList());
List<List<Path>> partitions = Lists.partition(paths, 4); // Guava method

partitions.forEach(group -> group.parallelStream().forEach(csvFilePath -> {
       // do your processing   
}));

(คำถามที่ถามซ้ำเกี่ยวกับเรื่องนี้ถูกล็อคดังนั้นโปรดช่วยฉันที่นี่


-2

คุณสามารถลองใช้ ForkJoinWorkerThreadFactory นี้และฉีดเข้ากับคลาส Fork-Join

public ForkJoinPool(int parallelism,
                        ForkJoinWorkerThreadFactory factory,
                        UncaughtExceptionHandler handler,
                        boolean asyncMode) {
        this(checkParallelism(parallelism),
             checkFactory(factory),
             handler,
             asyncMode ? FIFO_QUEUE : LIFO_QUEUE,
             "ForkJoinPool-" + nextPoolId() + "-worker-");
        checkPermission();
    }

คุณสามารถใช้ Constructor ของ Fork-Join pool เพื่อทำสิ่งนี้

หมายเหตุ: - 1. หากคุณใช้สิ่งนี้ให้คำนึงถึงว่าการใช้เธรดใหม่ของคุณการกำหนดตารางเวลาจาก JVM จะได้รับผลกระทบซึ่งโดยทั่วไปจะกำหนดเวลาการเข้าร่วมฟอร์กแยกการรวมเข้ากับคอร์ที่แตกต่างกัน 2. การกำหนดตารางงานโดยการเข้าร่วมฟอรัมกับเธรดจะไม่ได้รับผลกระทบ 3. ยังไม่ได้คิดออกอย่างชัดเจนว่ากระแสข้อมูลแบบขนานคือการเลือกเธรดจากการเข้าร่วมทางแยก (ไม่พบเอกสารที่เหมาะสม) ดังนั้นลองใช้โรงงานการตั้งชื่อเธรดอื่นเพื่อตรวจสอบให้แน่ใจว่ามีการเลือกเธรดในสตรีมขนาน จาก customThreadFactory ที่คุณระบุ 4. commonThreadPool จะไม่ใช้ customThreadFactory นี้


คุณสามารถให้ตัวอย่างที่ใช้งานได้ซึ่งจะสาธิตวิธีใช้สิ่งที่คุณระบุไว้หรือไม่?
J. Murray
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.