มี ExecutorService ที่ใช้เธรดปัจจุบันหรือไม่


94

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

ExecutorService es = threads == 0 ? new CurrentThreadExecutor() : Executors.newThreadPoolExecutor(threads);

// es.execute / es.submit / new ExecutorCompletionService(es) etc

คำตอบ:


70

นี่คือการใช้งานที่ง่ายมากExecutor(ไม่ExecutorServiceรังเกียจคุณ) ที่ใช้เฉพาะเธรดปัจจุบันเท่านั้น ขโมยสิ่งนี้มาจาก "Java Concurrency in Practice" (การอ่านที่จำเป็น)

public class CurrentThreadExecutor implements Executor {
    public void execute(Runnable r) {
        r.run();
    }
}

ExecutorService เป็นอินเทอร์เฟซที่ซับซ้อนมากขึ้น แต่สามารถจัดการได้ด้วยวิธีการเดียวกัน


4
+1: อย่างที่คุณพูด ExecutorService สามารถจัดการได้ในลักษณะเดียวกันโดยอาจใช้คลาสย่อย AbstractExecutorService
Paul Cager

@ พอลเย่อAbstractExecutorServiceดูเหมือนทางไป
คิดมาก

15
ใน Java8 คุณสามารถลดสิ่งนี้ให้เหลือเพียงRunnable::run
Jon Freedman

@Juude มันจะทำงานบนเธรดที่เรียกตัวดำเนินการเสมอ
Gustav Karlsson

ไม่ใช่จุดของตัวดำเนินการเธรดเดียวกันที่จะสามารถกำหนดเวลางานเพิ่มเติมจากภายใน execute () ได้หรือไม่? คำตอบนี้จะไม่ทำ ฉันไม่สามารถหาคำตอบที่ตรงตามนี้ได้
haelix

83

คุณสามารถใช้ Guava's MoreExecutors.newDirectExecutorService()หรือMoreExecutors.directExecutor()ถ้าคุณไม่ต้องการไฟล์ExecutorServiceไฟล์.

หากรวม Guava มีน้ำหนักมากเกินไปคุณสามารถนำสิ่งที่ดีไปใช้ได้:

public final class SameThreadExecutorService extends ThreadPoolExecutor {
  private final CountDownLatch signal = new CountDownLatch(1);

  private SameThreadExecutorService() {
    super(1, 1, 0, TimeUnit.DAYS, new SynchronousQueue<Runnable>(),
        new ThreadPoolExecutor.CallerRunsPolicy());
  }

  @Override public void shutdown() {
    super.shutdown();
    signal.countDown();
  }

  public static ExecutorService getInstance() {
    return SingletonHolder.instance;
  }

  private static class SingletonHolder {
    static ExecutorService instance = createInstance();    
  }

  private static ExecutorService createInstance() {
    final SameThreadExecutorService instance
        = new SameThreadExecutorService();

    // The executor has one worker thread. Give it a Runnable that waits
    // until the executor service is shut down.
    // All other submitted tasks will use the RejectedExecutionHandler
    // which runs tasks using the  caller's thread.
    instance.submit(new Runnable() {
        @Override public void run() {
          boolean interrupted = false;
          try {
            while (true) {
              try {
                instance.signal.await();
                break;
              } catch (InterruptedException e) {
                interrupted = true;
              }
            }
          } finally {
            if (interrupted) {
              Thread.currentThread().interrupt();
            }
          }
        }});
    return Executors.unconfigurableScheduledExecutorService(instance);
  }
}

1
สำหรับ Android จะส่งคืน Executors.unconfigurableExecutorService (อินสแตนซ์);
Maragues

ถ้าทั้งหมดที่เราใช้คือเธรดปัจจุบันเหตุใดจึงต้องมีการซิงโครไนซ์แบบดั้งเดิม ทำไมต้องสลัก?
haelix

@haelix จำเป็นต้องใช้สลักเนื่องจากแม้ว่างานจะเสร็จในเธรดเดียวกับที่เพิ่มงานเธรดใด ๆ ก็สามารถปิดตัวดำเนินการได้
NamshubWriter

63

สไตล์ Java 8:

Executor e = Runnable::run;


9
สกปรกอย่างแน่นอน ฉันรักมัน.
Rogue

มันสกปรกเกี่ยวกับอะไร? มันสง่างาม :)
lpandzic

1
มันเป็นสิ่งที่สกปรกที่สุด @Ipandzic มันผิดปกติและรวบรัด
Rogue

12

ฉันเขียนExecutorServiceโดยอิงจากไฟล์AbstractExecutorService.

/**
 * Executes all submitted tasks directly in the same thread as the caller.
 */
public class SameThreadExecutorService extends AbstractExecutorService {

    //volatile because can be viewed by other threads
    private volatile boolean terminated;

    @Override
    public void shutdown() {
        terminated = true;
    }

    @Override
    public boolean isShutdown() {
        return terminated;
    }

    @Override
    public boolean isTerminated() {
        return terminated;
    }

    @Override
    public boolean awaitTermination(long theTimeout, TimeUnit theUnit) throws InterruptedException {
        shutdown(); // TODO ok to call shutdown? what if the client never called shutdown???
        return terminated;
    }

    @Override
    public List<Runnable> shutdownNow() {
        return Collections.emptyList();
    }

    @Override
    public void execute(Runnable theCommand) {
        theCommand.run();
    }
}

เขตข้อมูลที่สิ้นสุดไม่ได้รับการป้องกันด้วยการซิงโครไนซ์
Daneel Yaitskov

1
@ DaneelS terminatedเขตYaitskov จะไม่ได้รับประโยชน์จากการเข้าถึงแบบซิงโครไนซ์ตามรหัสที่มีอยู่จริงที่นี่ การดำเนินการบนฟิลด์ 32 บิตเป็นแบบปรมาณูใน Java
Christopher Schultz

ฉันคิดว่าเมธอด isTerminated () ในด้านบนไม่ถูกต้องนักเนื่องจาก isTerminated () ควรจะคืนค่าจริงหากไม่มีงานที่กำลังดำเนินการอยู่ Guava ติดตามจำนวนงานในตัวแปรอื่นซึ่งน่าจะเป็นสาเหตุที่พวกเขาปกป้องตัวแปรทั้งสองด้วยการล็อก
Jeremy K

7

คุณสามารถใช้ RejectedExecutionHandler เพื่อรันงานในเธรดปัจจุบัน

public static final ThreadPoolExecutor CURRENT_THREAD_EXECUTOR = new ThreadPoolExecutor(0, 0, 0, TimeUnit.DAYS, new SynchronousQueue<Runnable>(), new RejectedExecutionHandler() {
    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
        r.run();
    }
});

คุณต้องการเพียงหนึ่งในนี้เท่านั้น


ฉลาด! สิ่งนี้ปลอดภัยแค่ไหน (คำถามที่ซื่อสัตย์)? มีวิธีใดบ้างที่งานจะถูกปฏิเสธโดยที่คุณไม่ต้องการดำเนินการในเธรดปัจจุบัน? งานจะถูกปฏิเสธหรือไม่หาก ExecutorService กำลังปิดหรือยุติ?
คิดมาก

เนื่องจากขนาดสูงสุดคือ 0 ทุกงานจึงถูกปฏิเสธ อย่างไรก็ตามพฤติกรรมที่ถูกปฏิเสธคือการรันในเธรดปัจจุบัน จะมีปัญหาก็ต่อเมื่องานไม่ถูกปฏิเสธ
Peter Lawrey

8
java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicyทราบว่ามีอยู่แล้วการดำเนินการตามนโยบายนี้ไม่จำเป็นต้องกำหนดของคุณเอง
jtahlborn

7
เป็นไปไม่ได้อีกต่อไปที่จะสร้าง ThreadPoolExecutor ที่มีขนาดพูลสูงสุดเป็น 0 ฉันเดาว่ามันเป็นไปได้ที่จะสร้างพฤติกรรมโดยใช้การปิดกั้นคิวขนาด 0 แต่ดูเหมือนว่าการใช้งานเริ่มต้นจะไม่อนุญาต
Axelle Ziegler

ที่จะไม่คอมไพล์เนื่องจาก {code} if (corePoolSize <0 || maximumPoolSize <= 0 || maximumPoolSize <corePoolSize || keepAliveTime <0) {code} ใน java.util.ThreadPoolExecutor (อย่างน้อย openJdk 7)
Bogdan

7

ผมใช้เดียวกัน "CurrentThreadExecutorService" สำหรับวัตถุประสงค์ในการทดสอบและแม้จะแก้ปัญหาทุกคนมีความสุข (โดยเฉพาะอย่างยิ่งคนที่กล่าวถึงวิธีที่ฝรั่ง ) ฉันมากับสิ่งที่คล้ายกับสิ่งที่ปีเตอร์ Lawrey แนะนำที่นี่

ดังกล่าวโดย Axelle Ziegler นี่โชคไม่ดีที่วิธีการแก้ปัญหาของปีเตอร์จะไม่จริงเพราะการทำงานของการตรวจสอบนำมาใช้ในThreadPoolExecutorในmaximumPoolSizeพารามิเตอร์คอนสตรัค (เช่นmaximumPoolSizeไม่สามารถ<=0)

เพื่อหลีกเลี่ยงสิ่งนั้นฉันได้ทำสิ่งต่อไปนี้:

private static ExecutorService currentThreadExecutorService() {
    CallerRunsPolicy callerRunsPolicy = new ThreadPoolExecutor.CallerRunsPolicy();
    return new ThreadPoolExecutor(0, 1, 0L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), callerRunsPolicy) {
        @Override
        public void execute(Runnable command) {
            callerRunsPolicy.rejectedExecution(command, this);
        }
    };
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.