जावा निष्पादक: किसी कार्य के पूरा होने पर, बिना किसी अवरोध के, कैसे सूचित किया जाए?


154

कहो कि मेरे पास कार्यों से भरी एक कतार है जिसे मुझे निष्पादक सेवा में प्रस्तुत करना होगा। मैं उन्हें एक समय में एक संसाधित करना चाहता हूं। सबसे सरल तरीका जो मैं सोच सकता हूं, वह है:

  1. कतार से एक कार्य लें
  2. इसे निष्पादक को भेजें
  3. लौटें भविष्य पर कॉल .get और परिणाम उपलब्ध होने तक ब्लॉक करें
  4. कतार से एक और काम ले लो ...

हालांकि, मैं पूरी तरह से अवरुद्ध होने से बचने की कोशिश कर रहा हूं। अगर मेरे पास 10,000 ऐसी कतारें हैं, जिन्हें एक समय में संसाधित करने के लिए अपने कार्यों की आवश्यकता होती है, तो मैं स्टैक स्पेस से बाहर निकल जाऊंगा क्योंकि उनमें से अधिकांश अवरुद्ध धागों को पकड़े रहेंगे।

मैं क्या चाहूंगा कि कोई कार्य प्रस्तुत करना है और कॉल-बैक प्रदान करना है जिसे कार्य पूरा होने पर कहा जाता है। मैं अगले कार्य को भेजने के लिए ध्वज के रूप में उस कॉल-बैक सूचना का उपयोग करूंगा। (कार्यात्मकजाव और जेटलैंग स्पष्ट रूप से ऐसे गैर-अवरोधक एल्गोरिदम का उपयोग करते हैं, लेकिन मैं उनके कोड को नहीं समझ सकता)

मैं JDK की java.util.concurrent का उपयोग कैसे कर सकता हूं, मेरी खुद की निष्पादक सेवा लिखने से कम है?

(कतार जो मुझे इन कार्यों को खिलाती है, खुद को अवरुद्ध कर सकती है, लेकिन बाद में निपटने के लिए यह एक मुद्दा है)

जवाबों:


146

पूर्ण अधिसूचना में जो भी पैरामीटर आप पास करना चाहते हैं उसे प्राप्त करने के लिए कॉलबैक इंटरफ़ेस को परिभाषित करें। फिर इसे कार्य के अंत में लागू करें।

आप Runnable कार्यों के लिए एक सामान्य आवरण भी लिख सकते हैं, और इन्हें सबमिट कर सकते हैं ExecutorService। या, जावा 8 में निर्मित तंत्र के लिए नीचे देखें।

class CallbackTask implements Runnable {

  private final Runnable task;

  private final Callback callback;

  CallbackTask(Runnable task, Callback callback) {
    this.task = task;
    this.callback = callback;
  }

  public void run() {
    task.run();
    callback.complete();
  }

}

CompletableFutureजावा 8 के साथ , पाइपलाइनों की रचना करने के लिए एक अधिक विस्तृत साधन शामिल था जहां प्रक्रियाओं को अतुल्यकालिक और सशर्त रूप से पूरा किया जा सकता है। यहां अधिसूचना का एक वंचित लेकिन पूर्ण उदाहरण है।

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;

public class GetTaskNotificationWithoutBlocking {

  public static void main(String... argv) throws Exception {
    ExampleService svc = new ExampleService();
    GetTaskNotificationWithoutBlocking listener = new GetTaskNotificationWithoutBlocking();
    CompletableFuture<String> f = CompletableFuture.supplyAsync(svc::work);
    f.thenAccept(listener::notify);
    System.out.println("Exiting main()");
  }

  void notify(String msg) {
    System.out.println("Received message: " + msg);
  }

}

class ExampleService {

  String work() {
    sleep(7000, TimeUnit.MILLISECONDS); /* Pretend to be busy... */
    char[] str = new char[5];
    ThreadLocalRandom current = ThreadLocalRandom.current();
    for (int idx = 0; idx < str.length; ++idx)
      str[idx] = (char) ('A' + current.nextInt(26));
    String msg = new String(str);
    System.out.println("Generated message: " + msg);
    return msg;
  }

