В многопоточном программировании и параллельной обработке мы часто слышим понятия процессов, потоков, сопрограмм, волокон и виртуальных потоков. Хотя они оба связаны с параллельным программированием, многие люди не понимают их различий и взаимосвязей. В этой статье будет представлен углубленный анализ различий и взаимосвязей между процессами, потоками, сопрограммами, волокнами и виртуальными потоками, чтобы помочь читателям лучше понять различные концепции параллельного программирования.
Процесс — это экземпляр программы, работающей на компьютере. Каждый процесс имеет собственное независимое пространство памяти и системные ресурсы и может иметь несколько потоков. Процессы независимы друг от друга. Они не могут обмениваться данными напрямую и должны обмениваться данными посредством межпроцессного взаимодействия (IPC).
Вот простой пример кода Java, показывающий, как создать процесс:
public class ProcessDemo {
public static void main(String[] args) {
ProcessBuilder processBuilder = new ProcessBuilder("myProgram.exe");
try {
Process process = processBuilder.start();
// Дождитесь завершения выполнения процесса
int exitCode = process.waitFor();
System.out.println("Выполнение процесса завершено, код выхода:" + exitCode);
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}
}
В приведенном выше коде,мы используемJavaизProcessBuilder
класс создает процесс,и выполнил вызов под названиемmyProgram.exe
программа。
Потоки относятся к независимым путям выполнения, выполняемым внутри процесса. Процесс может содержать несколько потоков, каждый поток выполняется независимо и имеет свой собственный порядок выполнения и статус.
К особенностям нитей относятся:
Подводя итог, можно сказать, что поток — это облегченная исполнительная единица, которая может выполняться одновременно и совместно использовать ресурсы процесса. Рационально используя потоки, мы можем полностью использовать вычислительную мощность компьютера и повысить эффективность выполнения и скорость ответа программы.
Поток — это исполнительная единица внутри процесса и базовая единица планирования ЦП. Каждый поток выполняется в контексте процесса и совместно использует пространство памяти и системные ресурсы процесса. Данные могут передаваться напрямую между потоками, поэтому взаимодействие между потоками становится более эффективным.
Создание потоков в Java может быть достигнуто двумя способами: наследованием класса Thread и реализацией интерфейса Runnable. Далее мы представим эти два метода один за другим.
Во-первых, давайте посмотрим, как создать поток, унаследовав класс Thread. Конкретные шаги заключаются в следующем:
public class MyThread extends Thread {
@Override
public void run() {
// Логика выполнения нити
}
}
MyThread myThread = new MyThread();
myThread.start();
Таким образом, мы можем создать новый поток и определить в нем логику выполнения потока.
Помимо наследования класса Thread, мы также можем создавать потоки, реализуя интерфейс Runnable. Конкретные шаги заключаются в следующем:
public class MyRunnable implements Runnable {
@Override
public void run() {
// Логика выполнения нити
}
}
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
thread.start();
Реализуя интерфейс Runnable, мы можем изолировать логику выполнения потока и улучшить возможность повторного использования и масштабируемость кода.
Уничтожение потока относится к процессу прекращения выполнения потока и освобождения связанных ресурсов. Уничтожение потоков в Java обычно выполняется автоматически, но мы также можем активно уничтожать потоки некоторыми способами.
Когда метод run() потока завершается или генерируется неперехваченное исключение, поток автоматически уничтожается. В этот момент статус потока изменится на ПРЕКРАЩЕНО.
Thread thread = new Thread(() -> {
// Логика выполнения нити
});
thread.start();
// Дождитесь окончания выполнения
thread.join();
// Определить, был ли уничтожен нить
if (thread.getState() == Thread.State.TERMINATED) {
System.out.println("Нить была уничтожена");
}
Иногда нам необходимо активно уничтожать потоки при определенных условиях. Java предоставляет некоторые методы для активного уничтожения потоков.
Мы можем установить бит флага в логике выполнения потока и решить, продолжать ли выполнение, проверив бит флага. Когда бит флага ложный, поток активно выйдет из цикла, тем самым достигнув активного уничтожения потока.
public class MyThread implements Runnable {
private volatile boolean isRunning = true;
@Override
public void run() {
while (isRunning) {
// Логика выполнения нити
}
}
public void stopThread() {
isRunning = false;
}
}
MyThread myThread = new MyThread();
Thread thread = new Thread(myThread);
thread.start();
// Активно уничтожать нить при определенных условиях
myThread.stopThread();
В приведенном выше примере мы добавили логический флаг isRunning в класс MyThread. В логике выполнения потока мы решаем, продолжать ли выполнение, проверяя этот бит флага. Когда нам нужно активно уничтожить поток, мы вызываем метод stopThread(), чтобы установить для isRunning значение false, чтобы поток вышел из цикла.
Другой способ активного уничтожения потоков — использование метода прерывания(). Этот метод прерывает выполнение потока и генерирует InterruptedException.
public class MyThread implements Runnable {
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
// Логика выполнения нити
}
}
}
MyThread myThread = new MyThread();
Thread thread = new Thread(myThread);
thread.start();
// Активно уничтожать нить при определенных условиях
thread.interrupt();
В приведенном выше примере мы использовали Thread.currentThread().isInterrupted() в логике выполнения потока, чтобы проверить, был ли поток прерван. Когда мы вызываем метод thread.interrupt(), метод isInterrupted() потока вернет true, заставляя поток выйти из цикла.
Прежде чем мы начнем, давайте рассмотрим основные понятия процессов и потоков. Процесс относится к экземпляру запущенной программы и имеет независимое пространство памяти и ресурсы. Поток — это исполнительная единица внутри процесса. Процесс может содержать несколько потоков, которые совместно используют пространство памяти и ресурсы процесса. В Java процессами управляет JVM, а потоки планируются операционной системой.
В операционной системе планирование процессов означает, что операционная система выбирает процесс из очереди готовности для распределения ресурсов ЦП в соответствии с определенной стратегией. За планирование процессов в Java отвечает операционная система, и мы не можем напрямую контролировать планирование процессов. Операционная система определяет, какой процесс получает время выполнения ЦП, на основе таких факторов, как приоритет процесса и алгоритм планирования.
Планирование потоков означает, что операционная система выбирает поток из очереди готовности для распределения ресурсов ЦП в соответствии с определенной стратегией. Java предоставляет некоторые механизмы, влияющие на планирование потоков.
У каждого нить есть приоритет,Используется для определения порядка выполнения при конкуренции за ресурсы ЦП. приоритет нить в Java варьируется от 1 до 10,По умолчанию5。Можно использоватьThread.setPriority()
методнастраиватьнитьизприоритет。
Thread thread1 = new Thread(() -> {
// Логика выполнения нить1
});
Thread thread2 = new Thread(() -> {
// Логика выполнения нит2
});
thread1.setPriority(Thread.MAX_PRIORITY); // Установите приоритет нить1 на самый высокий
thread2.setPriority(Thread.MIN_PRIORITY); // Установите приоритет нить2 на самый низкий
thread1.start();
thread2.start();
Приоритет потока является лишь подсказкой и не гарантирует выполнение в порядке приоритета. Конкретное планирование потоков определяется операционной системой.
ПозвонивThread.sleep()
метод,Можем перевести текущий нит в спящий режим,Сделайте паузу на некоторое время, прежде чем возобновить выполнение.
Thread thread = new Thread(() -> {
// Логика выполнения нити
try {
Thread.sleep(1000); // Спать 1 секунду
} catch (InterruptedException e) {
e.printStackTrace();
}
});
Сон потока можно использовать для моделирования некоторых сценариев, требующих ожидания, или для настройки временного интервала выполнения потока.
В многопоточном программировании выполнение между потоками происходит одновременно, и могут возникнуть некоторые проблемы с синхронизацией, такие как состояния гонки и взаимоблокировки. Java предоставляет некоторые механизмы, которые помогут нам решить эти проблемы.
критическая секция относится к фрагменту программы, области, которая может вызвать состояние гонки, когда несколько человек одновременно обращаются к фрагменту. Чтобы гарантировать критическое секцияиз Взаимоисключающий доступ,нас Можно использоватьsynchronized
Ключевые слова Приходить Изменитьметодиликодкусок。
public class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
public synchronized void decrement() {
count--;
}
}
В приведенном выше примере,increment()
иdecrement()
метододеялоsynchronized
Изменить,Гарантированоcount
переменнаяиз Доступ является взаимоисключающимиз,Избегайте возникновения условий гонки.
нить Коммуникация подразумевает множествонитьмеждупроходитьобщийиз对象Приходить进行信息交换исинхронный。Javaпредоставилwait()
、notify()
иnotifyAll()
метод Приходить实现нитьмеждуизкоммуникация。
public class Message {
private String content;
private boolean isReady;
public synchronized void setContent(String content) {
while (isReady) {
try {
wait(); // Подождите, пока сообщение будет использовано
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.content = content;
isReady = true;
notifyAll(); // Сообщите, что другие сообщения готовы
}
public synchronized String getContent() {
while (!isReady) {
try {
wait(); // Подождите, пока сообщение будет создано
} catch (InterruptedException e) {
e.printStackTrace();
}
}
isReady = false;
notifyAll(); // Уведомлять другие нить сообщения, которые были использованы
return content;
}
}
В приведенном выше примере,setContent()
иgetContent()
методиспользовалsynchronized
Изменить,обеспеченонитьмеждуизсинхронный。когда продюсернитьвызовsetContent()
методчас,Если сообщение готово,Затем войдите в состояние ожидания, в противном случае установите содержимое сообщения и отметьте сообщение как готовое;,И оповестить других нить.потребительнитьвызовgetContent()
методчас,Если сообщение еще не готово,Затем войдите в состояние ожидания; в противном случае получите содержимое сообщения и отметьте его как использованное;,И оповестить других нить.
Вот простой пример кода Java, показывающий, как создать и запустить поток:
public class ThreadDemo {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
System.out.println("Нить Начать выполнение");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("выполнение завершено");
});
thread.start();
System.out.println("Основная работа продолжает выполняться");
}
}
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ComplexMultithreadingExample {
public static void main(String[] args) {
// Создайте пул нить, содержащий 5 нить
ExecutorService executor = Executors.newFixedThreadPool(5);
// Создайте 10 задач и отправьте их в нить пул на выполнение.
for (int i = 0; i < 10; i++) {
Runnable task = new MyTask(i);
executor.submit(task);
}
// Закрыть бассейн
executor.shutdown();
}
}
class MyTask implements Runnable {
private int taskId;
public MyTask(int taskId) {
this.taskId = taskId;
}
@Override
public void run() {
System.out.println("Task " + taskId + " started.");
try {
// Время выполнения задачи моделирования
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Task " + taskId + " completed.");
}
}
В приведенном выше коде мы создаем поток и выполняем в нем некоторые задачи. Основной поток и дочерние потоки могут выполняться одновременно, и порядок выполнения между ними не определен.
Сопрограмма — это более легкий поток, который может переключаться между различными точками выполнения, а не полагаться на планирование потоков операционной системы. Переключение сопрограмм явно контролируется программистом и может переключаться при необходимости для повышения производительности параллелизма.
Вот простой пример кода Python, показывающий, как использовать сопрограммы:
import asyncio
async def myCoroutine():
print("Сопрограмма начинает выполняться")
await asyncio.sleep(1)
print("Выполнение сопрограммы завершено")
asyncio.run(myCoroutine())
В приведенном выше коде,мы используемPythonизasyncio
Библиотека создает сопрограмму,и выполнил некоторые задачи в сопрограмме。проходитьawait
Ключевые слова,Мы можем приостановить выполнение сопрограммы,Прежде чем продолжить, дождитесь завершения операции.
Fiber — это облегченный поток в пользовательском режиме. Он запланирован самой пользовательской программой и не зависит от планирования потоков операционной системы. Волокна могут переключать выполнение внутри одного потока, что снижает стоимость переключения потоков и повышает эффективность параллельной обработки.
Вот простой пример кода C++, показывающий, как использовать волокна:
#includesys/ucontext.h>
ucontext_t context[2];
void fiber1() {
printf("Fiber 1 начинает выполнение\n");
swapcontext(&context[1], &context[0]);
printf("Fibre 1 продолжает выполняться\n");
swapcontext(&context[1], &context[0]);
printf("Выполнение волокна 1 завершено\n");
}
void fiber2() {
printf("Fiber 2 начинает выполнение\n");
swapcontext(&context[0], &context[1]);
printf("Fiber 2 продолжает выполняться\n");
swapcontext(&context[0], &context[1]);
printf("Выполнение волокна 2 завершено\n");
}
int main() {
char stack1[8192];
char stack2[8192];
getcontext(&context[0]);
context[0].uc_stack.ss_sp = stack1;
context[0].uc_stack.ss_size = sizeof(stack1);
context[0].uc_link = &context[1];
makecontext(&context[0], fiber1, 0);
getcontext(&context[1]);
context[1].uc_stack.ss_sp = stack2;
context[1].uc_stack.ss_size = sizeof(stack2);
context[1].uc_link = &context[0];
makecontext(&context[1], fiber2, 0);
printf("Начинается основное выполнение\n");
swapcontext(&context[0], &context[1]);
printf("Главная нить продолжает выполнение\n");
swapcontext(&context[0], &context[1]);
printf("Основное выполнение завершено\n");
return 0;
}
В приведенном выше коде,мы используемC++изucontext
Библиотека создает два волокна,и переключаться между волокнами。проходитьswapcontext
функция,Мы можем вручную управлять переключением оптоволокна.
Virtual Threads — это новая модель параллелизма, представляющая собой облегченный поток, реализованный на уровне виртуальной машины Java. Виртуальные потоки могут создавать и планировать большое количество виртуальных потоков в одном потоке, избегая накладных расходов на переключение контекста, вызванных слишком большим количеством потоков в традиционной модели потоков.
Вот простой пример кода Java, показывающий, как использовать виртуальные потоки:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class VirtualThreadDemo {
public static void main(String[] args) {
ExecutorService executorService = Executors.newVirtualThreadExecutor();
for (int i = 0; i < 10; i++) {
int taskId = i;
executorService.execute(() -> {
System.out.println("виртуальныйнить" + taskId + «Начать выполнение»);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("виртуальныйнить" + taskId + «Выполнение завершено»);
});
}
executorService.shutdown();
}
}
В приведенном выше коде,мы используемJavaизExecutors
класс создаетVirtual Пул потоков Threads и создает в нем 10 виртуальных потоков для одновременного выполнения.
Виртуальные потоки — это новая функция, представленная в Java 17, которая представляет собой облегченную модель параллельного выполнения. По сравнению с традиционными потоками виртуальные потоки имеют меньшие затраты памяти и более высокую производительность одновременного выполнения. Он обеспечивает одновременное выполнение за счет запуска нескольких виртуальных потоков в одном или нескольких физических потоках.
Основная концепция виртуальных потоков — это продолжение, контекст, в котором продолжается выполнение. Каждый виртуальный поток содержит продолжение, которое можно приостановить и возобновить. Когда виртуальный поток приостанавливается, его состояние сохраняется, не занимая ресурсы физического потока. Когда виртуальный поток возобновляется, он возобновляет выполнение с того места, где он был в последний раз приостановлен.
Thread.setPriority(int) и Thread.setDaemon(boolean) неэффективны для виртуальных потоков.
Thread.getThreadGroup() вернет виртуальную пустую группу VirtualThreads.
Эффективность выполнения одной и той же задачи с использованием виртуальных потоков и потоков платформы совершенно одинакова, и повышения производительности не произойдет.
Попробуйте использовать управление параллелизмом в пакете JUC, например ReentrantLock, для управления синхронизацией вместо синхронизации. Синхронизированный блок кода закрепит (закрепит или заблокирует) виртуальный поток. Представитель JDK сказал, что это может быть оптимизировано позже.
Virtual Threads разработан как финальный класс и не может быть унаследован подклассами.
Не подходит для задач с интенсивными вычислениями. Виртуальные потоки подходят для задач с интенсивным вводом-выводом, но не для задач с интенсивными вычислениями, поскольку они выполняются в одном потоке и могут блокировать другие виртуальные потоки. Естественно, в новых функциях есть много ошибок, что действительно отражено в выпуске JDK. Используйте его с осторожностью! !
Прежде чем начать использовать виртуальные потоки, нам необходимо убедиться, что мы используем Java 17 и выше. Версию Java можно проверить с помощью следующего кода:
public class JavaVersionCheck {
public static void main(String[] args) {
String javaVersion = System.getProperty("java.version");
System.out.println("Java Version: " + javaVersion);
}
}
Использовать Виртуальный ThreadsНужно импортироватьjava.lang.VirtualThread
добрый。нас Можетпроходитьнижекод СоздайтеVirtual Thread:
import java.lang.VirtualThread;
public class VirtualThreadDemo {
public static void main(String[] args) {
VirtualThread virtualThread = new VirtualThread(() -> {
// Virtual Логика выполнения потока
});
virtualThread.start();
}
}
В приведенном выше коде,наспроходитьVirtualThread
класс создаетVirtual Thread и передается в лямбда-выражении как Virtual. Логика выполнения потока。Затемнасвызовstart()
метод ЗапуститеVirtual Thread。
Virtual ThreadМожет ПозвонивVirtualThread.yield()
метод Приходить主动сделайте паузуизосуществлять。после паузы,Virtual Состояние потока будет сохранено и будет ожидать следующего запланированного выполнения.
import java.lang.VirtualThread;
public class VirtualThreadDemo {
public static void main(String[] args) {
VirtualThread virtualThread = new VirtualThread(() -> {
// Virtual Логика выполнения потока
System.out.println("Virtual Thread: Hello, world!");
VirtualThread.yield(); // Пауза Виртуальный Выполнение потока
System.out.println("Virtual Thread: Goodbye!");
});
virtualThread.start();
}
}
В приведенном выше коде Virtual Thread сначала печатает сообщение,ЗатемвызовVirtualThread.yield()
методсделайте паузуизосуществлять。при следующей отправке,Virtual Поток продолжит выполнение с того места, где он в последний раз остановился, и напечатает еще одно сообщение.
Планирование виртуальных потоков выполняется автоматически средой выполнения Java, и нам не нужно вмешиваться вручную. Среда выполнения Java запланирует несколько виртуальных потоков для одного или нескольких физических потоков для выполнения в соответствии с определенными стратегиями. Этот метод планирования может улучшить производительность параллелизма, одновременно сокращая накладные расходы на создание и уничтожение потоков.
По сравнению с традиционной потоковой моделью Virtual Threads имеет следующие преимущества:
В традиционной модели потоков каждому потоку необходимо выделить определенный лимит слов, и он не может продолжать выводить данные. Пожалуйста, используйте команду «продолжить», чтобы получить оставшиеся части.
В этой статье мы даем углубленный анализ различий и взаимосвязей между процессами, потоками, сопрограммами, волокнами и виртуальными потоками. Процесс — это экземпляр программы, выполняющейся на компьютере, поток — это исполнительная единица внутри процесса, сопрограмма — это более легкий поток, волокно — это облегченный поток в пользовательском режиме, а виртуальные потоки — это облегченные потоки, реализованные в уровень виртуальной машины Java.
Понимая эти концепции, мы сможем лучше выбрать модель параллелизма, которая соответствует потребностям нашего проекта, и в полной мере воспользоваться преимуществами параллельного программирования.
Спасибо за чтение!