Как да използвам Java Executor най-правилно?

+5 гласа
234 прегледа
попитан 2016 март 30 от Nikola.Nikolov. (3,100 точки)
етикетиран отново 2016 април 1 от Mitko Vasilev

Използвал съм Java Executors в мои multi-threading App-ове, но изглежда не мога да разбера кога да използвам всеки един от тези начини:

1.

ExecutorService executor=Executors.newFixedThreadPool(50);
executor.execute(new A_Runner(... some parameter ...));
executor.shutdown();
while (!executor.isTerminated()) { Thread.sleep(100); }

2.
int Page_Count=200;
ExecutorService executor=Executors.newFixedThreadPool(50);
doneSignal=new CountDownLatch(Page_Count);
for (int i=0;i<Page_Count;i++) executor.execute(new A_Runner(doneSignal, ... some parameter ...));
doneSignal.await();
executor.shutdown();
while (!executor.isTerminated()) { Thread.sleep(100); }

3.

int Executor_Count=30;
ThreadPoolExecutor executor=new ThreadPoolExecutor(Executor_Count,Executor_Count*2,1,TimeUnit.SECONDS,new LinkedBlockingQueue());
List<Future<String>> futures=new ArrayList<>(3330);
for (int i=0;i<50;i++) futures.add(executor.submit(new A_Runner(... some parameter ...));
executor.shutdown();
while (!executor.isTerminated()) { executor.awaitTermination(1,TimeUnit.SECONDS); }
for (Future<String> future : futures)
{
    String f=future.get();
    // ...

По-специфично, в [2]: Ако skip-на doneSignal, тогава ще е като [1], тогава какъв е смисъла на doneSignal?

Също така, в [3]: Ако добавя doneSigna какво ще станеl? Възможно ли е ?

Това което искам да знам е : тези начини заменяеми ли са или има някакви сутации в които трябва задължително да използвам един от тези начини?

1 отговор

+2 гласа
отговорени 2016 март 31 от Daniel Ivanov (11,160 точки)
избран 2016 март 31 от Admin
 
Най-добър отговор

1.ExecutorService            executor=Executors.newFixedThreadPool(50);

Прост е и лесен за употреба. Можеш да го използваш, когато не се притесняваш за неограничени опашки(queues) от задачи (tasks),които могат да се изпълнят за по-малък интервал от време. Изпълняването на един Runnable или Callable task може да не чака друг такъв да се изпълни. Самото изпълняване крие low level детайлите (тези на по-ниско ниво) нa ThreadPoolExecutor-a.

Предпочитам този, когато става въпрос за малко task-ове и натрупването на такива в неограничените queue-та не натоварват memory-то и понижават изпълнението на системата. Ако имаш ограничения на процесора/Паметта поради претрупване на task-ове, бих ти препоръчал да използваш ThreadPoolExecutor с ограничение на капацитета и RejectedExecutionHandler да handle-ваш отхвърлянето на задачи.

2.CountDownLatch: Ти си инициализирал CountDownLatch с някакъв count. Този count се намалява от извикванията (call-овете) към countDown() метода. Предполагам,че декрементата я call-ваш в своя пуснат (Runnable) task по-късно. Thread-овете,чакащи този count да стане нула могат да call-нат един от await() методите. Call-ването блокира thread-а (нишката) докато count-a не стане нула. Този клас позволява на java thread-а да чака докато друг набор от thread-ове се завършат.

Използват се в следните случаи:

1). Когато искаш да достигнеш максимален паралелизъм: Понякога ние искаме да стартираме няколко thread-а по едно и също време,за да достигнем максимален паралелизъм.

2). Чакаме N thread-а да се завършат преди изпълняването.

3).Локация на зa така наречената мъртва точка (Deadlock) - Когато 2 thread-а се изчакват взаимно и никой не тръгва.

Можеш да видиш повече тук:

http://howtodoinjava.com/core-java/multi-threading/when-to-use-countdownlatch-java-concurrency-example-tutorial/ 

3.ThreadPoolExecutor: Осигурява ти повече контрол. Ако application-а е затлачен от няколко изчакващи runnable task-а, ще използваш

ограничено queue като избереш максимален капацитет. В момента в който queue-то стигне максимален капацитет, можеш да дефинираш RejectionHandler-а

Java ни дава 4 типа RejectedExecutionHandler-a :

http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ThreadPoolExecutor.html

1. В default ТhreadPoolExecutor.AbortPolicy handler-а throw-ва runtime RejectedExecutionException при отхвърляне.

2. В ThreadPoolExecutor.CallerRunsPolicy thread-a,който се обръща (invoke-ва) , самият той изпълнява task-а.

3. В ThreadPoolExecutor.DiscardPolicy -то, task,който не може да бъде изпълнен, се drop-ва.

4. В ТhreadPoolExecutor.DiscardOldestPolicy ,пък,ако изпълняващия (executor-a) не е изключен, task-а най-отгоре на опашката се drop-ва, и изпълняването се преповтаря (което може пак да fail-не, каращо това пак да се преповтори).

 Можеш да видиш тук за повече инфо :

http://letslearnjavaj2ee.blogspot.bg/2013/08/threadpoolexecutor-handler-policies-for.html

А ако искаш да симулираш CountDownLatch behaviour, трябва да call-неш  invokeAll() метода.

4. Между другото 1 механизъм, който не си споменал е ForkJoinPool

http://tutorials.jenkov.com/java-util-concurrent/java-fork-and-join-forkjoinpool.html

Той е бил добавен в Java 7. ForkJoinPool-а е горе-долу като Java ExecutorService-а ,но има 1 разлика.

Fork-а прави така,че task-овете да могат да си поделят работата като трансформират тези task-ове в по-малки, които по-късно се пращат при

ForkJoinPool-а. Крадене на task-oве (task stealing) може да се случи във Fork-а когато свободните работещи thread-ове "крадат" task-ове от заетите в queue.

Java 8 сложиха още 1 API в  ExecutorService-a (https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ExecutorService.html )

което да създава такъв pool за кражба на работа. Няма смисъл да правиш RecursiveTask и RecursiveAction, но пак можеш да си ползваш ForkJoinPool-а.

  public static ExecutorService newWorkStealingPool()

Създава pool с work-stealing thread, използващ всички налични процесори като parallelism level-а който ти трябва.

По default ще си вземе някакъв набор от ядра на процесора като параметър за паралелизма. Ако имаш 8-ядрен процесор, можеш да process-ваш work task queue-to с 8 thread-а и т.н.

коментиран 2016 април 12 от Nikola.Nikolov. (3,100 точки)
Благодаря много за подробния отговор- много ми помогна
...