  public static void sleep(long average, TimeUnit unit) {
    String name = Thread.currentThread().getName();
    long timeout = Math.min(exponential(average), Math.multiplyExact(10, average));
    System.out.printf("%s sleeping %d %s...%n", name, timeout, unit);
    try {
      unit.sleep(timeout);
      System.out.println(name + " awoke.");
    } catch (InterruptedException abort) {
      Thread.currentThread().interrupt();
      System.out.println(name + " interrupted.");
    }
  }

  public static long exponential(long avg) {
    return (long) (avg * -Math.log(1 - ThreadLocalRandom.current().nextDouble()));
  }

}

1
पलक झपकते ही तीन जवाब! मुझे कॉलबैक टस्क पसंद है, इस तरह के एक सरल और सीधे आगे समाधान। यह पूर्वव्यापी में स्पष्ट दिखता है। धन्यवाद। SingleThreadedExecutor के बारे में अन्य टिप्पणियों के बारे में: मेरी हजारों कतारें हो सकती हैं जिनमें हजारों कार्य हो सकते हैं। उनमें से प्रत्येक को एक समय में अपने कार्यों को संसाधित करने की आवश्यकता होती है, लेकिन विभिन्न कतारें समानांतर में काम कर सकती हैं। इसलिए मैं एक ही वैश्विक सूत्र का उपयोग कर रहा हूं। मैं जल्लाद के लिए नया हूं इसलिए कृपया मुझे बताएं कि क्या मैं गलत हूं।
शाहबाज

5
अच्छा पैटर्न, मैं फिर भी अमरूद के सुनने योग्य भविष्य एपीआई का उपयोग करूंगा जो इसे बहुत अच्छा कार्यान्वयन प्रदान करते हैं।
पियरे-हेनरी

क्या यह भविष्य के उपयोग के उद्देश्य को नहीं हराता है?
TakeCare

2
@Zelphir यह एक ऐसा Callbackइंटरफ़ेस था जिसे आप घोषित करते हैं; एक पुस्तकालय से नहीं। आजकल मैं शायद सिर्फ प्रयोग करेंगे Runnable, Consumerया BiConsumer, मैं क्या श्रोता के लिए काम से वापस उत्तीर्ण करने की आवश्यकता पर निर्भर करता है।
इरिकसन

1
@Bhargav यह कॉलबैक की एक खासियत है- एक बाहरी इकाई, नियंत्रण इकाई के लिए "कॉल बैक" है। क्या आप उस धागे को चाहते हैं, जो कार्य समाप्त होने तक कार्य को अवरुद्ध करने के लिए बनाया गया है? फिर दूसरे सूत्र पर कार्य चलाने में क्या उद्देश्य है? यदि आप थ्रेड को जारी रखने की अनुमति देते हैं, तो इसे बार-बार कुछ साझा स्थिति (शायद एक लूप में, लेकिन आपके प्रोग्राम पर निर्भर करता है) की जांच करने की आवश्यकता होगी, जब तक कि यह अपडेट (बूलियन फ्लैग, कतार में नया आइटम, इत्यादि) को सही न कर दे। इस उत्तर में वर्णित कॉलबैक। यह तब कुछ अतिरिक्त कार्य कर सकता है।
एरिक्सन

52

Java 8 में आप कंप्लीटटेबल सीमेंट का उपयोग कर सकते हैं । यहाँ एक उदाहरण है जो मेरे पास मेरे कोड में था, जहाँ मैं इसका उपयोग अपनी उपयोगकर्ता सेवा से उपयोगकर्ताओं को लाने के लिए कर रहा हूँ, उन्हें अपनी दृश्य वस्तुओं पर मैप करूँ और फिर अपना दृश्य अपडेट करूं या कोई त्रुटि संवाद दिखाऊं (यह एक GUI एप्लिकेशन है):

    CompletableFuture.supplyAsync(
            userService::listUsers
    ).thenApply(
            this::mapUsersToUserViews
    ).thenAccept(
            this::updateView
    ).exceptionally(
            throwable -> { showErrorDialogFor(throwable); return null; }
    );

यह एसिंक्रोनस रूप से निष्पादित करता है। मैं दो निजी तरीकों का उपयोग कर रहा हूं: mapUsersToUserViewsऔर updateView


एक निष्पादक के साथ एक कंप्लीटटेबल सीमेंट का उपयोग कैसे किया जाएगा? (समवर्ती / समानांतर उदाहरणों की संख्या को सीमित करने के लिए) क्या यह एक संकेत होगा: cf: सबमिटिंग-Futuretasks-to-a-execor-Why-does-it-work ?
user1767316

47

अमरूद के सुनने योग्य भविष्य एपीआई का उपयोग करें और एक कॉलबैक जोड़ें। सी एफ वेबसाइट से:

ListeningExecutorService service = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(10));
ListenableFuture<Explosion> explosion = service.submit(new Callable<Explosion>() {
  public Explosion call() {
    return pushBigRedButton();
  }
});
Futures.addCallback(explosion, new FutureCallback<Explosion>() {
  // we want this handler to run immediately after we push the big red button!
  public void onSuccess(Explosion explosion) {
    walkAwayFrom(explosion);
  }
  public void onFailure(Throwable thrown) {
    battleArchNemesis(); // escaped the explosion!
  }
});

नमस्ते, लेकिन अगर मैं onSuccess के बाद इस थ्रेड को रोकना चाहता हूं तो मैं कैसे कर सकता हूं?
DevOps85

24

आप FutureTaskवर्ग का विस्तार कर सकते हैं , और done()विधि को ओवरराइड कर सकते हैं , फिर FutureTaskऑब्जेक्ट को इसमें जोड़ सकते हैं ExecutorService, इसलिए done()विधि FutureTaskतुरंत पूरा होने पर आह्वान करेगी ।


then add the FutureTask object to the ExecutorService, क्या आप कृपया मुझे बता सकते हैं कि यह कैसे करना है?
गैरी गौह

@GaryGauh इसे देखें अधिक जानकारी के लिए आप FutureTask का विस्तार कर सकते हैं, हम इसे MyFutureTask कह सकते हैं। तब MyFutureTask सबमिट करने के लिए ExcutorService का उपयोग करें, फिर MyFutureTask की रन विधि चलेगी, जब MyFutureTask समाप्त हो जाएगा तो आपका किया हुआ तरीका कहा जाएगा। कुछ भ्रामक दो FutureTask है, और वास्तव में MyFutureTask एक सामान्य Runnable है।
चाजुन झोंग

15

ThreadPoolExecutorइसके अलावा beforeExecuteऔर afterExecuteहुक विधियाँ हैं जिन्हें आप ओवरराइड कर सकते हैं और उपयोग कर सकते हैं। यहाँ से वर्णन है ThreadPoolExecutorकी Javadocs

हुक के तरीके

यह वर्ग संरक्षित अधिलेखनीय beforeExecute(java.lang.Thread, java.lang.Runnable)और afterExecute(java.lang.Runnable, java.lang.Throwable)तरीके प्रदान करता है जिन्हें प्रत्येक कार्य के निष्पादन से पहले और बाद में कहा जाता है। इनका उपयोग निष्पादन पर्यावरण में हेरफेर करने के लिए किया जा सकता है; उदाहरण के लिए, पुनर्निमाण करना ThreadLocals, आंकड़े एकत्रित करना या लॉग एंट्री जोड़ना। इसके अतिरिक्त, terminated()किसी भी विशेष प्रसंस्करण को करने के लिए विधि को ओवरराइड किया जा सकता है जिसे एक बार Executorपूरी तरह समाप्त होने के बाद करने की आवश्यकता होती है। यदि हुक या कॉलबैक विधियाँ अपवादों को फेंकती हैं, तो आंतरिक कार्यकर्ता सूत्र विफल हो सकते हैं और अचानक समाप्त हो सकते हैं।


6

एक का उपयोग करें CountDownLatch

यह से है java.util.concurrentऔर यह जारी रखने से पहले निष्पादन को पूरा करने के लिए कई थ्रेड के लिए प्रतीक्षा करने का तरीका है।

