Как узнать время выполнения программы java
Во многих проектах требуется посчитать время, которое затратил тот или иной метод. Для этого можно вручную сохранять значение System.currentTimeMillis() и после метода вычислять затраченное время. Когда методов много это становится не очень удобным.
Поэтому я решил написать простенькую аннотацию, которая бы считала время выполнения метода. Попытавшись найти информацию в интернете, понял, что её по данной теме очень мало. Придётся как-то выкручиваться, собирая информацию по крупицам.
Наша аннотация будет помечать методы, для которых мы хотим посчитать время выполнения в миллисекундах или наносекундах и выводить результат через System.out.println.
Для начала создадим саму аннотацию:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target()
@Retention(RetentionPolicy.SOURCE)
public @ interface Time public enum TimeInterval < MILLISECOND, NANOSECOND >;
annotations.time.Time.TimeInterval interval() default annotations.time.Time.TimeInterval.MILLISECOND;
String format() default "Elapsed %s" ;
>
* This source code was highlighted with Source Code Highlighter .
Поле interval служит для указания интервала времени (миллисекунды или наносекунды), поле format задаёт формат вывода результата.
Теперь, чтобы данная аннотация сработала как надо, нужно создать класс-обработчик расширяющий AbstractProcessor. В данном классе добавляется сохранение времени перед кодом метода, сам код метода копируется в блок try-finally, а блоке finally вычисляется затраченное методом время и выводится в консоль:
import com.sun.tools.javac.code.Flags;
import com.sun.tools.javac.code.TypeTags;
import com.sun.tools.javac.model.JavacElements;
import com.sun.tools.javac.processing.JavacProcessingEnvironment;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.JCTree.JCBlock;
import com.sun.tools.javac.tree.JCTree.JCCatch;
import com.sun.tools.javac.tree.JCTree.JCExpression;
import com.sun.tools.javac.tree.JCTree.JCExpressionStatement;
import com.sun.tools.javac.tree.JCTree.JCMethodDecl;
import com.sun.tools.javac.tree.JCTree.JCStatement;
import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
import com.sun.tools.javac.tree.TreeMaker;
import com.sun.tools.javac.util. List ;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.Elements;
@SupportedAnnotationTypes( value = )
@SupportedSourceVersion(SourceVersion.RELEASE_6)
public class TimeAnnotationProcessor extends AbstractProcessor
public static final String ANNOTATION_TYPE = "annotations.time.Time" ;
private JavacProcessingEnvironment javacProcessingEnv;
private TreeMaker maker;
@Override
public void init(ProcessingEnvironment procEnv) super.init(procEnv);
this .javacProcessingEnv = (JavacProcessingEnvironment) procEnv;
this .maker = TreeMaker.instance(javacProcessingEnv.getContext());
>
@Override
public boolean process(Set annotations, RoundEnvironment roundEnv) if (annotations == null || annotations.isEmpty()) return false ;
>
final Elements elements = javacProcessingEnv.getElementUtils();
final TypeElement annotation = elements.getTypeElement(ANNOTATION_TYPE);
if (annotation != null ) // Выбираем все элементы, у которых стоит наша аннотация
final Set methods = roundEnv.getElementsAnnotatedWith(annotation);
JavacElements utils = javacProcessingEnv.getElementUtils();
for (final Element m : methods) Time time = m.getAnnotation(Time. class );
if (time != null ) JCTree blockNode = utils.getTree(m);
// Нам нужны только описания методов
if (blockNode instanceof JCMethodDecl) // Получаем содержимое метода
final List statements = ((JCMethodDecl) blockNode).body.stats;
// Новое тело метода
List newStatements = List .nil();
// Добавляем в начало метода сохранение текущего времени
JCVariableDecl var = makeTimeStartVar(maker, utils, time);
newStatements = newStatements.append( var );
// Создаём тело блока try, копируем в него оригинальное содержимое метода
List tryBlock = List .nil();
for (JCStatement statement : statements) tryBlock = tryBlock.append(statement);
>
// Создаём тело блока finally, добавляем в него вывод затраченного времени
JCBlock finalizer = makePrintBlock(maker, utils, time, var );
JCStatement stat = maker.Try(maker.Block(0, tryBlock), List .nil(), finalizer);
newStatements = newStatements.append(stat);
// Заменяем старый код метода на новый
((JCMethodDecl) blockNode).body.stats = newStatements;
>
>
>
private JCExpression makeCurrentTime(TreeMaker maker, JavacElements utils, Time time) // Создаём вызов System.nanoTime или System.currentTimeMillis
JCExpression exp = maker.Ident(utils.getName( "System" ));
String methodName;
switch (time.interval()) case NANOSECOND:
methodName = "nanoTime" ;
break ;
default :
methodName = "currentTimeMillis" ;
break ;
>
exp = maker.Select(exp, utils.getName(methodName));
return maker.Apply( List .nil(), exp, List .nil());
>
protected JCVariableDecl makeTimeStartVar(TreeMaker maker, JavacElements utils, Time time) // Создаём финальную переменную для хранения времени старта. Имя переменной в виде time_start_
JCExpression currentTime = makeCurrentTime(maker, utils, time);
String fieldName = fieldName = "time_start_" + ( int ) ( Math .random() * 10000);
return maker.VarDef(maker.Modifiers(Flags.FINAL), utils.getName(fieldName), maker.TypeIdent(TypeTags.LONG), currentTime);
>
protected JCBlock makePrintBlock(TreeMaker maker, JavacElements utils, Time time, JCVariableDecl var ) // Создаём вызов System.out.println
JCExpression printlnExpression = maker.Ident(utils.getName( "System" ));
printlnExpression = maker.Select(printlnExpression, utils.getName( "out" ));
printlnExpression = maker.Select(printlnExpression, utils.getName( "println" ));
// Создаём блок вычисления затраченного времени (currentTime - startTime)
JCExpression currentTime = makeCurrentTime(maker, utils, time);
JCExpression elapsedTime = maker.Binary(JCTree.MINUS, currentTime, maker.Ident( var .name));
// Форматируем результат
JCExpression formatExpression = maker.Ident(utils.getName( "String" ));
formatExpression = maker.Select(formatExpression, utils.getName( "format" ));
// Собираем все кусочки вместе
List formatArgs = List .nil();
formatArgs.append(maker.Literal(time.format()));
formatArgs.append(elapsedTime);
JCExpression format = maker.Apply( List .nil(), formatExpression, formatArgs);
List printlnArgs = List .nil();
printlnArgs.append(format);
JCExpression print = maker.Apply( List .nil(), printlnExpression, printlnArgs);
JCExpressionStatement stmt = maker.Exec(print);
List stmts = List .nil();
stmts.append(stmt);
return maker.Block(0, stmts);
>
>
* This source code was highlighted with Source Code Highlighter .
Для того чтобы компилятор java использовал наш управляющий класс, нужно создать файл META-INF/javax.annotation.processing.Processor, в котором должна быть прописана следующая строка:
annotations.time.TimeAnnotationProcessor
После этого собираем все наши файлы в annotations.jar и добавляем его в classpath к любому проекту.
Теперь, чтобы посчитать время выполнения метода, достаточно добавить к этому методу аннотацию Time и после его выполнения в консоль будет выведено затраченное методом время:
@Time(format= "method time: %s ms" )
public void start() Thread.sleep(1000);
>
* This source code was highlighted with Source Code Highlighter .
В результате в консоли увидим:
method time: 1000 ms
UPD: добавил немного комментариев в код
UPD2: Так же можно переработать пример, чтобы он выводил время затраченное методом в System.err только если оно больше заданного в параметрах аннотации. Это могло бы быть полезно для production серверов, где пользоваться профайлером не всегда удобно.
Зачастую требуется узнать, сколько времени выполняется тот или иной код. Иногда требуется замерить время выполнения метода или какой-то задачи. В данной статье мы расскажем вам принципы замера времени в Java и покажем лучшие практики для конкретных задач.
Замер времени с помощью currentTimeMills()
Это довольно простой способ измерить время. Метод System.currentTimeMillis() вернёт вам текущее время в миллисекундах. Его потребуется вызвать до выполнения нужной задачи и после, а затем вычислить разницу. В итоге мы узнаем время выполнения в миллисекундах:
При работе с данным методом следует учитывать его специфику: System.currentTimeMillis() показывает текущее время, основываясь на системных часах и может выдавать некорректный результат.
Замер времени с помощью nanoTime()
Ещё один метод для получения текущего времени это System.nanoTime(). Как следует из названия, этот метод возвращает время с точностью до нансекунд. Также работа этого метода не зависит от системных часов.
Он используется аналогично:
Для получения значения в миллисекундах результат можно разделить на 1000:
Важно: Хотя метод nanoTime() может возвращать время в наносекундах, он не гарантирует, что значения между его вызовами будут обновляться с точностью до наносекунд.
Но всё же это более приемлемый вариант, чем System.currentTimeMillis().
Замер времени с помощью Instant и Duration
В Java 8 добавили новый java.time API. В частности, ля измерения времени подойдут два новых класса – Instant и Duration. Оба эти класса иммутабельны.
Instant обозначает момент времени с начала эпохи Unix (1970-01-01T00:00:00Z). Для создания момента мы используем метод Instant.now(). После того, как мы создали два момент, вычислим разницу в миллисекундах:
Рекомендуется использовать именно этот подход в Java 8 и выше.
Замер времени выполнения с помощью StopWatch
StopWatch – это класс из библиотеки Apache Commons Lang. Он работает как секундомер. Для его использования сначала требуется подключить библиотеку к проекту:
Теперь создадим экземпляр StopWatch. Затем начнём отсчёт с помощью метода start() и окончим отсчёт с помощью метода stop():
StopWatch удобно использовать тогда, когда в проекте уже подключена данная библиотека.
Исходный код
Заключение
В данной статье мы разобрали простые методы замера времени выполнения в Java. Для простых замеров можно использовать все вышеперечисленные методы, кроме currentTimeMillis (из-за того, что он зависит от системных часов).
Most of the searches on Google return results for timers that schedule threads and tasks, which is not what I want.
You might also want to look at the Apache Commons Lang StopWatch class. A simple but useful utility class.
42 Answers 42
There is always the old-fashioned way:
This (or using System.currentTimeMillis()) seems to be the way it's usually done in Java. that I've seen anyway. It still mildly suprises me that there's no spiffy built-in class, like Timer t = new Timer(); String s = t.getElapsed(format); etc.
I go with the simple answer. Works for me.
It works quite well. The resolution is obviously only to the millisecond, you can do better with System.nanoTime(). There are some limitations to both (operating system schedule slices, etc.) but this works pretty well.
Average across a couple of runs (the more the better) and you'll get a decent idea.
Actually, System.currentTimeMillis() is only accurate above 15ms. For really low values it can't be trusted. The solution for this (as mentioned) is System.nanoTime();
Ok, I was about to accept this as the official answer until I read Steve g's comment. Great tidbit, Steve!
nanoTime() does not guarantee accuracy better than currentTimeMillis, but many JVM implementations do have better accuracy with nanoTime.
The one slight advantage of currentTimeMillis is that it's an actual timestamp, and could be used to log start/end times as well, while nanoTime "can only be used to measure elapsed time and is not related to any other notion of system or wall-clock time."
Come on guys! Nobody mentioned the Guava way to do that (which is arguably awesome):
The nice thing is that Stopwatch.toString() does a good job of selecting time units for the measurement. I.e. if the value is small, it'll output 38 ns, if it's long, it'll show 5m 3s
Note: Google Guava requires Java 1.6+
Using stopwatch in parallel would lead to you calling start() multiple times in a row (same for stop() ).
Using Instant and Duration from Java 8's new API,
@java123999: You can call Duration.between(start, end).getSeconds() . Duration also has methods to convert to other time units, e.g. toMillis() which converts to milliseconds.
Gathered all possible ways together into one place.
Human Readable Format
Guava: Google Stopwatch JAR « An object of Stopwatch is to measures elapsed time in nanoseconds.
Apache Commons Lang JAR « StopWatch provides a convenient API for timings.
JODA-TIME
Java date time API from Java 8 « A Duration object represents a period of time between two Instant objects.
Spring Framework provides StopWatch utility class to measure elapsed time in Java.
Stopwatch of Guava, Apache Commons and Spring Framework are not thread safe. Not safe for production usage.
Use a profiler (JProfiler, Netbeans Profiler, Visual VM, Eclipse Profiler, etc). You'll get the most accurate results and is the least intrusive. They use the built-in JVM mechanism for profiling which can also give you extra information like stack traces, execution paths, and more comprehensive results if necessary.
When using a fully integrated profiler, it's faily trivial to profile a method. Right click, Profiler -> Add to Root Methods. Then run the profiler just like you were doing a test run or debugger.
This was also a great suggestion, and one of those "duh" light-bulb moments for me when I read this answer. Our project uses JDeveloper, but I checked, and sure enough, it's got a built-in profiler!
From java 7 build 40 (i think) they included the former JRockits Flight Recorder to java (search for Java Mission Control)
System.currentTimeMillis(); IS NOT a good approach for measuring the performance of your algorithms. It measures the total time you experience as a user watching the computer screen. It includes also time consumed by everything else running on your computer in the background. This could make a huge difference in case you have a lot of programs running on your workstation.
Proper approach is using java.lang.management package.
- "User time" is the time spent running your application's own code.
- "System time" is the time spent running OS code on behalf of your application (such as for I/O).
getCpuTime() method gives you sum of those:
This is definitely a good point, that "user time" (wall-clock time) is not always a great measure of performance, especially in a multi-threaded program.
This probably isn't what you wanted me to say, but this is a good use of AOP. Whip an proxy interceptor around your method, and do the timing in there.
The what, why and how of AOP is rather beyond the scope of this answer, sadly, but that's how I'd likely do it.
Edit: Here's a link to Spring AOP to get you started, if you're keen. This is the most accessible implementation of AOP that Iive come across for java.
Also, given everyone else's very simple suggestions, I should add that AOP is for when you don't want stuff like timing to invade your code. But in many cases, that sort of simple and easy approach is fine.
With Java 8 you can do also something like this with every normal methods:
with TimeIt like:
With this methode you can make easy time measurement anywhere in your code without breaking it. In this simple example i just print the time. May you add a Switch for TimeIt, e.g. to only print the time in DebugMode or something.
If you are working with Function you can do somthing like this:
This looks much better than other solutions. Its closer to Spring AOP yet lighter than that. True java 8 way! +1 Thanks!
Maybe this looks good to you, because Stefan is using fancy new java functions. But I think this is needlesly difficult to read and understand.
Also We can use StopWatch class of Apache commons for measuring the time.
Add a basic suite of microbenchmarks to the JDK source code, and make it easy for developers to run existing microbenchmarks and create new ones.
This feature arrived in Java 12.
For earlier versions of Java, take a look at the Java Microbenchmark Harness (JMH) project on which JEP 230 is based.
Just a small twist, if you don't use tooling and want to time methods with low execution time: execute it many times, each time doubling the number of times it is executed until you reach a second, or so. Thus, the time of the Call to System.nanoTime and so forth, nor the accuracy of System.nanoTime does affect the result much.
Of course, the caveats about using the wall clock apply: influences of JIT-compilation, multiple threads / processes etc. Thus, you need to first execute the method a lot of times first, such that the JIT compiler does its work, and then repeat this test multiple times and take the lowest execution time.
We are using AspectJ and Java annotations for this purpose. If we need to know to execution time for a method, we simple annotate it. A more advanced version could use an own log level that can enabled and disabled at runtime.
Really good code.
Actually the question was how to calculate the amount of time a method takes, not how to format it. However this question is quite old (almost four years!). Try to avoid resurrecting old threads unless the response will add something new and significant over existing responses.
And to add remaining millis to the end, make the following changes: long millis = TimeUnit.MILLISECONDS.toMillis(duration) - TimeUnit.SECONDS.toMillis(TimeUnit.MILLISECONDS.toSeconds(duration)); if (days == 0) < res = String.format("%02d:%02d:%02d.%02d", hours, minutes, seconds, millis); >else < res = String.format("%dd%02d:%02d:%02d.%02d", days, hours, minutes, seconds, millis); >
Это вводная статья про то, как следует делать тесты производительности на JVM языках (java, kotlin, scala и тд.). Она полезна для случая, когда требуется в цифрах показать изменение производительности от использования определенного алгоритма.
Все примеры приведены на языке kotlin и для системы сборки gradle. Исходный код проекта доступен на github.
Подготовка
В первую очередь остановимся на основной части наших замеров — использовании JMH. Java Microbenchmark Harness — набор библиотек для тестирования производительности небольших функций (то есть тех, где пауза GC увеличивает время работы в разы).
Перед запуском теста JMH перекомпилирует код, так как:
- Для уменьшения погрешности вычисления времени работы функции необходимо запустить её N раз, подсчитать общее время работы, а потом поделить его на N.
- Для этого требуется обернуть запуск в виде цикла и вызова необходимого метода. Однако в этом случае на время работы функции повлияет сам цикл, а также сам вызов замеряемой функции. А потому вместо цикла будет вставлен непосредственно код вызова функции, без reflection или генерации методов в runtime.
После переделки байткода тестирование можно запустить командой вида java -jar benchmarks.jar , так как все необходимые компоненты уже будут запакованы в один jar файл.
JMH Gradle Plugin
Как понятно из описания выше, для тестирования производительности кода недостаточно просто добавить необходимые библиотеки в classpath и запустить тесты в стиле JUnit. А потому, если мы хотим делать дело, а не разбираться в особенности написания билд скриптов, нам не обойтись без плагина к maven/gradle. Для новых проектов преимущество остается за gradle, потому выбираем его.
Для JMH есть полуофициальный плагин для gradle — jmh-gradle-plugin. Добавляем его в проект:
Плагин автоматом создаст новый source set (это "набор файлов и ресурсов, которые должны компилироваться и запускаться вместе", прочитать можно или статью на хабре за авторством svartalfar, или же в официальной документации gradle). jmh source set автоматически ссылается на main, то есть получаем короткий алгоритм работы:
- Код, который мы будем изменять, пишем в стандартном main source set, там же, где и всегда.
- Код с настройкой и прогревом тестов пишем в отдельном source set. Именно его byte code и будет перезаписываться, сюда плагин добавит необходимые зависимости, в которых есть определения аннотация и тд.
Получаем следующую иерархию каталогов:
Или как это выглядит в IntelliJ Idea:
В итоге, после настройки проекта, можно запускать тесты простым вызовом .\gradlew.bat jmh (или .\gradlew jmh для Linux, Mac, BSD)
С плагином есть пара интересных особенностей на Windows:
Тестирование
В качестве примера я возьму вопрос (ранее заданный на kotlin discussions), который мучал меня ранее — зачем в конструкции use используется inline метод?
Пример java кода:
В kotlin есть полный аналог, который имеет немного другой синтаксис:
То есть, как видно:
- Use — это просто метод-расширение, а не отдельная конструкция языка
- Use является inline методом, то есть одни и те же конструкции встраиваются в каждый метод, что увеличивает размер байткода, а значит в теории JIT`у будет сложнее оптимизировать код и т.д. И вот эту теорию мы и будем проверять.
Итак, необходимо сделать два метода:
- Первый будет просто использовать use, который поставляется в библиотеке kotlin
- Второй будет использовать те же методы, однако без inline. В итоге на каждый вызов в куче будет создаваться объект с параметрами для лямбды.
Код с JMH аттрибутами, который будет запускать разные функции:
Dead Code Elimination
Java Compiler & JIT довольно умные и имеют ряд оптимизаций, как в compile time, так и в runtime. Метод ниже, например, вполне может свернуться в одну строку (как для kotlin, так и для java):
И в итоге мы будем тестировать метод:
Однако результат ведь никак не используется, потому компиляторы (byte code + JIT) в итоге вообще выкинут метод, так как он в принципе не нужен.
Чтобы избежать этого, в JMH существует специальный класс "черная дыра" — Blackhole. В нем есть методы, которые с одной стороны не делают ничего, а с другой стороны — не дают JIT выкинуть ветку с результатом.
А для того, чтобы javac не пытался сложить-таки a и b в процессе компиляции, нам требуется определить объект state, в котором будут храниться наши значения. В итоге в самом тесте мы будем использовать уже подготовленный объект (то есть не тратим время на его создание и не даем компилятору применить оптимизации).
В итоге для грамотного тестирования нашей функции требуется её написать вот в таком виде:
Здесь мы взяли a и b из некоторого state, что помешает компилятору сразу посчитать выражение. А результат мы отправили в черную дыру, что помешает JIT выкинуть последнюю часть функции.
Возвращаясь к моей функции:
- Объект для вызова метода close я создам в самом тесте, так как практически всегда при вызове метода close у нас до этого создавался объект.
- Внутри нашего метода придется вызывть функцию из blackhole, чтобы спровоцировать создание лямбды в куче (и не дать JIT выкинуть потенциально ненужный код).
Результат теста
Запустив ./gradle jmh , а потом подождав два часа, я получил следующие результаты работы на моем mac mini:
Или, если сократить таблицу:
В результате есть две самые важные метрики:
- Inline метод показал производительность 11,6 * 10^6 ± 0,02 * 10^6 операций в секунду.
- Lambda-based метод показал производительность 11,5 * 10^6 ± 0,04 * 10^6 операций в секунду.
- Inline метод в итоге работает быстрее и стабильнее по скорости. Возможно, увеличенная погрешность для lambdaUse связана с более активной работой с памятью.
- Я таки был неправ на том форуме — в стандартной библиотеке kotlin лучше оставить текущую реализацию метода.
Заключение
При разработке ПО есть два довольно частых способа сравнения производительности:
- Замер скорости работы цикла с N итерациями подопытной функции.
- Философские рассуждения вида "я уверен, что быстрее использовать сдвиг, чем умножение на 2", "сколько я программирую, всегда XML сериализация была самой быстрой" и тд.
Однако, как знает любой технически подкованный специалист, оба этих варианта зачастую приводят к ошибочным суждениям, тормозам в приложениях и пр. Я надеюсь, что эта статья поможет вам делать хорошее и быстрое ПО.
Итак, предположим, что мы захотели набрать бутылку воды. В наличии имеются бутылка и кран с водой дяди Пети. Дяде Пете сегодня установили новый кран, и он без умолку нахваливал его красоту. До этого он пользовался только старым засорившимся краном, поэтому очереди на разливе были колоссальные. Немного повозившись, со стороны разлива послышался звук набирающейся воды, спустя 2 минуты бутылка все еще находится в стадии наполнения, за нами собралась привычная очередь, а в голове рисуется образ того, как заботливый дядя Петя отбирает только лучшие молекулы H2O в нашу бутылку. Обученный жизнью дядя Петя успокаивает особо агрессивных и обещает закончить как можно быстрее. Покончив с бутылкой, он берет следующую и включает привычный напор, не раскрывающий всех возможностей нового крана. Люди не довольны…
Теория
- MIN_PRIORITY
- NORM_PRIORITY (default)
- MAX_PRIORITY
- run() – выполняет поток
- start() – запускает поток
- getName() – возвращает имя потока
- setName() – задает имя потока
- wait() – наследуемый метод, поток ожидает вызова метода notify() из другого потока
- notify() – наследуемый метод, возобновляет ранее остановленный поток
- notifyAll() – наследуемый метод, возобновляет ранее остановленные потоки
- sleep() – приостанавливает поток на заданное время
- join() – ждет завершения потока
- interrupt() – прерывает выполнение потока
- Доступ к сети
- Доступ к файловой системе
- GUI
Класс Thread
Потоки в Java представлены в виде класса Thread и его наследников. Приведенный ниже пример является простейшей реализацией потокового класса. В результате получим Здесь мы создаем наш класс и делаем его наследником класса Thread , после чего пишем метод main() для запуска основного потока и переопределяем метод run() класса Thread . Теперь создав экземпляр нашего класса и выполнив его унаследованный метод start() мы запустим новый поток, в котором выполнится все что описано в теле метода run() . Звучит сложно, но взглянув на код примера все должно быть понятно.
Интерфейс Runnable
Oracle также предлагает для запуска нового потока реализовывать интерфейс Runnable , что дает нам большую гибкость в разработке, чем единственное доступное наследование в предыдущем примере (если заглянуть в исходники класса Thread можно увидеть, что он также реализует интерфейс Runnable ). Применим рекомендуемый метод создания нового потока. В результате получим Примеры очень похожи, т.к. при написании кода нам пришлось реализовать абстрактный метод run() , описанный в интерфейсе Runnable . Запуск же нового потока немного отличается. Мы создали экземпляр класса Thread , передав в качестве параметра ссылку на экземпляр нашей реализации интерфейса Runnable . Именно такой подход позволяет создавать новые потоки без прямого наследования класса Thread .
Долгие операции
Следующий пример наглядно покажет преимущества использования нескольких потоков. Допустим у нас есть простая задача, требующая нескольких длительных вычислений, до этой статьи мы решали бы ее в методе main() возможно разбив для удобства восприятия на отдельные методы, может быть даже классы, но суть была бы одна. Все операции совершались бы последовательно одна за другой. Давайте смоделируем тяжеловесные вычисления и замерим время их выполнения. В результате получим Время выполнения оставляет желать лучшего, а мы все это время смотрим на пустой экран вывода, и ситуация очень смахивает на историю про дядю Петю только теперь в его роли мы, разработчики, не воспользовавшиеся всеми возможностями современных устройств. Будем исправляться. В результате получим Время работы значительно сократилось (этот эффект может быть не достигнут или и вовсе увеличить время выполнения на процессорах не поддерживающих многопоточность). Стоит заметить, что потоки могут завершатся не по порядку, и если разработчику необходима предсказуемость действий он должен реализовать ее самостоятельно под конкретный случай.
Группы потоков
Потоки в Java можно объединять в группы, для этого используется класс ThreadGroup . В состав групп могут входить как одиночные потоки, так и целые группы. Это может оказаться удобным если вам надо прервать потоки связанные, например, с сетью при обрыве соединения. Подробнее о группах можно почитать здесь Надеюсь теперь тема стала вам более понятной и ваши пользователи будут довольны.
Читайте также: