English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
Java предоставляет встроенную поддержку для многоядерной программирования. Одна нить указывает на единственный поток управления в процессе, в одном процессе может быть параллельно несколько нитей, которые выполняют различные задачи.
Многоядерные потоки - это особая форма многозадачности, но они используют меньше ресурсов.
Здесь определен еще один термин, связанный с потоками - процесс: процесс включает в себя память, выделенную операционной системой, и содержит один или несколько потоков. Поток не может существовать самостоятельно, он должен быть частью процесса. Процесс продолжает работать до тех пор, пока все непостоянные потоки не завершат свою работу.
Многоядерные потоки позволяют программистам пишите эффективные программы для максимального использования CPU.
Поток - это динамический процесс выполнения, у него также есть процесс от возникновения до смерти.
На рисунке показан полный жизненный цикл потока.
Состояние создания:
используется new ключевое слово и Thread Класс или его подкласс создает объект потока, и этот объект потока переходит в состояние создания. Он остается в этом состоянии до тех пор, пока программа start() этот поток.
Состояние готовности:
После вызова метода start() объект потока переходит в состояние готовности. Потоки в состоянии готовности находятся в очереди готовности и ждут调度 потока JVM.
Состояние выполнения:
Если поток в состоянии готовности получает ресурсы CPU, он может выполнить run()в этот момент поток находится в состоянии выполнения. Потоки в состоянии выполнения наиболее сложны, они могут переходить в состояния блокировки, готовности и завершения.
Состояние блокировки:
Если поток выполняет методы sleep (сон), suspend (приостановка) и т.д., после потери所占ываемых ресурсов поток переходит из состояния выполнения в состояние блокировки. После истечения времени сна или получения ресурсов устройства поток может вернуться в состояние готовности. Это можно разделить на три типа:
Блокировка ожидания: поток в состоянии выполнения выполняет метод wait(), чтобы перейти в состояние блокировки ожидания.
Синхронная блокировка: поток не может получить блокировку synchronized (поскольку блокировка занята другим потоком).
Другие блокировки: когда вызывается метод sleep() или join() и выполняется запрос I/O, поток переходит в состояние блокировки. Когда время ожидания sleep() истекает, ожидание завершения потока join() или истечение времени ожидания, или завершение обработки I/O, поток возвращается в состояние готовности.
Состояние смерти:
Если поток в состоянии выполнения завершает задачу или наступают другие условия завершения, поток переходит в состояние завершения.
Каждый поток Java имеет приоритет, что помогает операционной системе определить порядок调度 потоков.
Порядок приоритета потоков Java - это целое число, значение которого варьируется от 1 (Thread.MIN_PRIORITY) до 10 (Thread.MAX_PRIORITY).
По умолчанию, каждому потоку назначается приоритет NORM_PRIORITY (5).
Текущие потоки с более высокой приоритетностью важнее для программы и должны быть распределены ресурсы процессора до потоков с низким приоритетом. Однако, приоритет потока не гарантирует порядок выполнения потока, и он очень зависит от платформы.
Java предоставляет три способа создания потоков:
Создание потока через реализацию интерфейса Runnable
Создание потока через наследование от класса Thread
Создание потока через Callable и Future
Самый простой способ создания потока - это создание класса, реализующего интерфейс Runnable.
Чтобы реализовать интерфейс Runnable, классу нужно выполнить только один вызов метода run(), как показано ниже:
public void run()
Вы можете переопределить этот метод, важно понимать, что run() может вызывать другие методы, использовать другие классы и объявлять переменные, как и в главном потоке.
После создания класса, реализующего интерфейс Runnable, вы можете создать объект потока в этом классе.
Класс Thread определяет несколько конструкторов, один из которых мы часто используем:
Thread(Runnable threadOb, String threadName);
Здесь threadOb - это пример класса, реализующего интерфейс Runnable, и threadName определяет имя нового потока.
После создания нового потока, вы должны вызвать его метод start(), чтобы он начал работать.
void start();
Ниже приведен пример создания потока и начала его выполнения:
class RunnableDemo implements Runnable { private Thread t; private String threadName; RunnableDemo(String name) { threadName = name; System.out.println("Создание " + threadName); {} public void run() { System.out.println("Запуск " + threadName); try { for (int i = 4; i > 0; i--) { System.out.println("Thread: " + threadName + ", " + i); // Позвольте потоку поспать немного catch (InterruptedException e) { {} } System.out.println("Thread " + threadName + " прерван."); {} System.out.println("Thread " + threadName + " выходит."); {} public void start() { System.out.println("Запуск " + threadName); if (t == null) { t = new Thread(this, threadName); t.start(); {} {} {} public class TestThread { public static void main(String args[]) { RunnableDemo R1 = new RunnableDemo("Thread-1"); R1.start(); RunnableDemo R2 = new RunnableDemo("Thread-2"); R2.start(); {} {}
Результат выполнения программы, скомпилированной выше, таков:
Создание потока-1 Запуск потока-1 Создание потока-2 Запуск потока-2 Running Thread-1 Thread: Thread-1, 4 Running Thread-2 Thread: Thread-2, 4 Thread: Thread-1, 3 Thread: Thread-2, 3 Thread: Thread-1, 2 Thread: Thread-2, 2 Thread: Thread-1, 1 Thread: Thread-2, 1 Thread Thread-1 exiting. Thread Thread-2 exiting.
Второй способ создания потока - это создание нового класса, который наследуется от класса Thread, и создание экземпляра этого класса.
Класс-наследник должен переопределить метод run(), который является точкой входа для нового потока. Он также должен вызывать метод start(), чтобы начать выполнение.
Этот метод, хотя и перечислен как один из способов реализации многопоточности, по сути является примером, реализующим интерфейс Runnable.
class ThreadDemo extends Thread { private Thread t; private String threadName; ThreadDemo(String name) { threadName = name; System.out.println("Создание " + threadName); {} public void run() { System.out.println("Запуск " + threadName); try { for (int i = 4; i > 0; i--) { System.out.println("Thread: " + threadName + ", " + i); // Позвольте потоку поспать немного catch (InterruptedException e) { {} } System.out.println("Thread " + threadName + " прерван."); {} System.out.println("Thread " + threadName + " выходит."); {} public void start() { System.out.println("Запуск " + threadName); if (t == null) { t = new Thread(this, threadName); t.start(); {} {} {} public class TestThread { public static void main(String args[]) { ThreadDemo T1 = new ThreadDemo("Thread-1"); T1.start(); ThreadDemo T2 = new ThreadDemo("Thread-2"); T2.start(); {} {}
Результат выполнения программы, скомпилированной выше, таков:
Создание потока-1 Запуск потока-1 Создание потока-2 Запуск потока-2 Running Thread-1 Thread: Thread-1, 4 Running Thread-2 Thread: Thread-2, 4 Thread: Thread-1, 3 Thread: Thread-2, 3 Thread: Thread-1, 2 Thread: Thread-2, 2 Thread: Thread-1, 1 Thread: Thread-2, 1 Thread Thread-1 exiting. Thread Thread-2 exiting.
В таблице перечислены некоторые важные методы класса Thread:
Номер | Описание метода |
---|---|
1 | public void start() Включить выполнение этого потока.;Java Виртуальная машина вызывает метод run потока. |
2 | public void run() Если поток был создан с использованием независимого объекта Runnable, вызывается метод run объекта Runnable; в противном случае,该方法 не выполняет никаких действий и возвращает. |
3 | public final void setName(String name) Изменить имя потока, чтобы оно соответствовало параметру name. |
4 | public final void setPriority(int priority) Изменить приоритет потока. |
5 | public final void setDaemon(boolean on) Отметить этот поток как демонстративный поток или пользовательский поток. |
6 | public final void join(long millisec) Ожидать завершения этого потока в течение longest of millis миллисекунд. |
7 | public void interrupt() Интеррумпировать поток. |
8 | public final boolean isAlive() Тестировать, находится ли поток в активном состоянии. |
Тестировать, находится ли поток в активном состоянии. Указанные методы вызываются объектом Thread. Следующие методы являются статическими методами класса Thread.
Номер | Описание метода |
---|---|
1 | public static void yield() Приостановить текущий выполняющийся поток объекта и выполнить другой поток. |
2 | public static void sleep(long millisec) В течение указанного миллисекундного интервала позволяет текущий выполняющийся поток休眠(приостановить выполнение), эта операция зависит от точности и точности системного таймера и планировщика. |
3 | public static boolean holdsLock(Object x) Возвращает true, только если текущий поток удерживает монитор на指定的 объекте. |
4 | public static Thread currentThread() Возвращает ссылку на объект потока, который в настоящее время выполняется. |
5 | public static void dumpStack() Печатает трассировку стека текущего потока в стандартный поток ошибок. |
Программа ThreadClassDemo демонстрирует некоторые методы класса Thread:
// Имя файла: DisplayMessage.java // Создание потока через реализацию интерфейса Runnable public class DisplayMessage implements Runnable { private String message; public DisplayMessage(String message) { this.message = message; {} public void run() { while(true) { System.out.println(message); {} {} {}
// Имя файла: GuessANumber.java // Создание потока через наследование класса Thread public class GuessANumber extends Thread { private int number; public GuessANumber(int number) { this.number = number; {} public void run() { int counter = 0; int guess = 0; do { guess = (int) (Math.random() * 100 + 1); System.out.println(this.getName() + " попытки " + guess); counter++; } System.out.println("** Правильно!" + this.getName() + "в" + counter + "попытках.**"); {} {}
// 文件名 : ThreadClassDemo.java public class ThreadClassDemo { public static void main(String [] args) { Runnable hello = new DisplayMessage("Hello"); Thread thread1 = new Thread(hello); thread1.setDaemon(true); thread1.setName("hello"); System.out.println("Starting hello thread..."); thread1.start(); Runnable bye = new DisplayMessage("Goodbye"); Thread thread2 = new Thread(bye); thread2.setPriority(Thread.MIN_PRIORITY); thread2.setDaemon(true); System.out.println("Starting goodbye thread..."); thread2.start(); System.out.println("Starting thread3..."); Thread thread3 = new GuessANumber(27); thread3.start(); try { thread3.join(); }catch(InterruptedException e) { System.out.println("Thread interrupted."); {} System.out.println("Starting thread4..."); Thread thread4 = new GuessANumber(75); thread4.start(); System.out.println("main() is ending..."); {} {}
运行结果如下,每一次运行的结果都不一样。
Starting hello thread... Запуск потока прощания... Привет Привет Привет Привет Привет Привет Прощание Прощание Прощание Прощание Прощание ......
1. Создание класса, реализующего интерфейс Callable, и реализация метода call(), который будет выполняться в потоке и имеет возвращаемое значение.
2. Создание примера класса, реализующего интерфейс Callable, и использование класса FutureTask для обертки объекта Callable, который封装ирует возвращаемое значение метода call().
3. Использование объекта FutureTask в качестве目标是 создания и запуска нового потока.
4. Вызов метода get() объекта FutureTask для получения возвращаемого значения после завершения выполнения под线程а.
public class CallableThreadTest implements Callable<Integer> { public static void main(String[] args) { CallableThreadTest ctt = new CallableThreadTest(); FutureTask<Integer> ft = new FutureTask<>(ctt); for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName() + " " + " Worth of loop variable i " + i); if (i == 20) { new Thread(ft, "Струна с возвращаемым значением").start(); {} {} try { System.out.println("Возвратное значение под线程а: " + ft.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); {} {} @Override public Integer call() throws Exception { int i = 0; for (; i < 100; i++) { System.out.println(Thread.currentThread().getName() + " " + i); {} return i; {} {}
1. При создании многопоточности с помощью реализации интерфейсов Runnable, Callable, потоковый класс реализует интерфейс Runnable или Callable, и может наследовать другие классы.
2. При создании многопоточности с помощью наследования класса Thread, написание просто, если вам нужно получить текущий поток, вам не нужно использовать метод Thread.currentThread(), просто используйте this, чтобы получить текущий поток.
При параллельном программировании на многопоточности вам необходимо понять несколько концепций:
Синхронизация потоков
Коммуникация между потоками
Тяжелый deadlock потока
Управление потоками: приостановка, остановка и восстановление
Ключом к эффективному использованию многопоточности является понимание того, что программа выполняется параллельно, а не последовательно. Например: у программы есть две подсистемы, которые необходимо выполнять параллельно, в этом случае необходимо использовать параллельное программирование на многопоточности.
Использование многопоточности позволяет создавать очень эффективные программы. Однако обратите внимание, что если вы создадите слишком много потоков, эффективность выполнения программы实际上是 снижается, а не увеличивается.
Помните, также важна стоимость переключения контекста, если вы создадите слишком много потоков, время, затраченное на переключение контекста CPU, будет больше времени выполнения программы!