आपके द्वारा खोजे जा रहे कॉलबैक प्रभाव को प्राप्त करने के लिए, इसके लिए थोड़े अतिरिक्त अतिरिक्त काम की आवश्यकता होती है। अर्थात्, एक अलग थ्रेड में अपने आप को संभालना जो इसका उपयोग करता है CountDownLatchऔर उस पर प्रतीक्षा करता है, फिर जो कुछ भी आपको सूचित करने की आवश्यकता है उसे सूचित करने के बारे में आगे बढ़ता है। कॉलबैक के लिए कोई मूल समर्थन नहीं है, या उस प्रभाव के समान कुछ भी नहीं है।


संपादित करें: अब जब मैं आपके प्रश्न को समझता हूं, तो मुझे लगता है कि आप अनावश्यक रूप से बहुत दूर तक पहुंच रहे हैं। यदि आप एक नियमित लेते हैं SingleThreadExecutor, तो इसे सभी कार्यों को दें, और यह मूल रूप से कतारबद्ध करेगा।


SingleThreadExecutor का उपयोग करना यह जानने का सबसे अच्छा तरीका है कि सभी थ्रेड पूरे हो चुके हैं? मैंने देखा कि एक उदाहरण एक समय का उपयोग करता है! निष्पादक.विशिष्ट लेकिन यह बहुत सुंदर नहीं लगता है। मैंने प्रत्येक कार्यकर्ता के लिए कॉलबैक सुविधा लागू की और एक गिनती बढ़ाई जो काम करती है।
भालू

5

यदि आप यह सुनिश्चित करना चाहते हैं कि कोई भी कार्य एक ही समय में नहीं चलेगा, तो SingleThreadedExecutor का उपयोग करें । सबमिट किए गए आदेश में कार्यों को संसाधित किया जाएगा। आपको कार्यों को रखने की भी आवश्यकता नहीं है, बस उन्हें निष्पादन के लिए प्रस्तुत करें।


2

सरल कोड का Callbackउपयोग कर तंत्र को लागू करने के लिएExecutorService

import java.util.concurrent.*;
import java.util.*;

public class CallBackDemo{
    public CallBackDemo(){
        System.out.println("creating service");
        ExecutorService service = Executors.newFixedThreadPool(5);

        try{
            for ( int i=0; i<5; i++){
                Callback callback = new Callback(i+1);
                MyCallable myCallable = new MyCallable((long)i+1,callback);
                Future<Long> future = service.submit(myCallable);
                //System.out.println("future status:"+future.get()+":"+future.isDone());
            }
        }catch(Exception err){
            err.printStackTrace();
        }
        service.shutdown();
    }
    public static void main(String args[]){
        CallBackDemo demo = new CallBackDemo();
    }
}
class MyCallable implements Callable<Long>{
    Long id = 0L;
    Callback callback;
    public MyCallable(Long val,Callback obj){
        this.id = val;
        this.callback = obj;
    }
    public Long call(){
        //Add your business logic
        System.out.println("Callable:"+id+":"+Thread.currentThread().getName());
        callback.callbackMethod();
        return id;
    }
}
class Callback {
    private int i;
    public Callback(int i){
        this.i = i;
    }
    public void callbackMethod(){
        System.out.println("Call back:"+i);
        // Add your business logic
    }
}

उत्पादन:

creating service
Callable:1:pool-1-thread-1
Call back:1
Callable:3:pool-1-thread-3
Callable:2:pool-1-thread-2
Call back:2
Callable:5:pool-1-thread-5
Call back:5
Call back:3
Callable:4:pool-1-thread-4
Call back:4

