Многопоточность в Java — это одновременное выполнение двух или более потоков для максимального использования центрального процессора (CPU — central processing unit). Каждый поток работает параллельно и не требует отдельной области памяти. К тому же, переключение контекста между потоками занимает меньше времени.
Использование многопоточности:
Поток — наименьшее составляющее процесса. Потоки могут выполняться параллельно друг с другом. Их также часто называют легковесными процессами. Они используют адресное пространство процесса и делят его с другими потоками.
Потоки могут контролироваться друг друга и общаться посредством методов wait(), notify(), notifyAll().
Потоки могут пребывать в нескольких состояниях:
Приложение, создающее экземпляр класса Thread, должно предоставить код, который будет работать в этом потоке. Существует два способа, чтобы добиться этого:
public class HelloRunnable implements Runnable { public void run() { System.out.println("Hello from a thread!"); } public static void main(String args[]) { (new Thread(new HelloRunnable())).start(); } }
public class HelloThread extends Thread { public void run() { System.out.println("Hello from a thread!"); } public static void main(String args[]) { (new HelloThread()).start(); } }
Обратите внимание, что оба примера вызывают Thread.start, чтобы запустить новый поток.
Какой из способов выбрать? Первый — с использованием объекта Runnable — более общий, потому что этот объект может превратить отличный от Thread класс в подкласс. Этот способ более гибкий и может использоваться для высокоуровневых API управления потоками.
Второй способ больше подходит для простых приложений, но есть условие: класс задачи должен быть потомком Thread.
В Java процесс завершается тогда, когда завершаются все его основные и дочерние потоки.
Потоки-демоны — это низкоприоритетные потоки, работающие в фоновом режиме для выполнения таких задач, как сбор «мусора»: они освобождают память неиспользованных объектов и очищают кэш. Большинство потоков JVM (Java Virtual Machine) являются потоками-демонами.
Свойства потоков-демонов:
Чтобы установить, является ли поток демоном, используется метод boolean isDaemon(). Если да, то он возвращает значение true, если нет, то — то значение false.
Завершение потока Java требует подготовки кода реализации потока. Класс Java Thread содержит метод stop(), но он помечен как deprecated. Оригинальный метод stop() не дает никаких гарантий относительно состояния, в котором поток остановили. То есть, все объекты Java, к которым у потока был доступ во время выполнения, останутся в неизвестном состоянии. Если другие потоки в приложении имели доступ к тем же объектам, то они могут неожиданно «сломаться».
Вместо вызова метода stop() нужно реализовать код потока, чтобы его остановить. Приведем пример класса с реализацией Runnable, который содержит дополнительный метод doStop(), посылающий Runnable сигнал остановиться. Runnable проверит его и остановит, когда будет готов.
public class MyRunnable implements Runnable { private boolean doStop = false; public synchronized void doStop() { this.doStop = true; } private synchronized boolean keepRunning() { return this.doStop == false; } @Override public void run() { while(keepRunning()) { // keep doing what this thread should do. System.out.println("Running"); try { Thread.sleep(3L * 1000L); } catch (InterruptedException e) { e.printStackTrace(); } } } }
Обратите внимание на методы doStop() и keepRunning(). Вызов doStop() происходит не из потока, выполняющего метод run() в MyRunnable.
Метод keepRunning() вызывается внутренней потоком, выполняющим метод run() MyRunnable. Поскольку метод doStop() не вызван, метод keepRunning() возвратит значение true, то есть поток, выполняющий метод run(), продолжит работать.
Например:
public class MyRunnableMain { public static void main(String[] args) { MyRunnable myRunnable = new MyRunnable(); Thread thread = new Thread(myRunnable); thread.start(); try { Thread.sleep(10L * 1000L); } catch (InterruptedException e) { e.printStackTrace(); } myRunnable.doStop(); } }
В примере сначала создается MyRunnable, а затем передается потоку и запускает его. Поток, выполняющий метод main() (главный поток), засыпает на 10 секунд и потом вызывает метод doStop() экземпляра класса MyRunnable. Впоследствии поток, выполняющий метод MyRunnable, остановится, потому что после того, как вызван doStop(), keepRunning() возвратит false.
Обратите внимание, если для реализация Runnable нужен не только метод run() (а например, еще метод stop() или pause()), реализацию Runnable больше нельзя будет создать с помощью лямбда-выражений. Понадобится кастомный класс или интерфейс, расширяющий Runnable, который содержит дополнительные методы и реализуется анонимным классом.
Поток может остановиться сам, вызвав статический метод Thread.sleep(). Thread.sleep() принимает в качестве параметра количество миллисекунд. Метод sleep() попытается заснуть на это количество времени перед возобновлениям выполнения. Thread sleep() не гарантирует абсолютной точности.
Приведем пример остановки потока Java на 10 секунд (10 тысяч миллисекунд) с помощью вызова метода Thread sleep():
try { Thread.sleep(10L * 1000L); } catch (InterruptedException e) { e.printStackTrace(); }
Поток, выполняющий код, уснет примерно на 10 секунд.
Предотвратить выполнение потока можно методом yield(): предположим, существует три потока t1, t2, and t3. Поток t1 выполняется процессором, а потоки t2 и t3 находятся в состоянии Ready/Runnable. Время выполнения для потока t1 — 5 часов, а для t2 – 5 минут.
Поскольку t1 закончит свое выполнение через 5 часов, t2 придется ждать все это время, чтобы закончить 5-минутную задачу. В таких случаях, когда один поток требует слишком много времени, чтобы завершить свое выполнение, нужен способ приостановить выполнение длинного потока в промежутке, если какая-то важная задача не завершена. Тут и поможет yield ().
По сути, yield() означает, что поток не выполняет ничего особо важного, и если другие потоки или процессы требуют запуска, то их можно запустить.
Использование метода yield() :
Синтаксис:
public static native void yield() // Java program to illustrate yield() method // in Java import java.lang.*; // MyThread extending Thread class MyThread extends Thread { public void run() { for (int i=0; i<5 ; i++) System.out.println(Thread.currentThread().getName() + " in control"); } } // Driver Class public class yieldDemo { public static void main(String[]args) { MyThread t = new MyThread(); t.start(); for (int i=0; i<5; i++) { // Control passes to child thread Thread.yield(); // After execution of child Thread // main thread takes over System.out.println(Thread.currentThread().getName() + " in control"); } } }
Результат:
Thread-0 in control Thread-0 in control Thread-0 in control Thread-0 in control Thread-0 in control main in control main in control main in control main in control main in control
Метод join() экземпляра класса Thread используется для объединения начала выполнения одного потока с завершением выполнения другого потока. Это необходимо, чтобы один поток не начал выполняться до того, как завершится другой поток. Если метод join() вызывается на Thread, то выполняющийся в этот момент поток блокируется до момента, пока Thread не закончит выполнение.
Метод join() ждет не более указанного количества миллисекунд, пока поток умрет. Тайм-аут 0 (ноль) означает «ждать вечно».
Синтаксис:
public void join()throws InterruptedException
Например:
class TestJoinMethod1 extends Thread{ public void run(){ for(int i=1;i<=5;i++){ try{ Thread.sleep(500); }catch(Exception e){System.out.println(e);} System.out.println(i); } } public static void main(String args[]){ TestJoinMethod1 t1=new TestJoinMethod1(); TestJoinMethod1 t2=new TestJoinMethod1(); TestJoinMethod1 t3=new TestJoinMethod1(); t1.start(); try{ t1.join(); }catch(Exception e){System.out.println(e);} t2.start(); t3.start(); } }
Результат:
1 2 3 4 5 1 1 2 2 3 3 4 4 5 5
Из примера видно, что как только поток t1 завершает выполнение задачи, потоки t2 и t3 начинают выполнять свои задачи.
У каждого потока есть приоритет. Приоритет обозначается числом от 1 до 10. В большинстве случаев планировщик распределяет потоки относительно их приоритета (другими словами — происходит приоритетное планирование). Но это не гарантированно, поскольку он зависит от того, какое планирование выберет JVM.
Три константы, которые определяются в классе Thread:
1. public static int MIN_PRIORITY (значение равно 1);
2. public static int NORM_PRIORITY (дефолтный приоритет потока);
3. public static int MAX_PRIORITY (значение равно 10).
Пример приоритета потока:
class TestMultiPriority1 extends Thread{ public void run(){ System.out.println("running thread name is:"+Thread.currentThread().getName()); System.out.println("running thread priority is:"+Thread.currentThread().getPriority()); } public static void main(String args[]){ TestMultiPriority1 m1=new TestMultiPriority1(); TestMultiPriority1 m2=new TestMultiPriority1(); m1.setPriority(Thread.MIN_PRIORITY); m2.setPriority(Thread.MAX_PRIORITY); m1.start(); m2.start();
Результат:
running thread name is:Thread-0 running thread priority is:10 running thread name is:Thread-1 running thread priority is:1
Синтаксис:
final boolean isAlive(){ }
Например:
import java.lang.Thread; class IsThreadAlive extends Thread { public void run() { try { // this thread stops for few miliseconds before // run() method complete its execution Thread.sleep(500); // Display status of Current Thread is alive System.out.println("Is thread alive in run() method " + Thread.currentThread().isAlive()); } catch (Exception ex) { System.out.println(ex.getMessage()); } } public static void main(String[] args) { // creating an object of class named IsThreadAlive alive = new IsThreadAlive(); // Display status of thread is alive before // calling start() method System.out.println("Is thread alive before start() call:" + alive.isAlive()); alive.start(); // Display status of thread is alive after // calling start() method System.out.println("Is thread alive after start() call:" + alive.isAlive()); } }
Результат:
E:\Programs>javac IsThreadAlive.java E:\Programs>java IsThreadAlive Is thread alive before start() call:false Is thread alive after start() call:true Is thread alive in run() method true
Синтаксис:
public final void setName(String a)
Например:
public class SetNameExample extends Thread { public void run() { System.out.println("running..."); } public static void main(String args[]) { // creating two threads SetNameExample t1=new SetNameExample(); SetNameExample t2=new SetNameExample(); // start of thread t1.start(); t2.start(); // change the thread name t1.setName("Sonoo Jaiswal"); t2.setName("javatpoint"); // print the thread after changing System.out.println("After changing name of t1: "+t1.getName()); System.out.println("After changing name of t2: "+t2.getName()); } }
Результат:
After changing name of t1: Sonoo Jaiswal running... running... After changing name of t2: javatpoint
Имя потока – ассоциированная с ним строка, которая в некоторых случаях помогает понять, какой поток выполняет действие.
Синтаксис:
public final String getName()
Например:
public class GetNameExample extends Thread { public void run() { System.out.println("Thread is running..."); } public static void main(String args[]) { // creating two threads GetNameExample t1=new GetNameExample(); GetNameExample t2=new GetNameExample(); // return the name of threads System.out.println("Name of t1: "+ t1.getName()); System.out.println("Name of t2: "+t2.getName()); // start t1 and t2 threads t1.start(); t2.start(); } }
Результат:
Name of t1: Thread-0 Name of t2: Thread-1 Thread is running... Thread is running...
Синтаксис:
public String toString()
Например:
public class JavaToStringExp implements Runnable { Thread t; JavaToStringExp() { t = new Thread(this); // this will call run() function t.start(); } public void run() { // returns a string representation of this thread System.out.println(t.toString()); } public static void main(String[] args) { new JavaToStringExp(); } }
Результат:
Thread[Thread-0,5,main]
Синтаксис:
public static Thread currentThread()
Например:
public class CurrentThreadExp extends Thread { public void run() { // print currently executing thread System.out.println(Thread.currentThread().getName()); } public static void main(String args[]) { // creating two thread CurrentThreadExp t1=new CurrentThreadExp(); CurrentThreadExp t2=new CurrentThreadExp(); // this will call the run() method t1.start(); t2.start(); } }
Результат:
Thread-0 Thread-1
Синтаксис:
long getId(){ }
Например:
*/import java.lang.Thread; class GetThreadId extends Thread { // Override run() of Thread class public void run() { System.out.println("The priority of this thread is : " + Thread.currentThread().getPriority()); } public static void main(String[] args) { // Creating an object of GetThreadId class GetThreadId gt_id = new GetThreadId(); // Calling start() method with GetThreadId class // object of Thread class gt_id.start(); // Get name of a thread th and display on standard // output stream System.out.println("The name of this thread is " + " " + gt_id.getName()); // Get Id of a thread th and display on standard // output stream System.out.println("The Id of this thread is " + " " + gt_id.getId()); } }
Результат:
E:\Programs>javac GetThreadId.java E:\Programs>java GetThreadId The priority of this thread is :5 The name of this thread is Thread-0 The Id of this thread is 9
Прокси (proxy), или прокси-сервер — это программа-посредник, которая обеспечивает соединение между пользователем и интернет-ресурсом. Принцип…
Согласитесь, было бы неплохо соединить в одно сайт и приложение для смартфона. Если вы еще…
Повсеместное распространение смартфонов привело к огромному спросу на мобильные игры и приложения. Миллиарды пользователей гаджетов…
В перечне популярных чат-ботов с искусственным интеллектом Google Bard (Gemini) еще не пользуется такой популярностью…
Скрипт (англ. — сценарий), — это небольшая программа, как правило, для веб-интерфейса, выполняющая определенную задачу.…
Дедлайн (от англ. deadline — «крайний срок») — это конечная дата стачи проекта или задачи…