3

Я читал структуру Java ForkJoin. Какие дополнительные преимущества существуют, если нет прямого вызова invoke() по реализации ForkJoinTask (например, RecursiveTask), но для создания экземпляра ForkJoinPool и вызова pool.invoke(task)? Что именно происходит, когда мы называем эти 2 метода все называемыми invoke?ForkJoinPool.invoke() и ForkJoinTask.invoke() или compute()

От источника кажется, что если вызывается recursiveTask.invoke, он будет вызывать его exec и, в конечном итоге, compute, в пуле управляемого потока. Как таковой, это еще более запутанно, почему у нас есть идиома pool.invoke(task).

Я написал несколько простых кодов для проверки разницы в производительности, но я их не видел. Может быть, тестовый код неправильный? Смотрите ниже:

public class MyForkJoinTask extends RecursiveAction { 

    private static int totalWorkInMillis = 20000; 
    protected static int sThreshold = 1000; 

    private int workInMillis; 


    public MyForkJoinTask(int work) { 
     this.workInMillis = work; 
    } 

    // Average pixels from source, write results into destination. 
    protected void computeDirectly() { 
     try { 

      ForkJoinTask<Object> objectForkJoinTask = new ForkJoinTask<>(); 
      Thread.sleep(workInMillis); 
     } catch (InterruptedException e) { 
      e.printStackTrace(); 
     } 
    } 

    @Override 
    protected void compute() { 
     if (workInMillis < sThreshold) { 
      computeDirectly(); 
      return; 
     } 

     int discountedWork = (int) (workInMillis * 0.9); 
     int split = discountedWork/2; 

     invokeAll(new MyForkJoinTask(split), 
       new MyForkJoinTask(split)); 
    } 

    public static void main(String[] args) throws Exception { 
     System.out.printf("Total work is %d in millis.%n", totalWorkInMillis); 
     System.out.printf("Threshold is %d in millis.%n", sThreshold); 

     int processors = Runtime.getRuntime().availableProcessors(); 
     System.out.println(Integer.toString(processors) + " processor" 
       + (processors != 1 ? "s are " : " is ") 
       + "available"); 

     MyForkJoinTask fb = new MyForkJoinTask(totalWorkInMillis); 

     ForkJoinPool pool = new ForkJoinPool(); 

     long startTime = System.currentTimeMillis(); 


     // These 2 seems no difference! 
     pool.invoke(fb); 
//  fb.compute(); 


     long endTime = System.currentTimeMillis(); 

     System.out.println("Took " + (endTime - startTime) + 
       " milliseconds."); 
    } 
} 

ответ

4

compute() метод RecursiveTask класса просто абстрактный метод, который содержит код задачи. Он не использует новый поток из пула, и если вы его обычно вызываете, он не запускается в потоке, управляемом пулом.

Метод invoke в пуле соединений fork отправляет задание в пул, который затем запускается в отдельном потоке, вызывает метод compute в этом потоке и затем ждет результата.

Вы можете видеть это в формулировке в java-документе для RecursiveTask и ForkJoinPool. Метод invoke() фактически выполняет задачу, тогда как метод compute() просто инкапсулирует вычисление.

protected abstract V compute() 

Основное вычисление выполняется с помощью этой задачи.

И ForkJoinPool

public <T> T invoke(ForkJoinTask<T> task) 

выполняет поставленную задачу, возвращая ее результат после завершения. ...

Таким образом, с помощью метода вычисления вы выполняете первый вызов compute за пределами пула соединений fork. Вы можете проверить это, добавив строку журнала внутри метода вычисления.

System.out.println(this.inForkJoinPool()); 

Вы также можете проверить, что он работает в том же потоке, регистрируя идентификатор потока

System.out.println(Thread.currentThread().getId()); 

После того, как вы называете invokeAll, подзадачи, включенных в этот вызов затем запустить в бассейне. Обратите внимание, что он НЕ обязательно запускается в созданном вами пуле непосредственно перед вызовом compute(). Вы можете прокомментировать свой код new ForkJoinPool(), и он все равно будет работать. Интересно, что в документе java 7 указано, что метод invokeAll() выдаст исключение, если он вызван за пределами потока управления пулом, но в документе java 8 нет. Я не тестировал его в java 7, если вы только (8). Но, вполне возможно, ваш код генерирует исключение при вызове compute() непосредственно в java 7.

Причина, по которой оба результата возвращаются в одно и то же время, заключается в том, что миллисекунды не достаточно точны, чтобы зафиксировать разницу начала первого потока в потоке управляемого пула или просто запустить первый вызов compute в существующем потоке.

Способ, которым руководство по исследованиям OCA/OCP проводит Сьерра и Бейтс, рекомендует использовать инфраструктуру соединения fork, чтобы вызвать invoke() из бассейна. Он дает понять, какой пул вы используете, а также означает, что вы можете отправлять несколько задач в один пул, что экономит накладные расходы на воссоздание новых пулов каждый раз. Логично, что также чище держать все ваши вычисления задач в потоке, управляемом пулом (или, по крайней мере, я думаю, что это так).

+0

Спасибо за ответ. Но 'RecursiveTask' также имеет унаследованный метод' invoke', и обычно вызывается 'invoke' внутри его основного метода' compute'. Что это значит? И все-таки, почему нет различий в производительности? – Boyang

+0

Итак, я копаю исходный код. Если вызывается 'recursiveTask.invoke', он будет вызывать его' exec' и в конечном итоге 'compute' в пуле управляемых потоков. В таком случае меня еще больше путают с идиомой 'pool.invoke (task)'. Зачем? – Boyang

+0

Обратитесь к моим новым изменениям – Boyang