मुख्य नोट:

  1. यदि आप FIFO क्रम में अनुक्रम कार्यों को संसाधित करना चाहते हैं, तो प्रतिस्थापित करें newFixedThreadPool(5) साथnewFixedThreadPool(1)
  2. यदि आप callbackपिछले कार्य से परिणाम का विश्लेषण करने के बाद अगले कार्य को संसाधित करना चाहते हैं , तो बस नीचे टिप्पणी करें

    //System.out.println("future status:"+future.get()+":"+future.isDone());
  3. आप प्रतिस्थापित कर सकते हैं newFixedThreadPool() इनमें से किसी एक से हैं

    Executors.newCachedThreadPool()
    Executors.newWorkStealingPool()
    ThreadPoolExecutor

    आपके उपयोग के मामले पर निर्भर करता है।

  4. यदि आप कॉलबैक विधि को असिंक्रोनस रूप से हैंडल करना चाहते हैं

    ए। एक साझा पास करेंExecutorService or ThreadPoolExecutorकॉल करने योग्य कार्य के लिए

    ख। Callableकरने के लिए अपनी विधि परिवर्तित करेंCallable/Runnable कार्य में

    सी। करने के लिए पुश कॉलबैक कार्य ExecutorService or ThreadPoolExecutor


1

बस मैट के उत्तर को जोड़ने के लिए, जिसने मदद की, यहां कॉलबैक के उपयोग को दिखाने के लिए एक अधिक fleshed-out उदाहरण है।

private static Primes primes = new Primes();

public static void main(String[] args) throws InterruptedException {
    getPrimeAsync((p) ->
        System.out.println("onPrimeListener; p=" + p));

    System.out.println("Adios mi amigito");
}
public interface OnPrimeListener {
    void onPrime(int prime);
}
public static void getPrimeAsync(OnPrimeListener listener) {
    CompletableFuture.supplyAsync(primes::getNextPrime)
        .thenApply((prime) -> {
            System.out.println("getPrimeAsync(); prime=" + prime);
            if (listener != null) {
                listener.onPrime(prime);
            }
            return prime;
        });
}

आउटपुट है:

    getPrimeAsync(); prime=241
    onPrimeListener; p=241
    Adios mi amigito

1

आप इस तरह के कॉल करने के कार्यान्वयन का उपयोग कर सकते हैं

public class MyAsyncCallable<V> implements Callable<V> {

    CallbackInterface ci;

    public MyAsyncCallable(CallbackInterface ci) {
        this.ci = ci;
    }

    public V call() throws Exception {

        System.out.println("Call of MyCallable invoked");
        System.out.println("Result = " + this.ci.doSomething(10, 20));
        return (V) "Good job";
    }
}

जहां CallbackInterface कुछ बहुत ही बुनियादी तरह है

public interface CallbackInterface {
    public int doSomething(int a, int b);
}

और अब मुख्य वर्ग इस तरह दिखेगा

ExecutorService ex = Executors.newFixedThreadPool(2);

MyAsyncCallable<String> mac = new MyAsyncCallable<String>((a, b) -> a + b);
ex.submit(mac);

1

यह अमरूद के उपयोग से पचे के उत्तर का विस्तार है ListenableFuture

विशेष रूप से, Futures.transform()रिटर्न ListenableFutureइसलिए async कॉल का उपयोग किया जा सकता है। Futures.addCallback()रिटर्न void, इसलिए इसका उपयोग चैनिंग के लिए नहीं किया जा सकता है, लेकिन एक async पूर्णता पर सफलता / विफलता से निपटने के लिए अच्छा है।

// ListenableFuture1: Open Database
ListenableFuture<Database> database = service.submit(() -> openDatabase());

// ListenableFuture2: Query Database for Cursor rows
ListenableFuture<Cursor> cursor =
    Futures.transform(database, database -> database.query(table, ...));

// ListenableFuture3: Convert Cursor rows to List<Foo>
ListenableFuture<List<Foo>> fooList =
    Futures.transform(cursor, cursor -> cursorToFooList(cursor));

// Final Callback: Handle the success/errors when final future completes
Futures.addCallback(fooList, new FutureCallback<List<Foo>>() {
  public void onSuccess(List<Foo> foos) {
    doSomethingWith(foos);
  }
  public void onFailure(Throwable thrown) {
    log.error(thrown);
  }
});

नोट: Async कार्यों का पीछा करने के अलावा, Futures.transform()आपको प्रत्येक कार्य को एक अलग निष्पादक पर शेड्यूल करने की भी अनुमति देता है (इस उदाहरण में नहीं दिखाया गया है)।


यह बहुत अच्छा लगता है।
केसर
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.