Многие люди, вероятно, уже знакомы с многозадачностью в операционных системах: возможностью одновременного запуска нескольких программ.
Многопоточные программы расширяют концепцию многозадачности на более низком уровне: программа выполняет несколько задач одновременно. Обычно каждая задача называется потоком, что является сокращением от управления потоком. Программа, которая может запускать более одного потока одновременно, называется многопоточной программой.
что такое нить
процесс
Процесс — это программа, работающая в системе, и когда программа запущена, это процесс.
Процесс можно рассматривать как экземпляр работающей программы. Процесс — это объект, который распределяет ресурсы системой, и каждый процесс имеет независимое адресное пространство.
Один процесс не может получить доступ к переменным и структурам данных другого процесса.Если вы хотите, чтобы один процесс имел доступ к ресурсам другого процесса, вам необходимо использовать межпроцессное взаимодействие, такое как каналы, файлы, сокеты.
нить
Поток — это сущность процесса, котораяБазовая единица планирования и диспетчеризации ЦП, который является базовой единицей, которая меньше процесса и может работать независимо. Сам поток в основном не владеет системными ресурсами, а имеет лишь несколько ресурсов, необходимых в работе (таких как счетчик программ, набор регистров и стек), но он может совместно использовать все ресурсы, которыми владеет процесс, с другими принадлежащими ему потоками. к тому же процессу.
Многопоточность
Многопоточность означает возможность одновременного запуска нескольких разных потоков в одной программе для выполнения разных задач.
Цель многопоточного программирования состоит в том, чтобы «максимально использовать ресурсы ЦП».Когда потоку не нужно занимать ЦП и он имеет дело только с такими ресурсами, как ввод-вывод, он позволяет другим потокам, которым необходимо занимать ЦП, иметь другие возможности для работы. получить ресурсы процессора. По сути, это конечная цель многопоточного программирования.
Программа, которая реализует несколько кодов, работающих попеременно одновременно, должна генерировать несколько потоков. Процессор берет свое время случайным образом. Давайте заставим программу делать это какое-то время, а другое какое-то время.
Java имеет встроенную поддержку многопоточного программирования (Multithreaded Programming).
Многопоточная программа состоит из двух или более частей, которые выполняются одновременно, и каждая такая часть программы называется потоком. Каждый поток имеет независимый путь выполнения, поэтому многопоточность — это особая форма многозадачности.
Многозадачность поддерживается всеми современными операционными системами. Однако существует два различных типа многозадачности:основанный на процессеина основе потоков.
1. Многозадачность, основанная на процессах, — более привычная форма. Процесс — это, по сути, исполняемая программа. Таким образом, многозадачность на основе процессов характеризуется тем, что ваш компьютер позволяет запускать две или более программ одновременно.
Например, многозадачность на основе процессов позволяет запускать компилятор Java во время редактирования текста.
2. В многозадачной среде, основанной на потоках, поток является наименьшей единицей выполнения. Это означает, что программа может выполнять две или более задач одновременно.
Например, текстовый редактор может форматировать текст во время печати.
разница между потоком и процессом
Внутренние данные и состояния нескольких процессов полностью независимы, в то время как многопоточность разделяет пространство памяти и набор системных ресурсов, которые могут влиять друг на друга.
Данные самого потока обычно представляют собой только данные регистра и стека, используемые при выполнении программы, поэтомуНагрузка на переключение потоков меньше, чем нагрузка на переключение процессов. Многопоточные программы требуют меньше накладных расходов, чем многопроцессорные программы.
Процессы — это тяжеловесные задачи, для которых необходимо выделять отдельные адресные пространства, межпроцессное взаимодействие является дорогостоящим и ограниченным, а трансляция между процессами является дорогостоящей. Потоки — это легковесные игроки, они используют одно и то же адресное пространство и один и тот же процесс, связь между потоками дешева, а преобразование между потоками также малозатратно.
реализация потока
Существует два метода предоставления потоку метода run:
- Унаследуйте класс Thread и переопределите его метод запуска. Затем создайте объект этого подкласса и вызовите метод start().
- Реализуйте метод run, определив класс, реализующий интерфейс Runnable. Объект этого класса передается в качестве параметра при создании потока, а затем вызывается метод start().
- Унаследуйте класс Thread и переопределите его метод запуска. Затем создайте объект этого подкласса и вызовите метод start()
- Реализуйте метод run, определив класс, реализующий интерфейс Runnable. Объект этого класса передается в качестве параметра при создании потока, а затем вызывается метод start().
- Реализовать вызываемый интерфейс
- 4. Пул потоков: Предоставляет очередь потоков, в которой сохраняются все потоки в состоянии ожидания. Избегайте создания и уничтожения дополнительных накладных расходов и улучшите скорость отклика.
Этим двум методам необходимо выполнить метод start() потока, чтобы выделить необходимые системные ресурсы для потока, и запланировать поток для запуска метода run() потока.
Метод start() — единственный способ запустить поток. Метод start() сначала подготавливает системные ресурсы для выполнения потока, а затем вызывает метод run(). Поток может быть запущен только один раз, и его повторный запуск незаконен.
Логика потока заложена в методе run(), то есть мы хотим, чтобы этот поток что-то делал.
Обычно я использую для его реализации второй метод, когда поток унаследовал другой класс, его можно сконструировать только вторым методом, то есть реализовать интерфейс Runnable.
Thread
package com.java.test;
public class ThreadTest
{
public static void main(String[] args)
{
TreadTest1 thread1 = new TreadTest1();
TreadTest2 thread2 = new TreadTest2();
thread1.start();
thread2.start();
}
}
class TreadTest1 extends Thread{
@Override
public void run() {
for (int i = 0; i < 1000; ++i)
{
System.out.println("Test1 " + i);
}
}
}
class TreadTest2 extends Thread{
@Override
public void run() {
for (int i = 0; i < 1000; ++i)
{
System.out.println("Test2 " + i);
}
}
}
При использовании первого метода (наследования Thread) для создания объектов потока, нам нужно переопределить метод run(), потому что метод run() класса Thread в это время ничего не делает.
Runnable
package com.java.test;
public class ThreadTest
{
public static void main(String[] args)
{
// 线程的另一种实现方法,也可以使用匿名的内部类
Thread threadtest1=new Thread((new ThreadTest1()));
threadtest1.start();
Thread threadtest2=new Thread((new ThreadTest2()));
threadtest2.start();
}
}
class ThreadTest1 implements Runnable
{
@Override
public void run()
{
for (int i = 0; i < 100; ++i)
{
System.out.println("Hello: " + i);
}
}
}
class ThreadTest2 implements Runnable
{
@Override
public void run()
{
for (int i = 0; i < 100; ++i)
{
System.out.println("Welcome: " + i);
}
}
}
При использовании второго способа (способ реализации интерфейса Runnable) для генерации объекта потока, нам нужно реализовать метод run() интерфейса Runnable, а затем использовать new Thread(new RunnableClass()) для создания объекта потока (RunnableClass реализовал интерфейс Runnable), затем метод run() объекта потока вызовет запуск метода RunnableClass()
Запускаемый исходный код
package java.lang;
public
interface Runnable {
public abstract void run();
}
В интерфейсе Runnable есть только метод run(), а класс Thread также реализует интерфейс Runnable, поэтому он также реализует метод run() в интерфейсе.
При создании объекта потока, если для него не указано имя, имя объекта потока будет использовать следующую форму: Номер потока, который является автоматически увеличивающимся числом и является общим для всех объектов потока, поскольку он является статической переменной-членом. .
остановить нить
Теперь кончина потока не может быть выполнена вызовом команды stop(), это должно позволить методу run() завершиться естественным образом. Метод stop() небезопасен и устарел.
Рекомендуемый способ остановки потока: установить флаговую переменную, которая является циклом в методе run().Переменная флага контролирует, продолжает ли цикл выполняться или выпрыгивает, если цикл выходит, поток завершается.
Потоки обмениваются ресурсами
Возьмите маленький каштан (*╹▽╹*)
Следующий код, я надеюсь, будет выполняться тремя потоками одновременноi--
, так что значение выхода i равно 0, но результат 3 выходов равен 2. Это потому, что вmain
Все три потока, созданные в методе, содержат переменную i сами по себе, и наша цель должна заключаться в том, чтобы три потока совместно использовали переменную i.
class ThreadTest1 extends Thread {
private int i = 3;
@Override
public void run() {
i--;
System.out.println(i);
}
}
public class ThreadTest {
public static void main(String[] strings) {
Thread thread1 = new ThreadTest1();
thread1.start();
Thread thread2 = new ThreadTest1();
thread2.start();
Thread thread3 = new ThreadTest1();
thread3.start();
}
}
输出 // 2 2 2
Должно быть написано так
public class ThreadTest {
public static void main(String[] strings) {
Thread thread1 = new ThreadTest1();
thread1.start();
thread1.start();
thread1.start();
}
}
Несколько потоков выполняют один и тот же код
В этом случае вы можете использовать тот же объект Runnable (см. предыдущий блог, это способ создания потока), чтобы внедрить данные, которые необходимо разделить, в этот объект Runnable. Например, в системе продажи билетов необходимо поделиться оставшимися билетами, но при этом, я думаю, следует добавить ключевое слово synchronized!
Код, выполняемый несколькими потоками, не одинаков
В этом случае могут быть реализованы две идеи (см. здесь точку зрения Учителя Чжан Сяосян)
Первый: инкапсулировать общие данные в другой объект, а затем передавать этот объект каждому объекту Runnable по одному. Метод работы каждого потока с общими данными также назначается этому объекту, так что легко добиться взаимного исключения и связи различных операций с измененными данными.
Во-вторых: возьмите эти объекты Runnable как внутренние классы в определенном классе, совместно используйте данные как переменные-члены в этом внешнем классе и назначьте метод работы каждого потока для общих данных во внешний класс, чтобы реализовать различные операции над общими данными. Взаимное исключение и связь операций, так как каждый объект Runnable внутреннего класса вызывает эти методы внешнего класса.
Комбинация: общие данные инкапсулируются в другой объект, и метод работы каждого потока с общими данными также назначается этому объекту. Объект используется в качестве переменной-члена в этом внешнем классе или локальной переменной в методе. Выполняемый объект действует как внутренний класс-член или локальный внутренний класс во внешнем классе. (метод, использованный кодом примера), одним словом, лучше разместить несколько кусков кода для синхронизации взаимного исключения в нескольких независимых методах, и эти методы разместить в одном классе, чтобы было проще реализовать разница между ними синхронная мьютексная связь
простой и грубый способ
В программе определите статическую переменную
жизненный цикл нити
Жизненный цикл потока — это на самом деле процесс потока от создания до смерти.
Поток может иметь 6 состояний
New (вновь созданный), Runnable (работоспособный), Blocked (заблокированный), Waiting (ожидание), Timed wait (ожидание по времени), Terminated (прекращено).
Чтобы определить текущее состояние потока, вызовите метод getState.
1. Создайте состояние:
Когда новый объект потока создается с помощью оператора new, такого как new Thread(t), поток еще не начал выполняться, поток находится в состоянии создания, и программа еще не начала выполнять код в нить. Поток в состоянии created — это просто пустой объект потока, и система не выделяет для него ресурсы.
2. Рабочее состояние
После вызова метода start() поток находится в рабочем состоянии. Выполняемый поток может выполняться, а страницы могут не выполняться, в зависимости от того, сколько времени операционная система предоставляет потоку для выполнения.
Как только поток начинает работать, он не должен продолжать работать все время. Фактически работающий поток прерывается, чтобы дать возможность другим потокам работать. Детали планирования потоков зависят от служб, предоставляемых операционной системой.
Имейте в виду, что в любой момент работающий поток может быть запущен, а страница может быть не запущена, поэтому это состояние становится работоспособным, а не запущенным.
3. Неработоспособное состояние
Невыполняемые состояния включают заблокированные или ожидающие состояния, когда поток временно неактивен, не выполняет кода и потребляет минимальные ресурсы. пока планировщик потоков не активирует его повторно.
Когда поток пытается получить внутреннюю блокировку объекта, а блокировка удерживается другими потоками, он переходит в состояние блокировки. Поток становится неблокирующим, когда все другие потоки утомительно освобождаются, позволяя планировщику потоков разрешить потоку удерживать его.
Когда поток ожидает, пока другой поток уведомит планировщик об условии, он сам переходит в состояние ожидания. Это происходит, когда вызывается метод ожидания и метод соединения.
Есть несколько методов с параметром тайм-аута, например, sleep, wait, join. Их вызов приводит к тому, что поток входит в состояние ожидания по времени. Это состояние будет сохраняться до истечения тайм-аута или после соответствующего уведомления.
Проще говоря:
Поток в рабочем состоянии переходит в нерабочее состояние, когда происходят следующие события:
Вызывается метод sleep();
Поток вызывает метод wait() для ожидания выполнения определенных условий;
Блокировка ввода/вывода потока.
Вернуться в рабочее состояние:
Поток в спящем состоянии по истечении указанного времени;
Если поток ожидает условия, другой объект должен уведомить ожидающий поток об изменении условия с помощью метода notify() или notifyAll();
Если поток заблокирован из-за ввода и вывода, дождитесь завершения ввода и вывода.
4. Прекращенное состояние
Тема была закрыта по одной из двух причин:
- Естественно умирает, потому что метод запуска завершается нормально
- умирает, потому что неперехваченное исключение завершает метод запуска
приоритет потока
Потоки Java имеют настройки приоритета, и у потоков с высоким приоритетом больше шансов быть выполненными, чем у потоков с основным приоритетом.
- Когда текущий поток не имеет определенного приоритета, все потоки имеют обычный приоритет.
- Приоритет указывается в диапазоне от 1 до 10. 10 — наивысший приоритет, 1 — низший приоритет, 5 — обычный приоритет.
- Поток с наивысшим приоритетом получает приоритет при выполнении. Но нет никакой гарантии, что поток перейдет в состояние выполнения при запуске.
- Выполняемый поток всегда может иметь более высокий приоритет, чем поток, ожидающий запуска в пуле потоков.
- Планировщик должен решить, какой поток будет выполняться.
- t.setPriority() используется для установки приоритета потока.
- Помните, что приоритет потока должен быть установлен до вызова метода запуска потока.
- Вы можете использовать константы, такие как MIN_PRIORITY, MAX_PRIORITY, NORM_PRIORITY, чтобы установить приоритет. Приоритет потока Java является целым числом, диапазон значений которого составляет 1 (Thread.MIN_PRIORITY) - 10 (Thread.MAX_PRIORITY).
public static final int MIN_PRIORITY = 1;
public static final int NORM_PRIORITY = 5;
public static final int MAX_PRIORITY = 10;
вообще-то нет. Приоритет по умолчанию — приоритет родительского потока. В методе инициализации
Thread parent = currentThread();
this.priority = parent.getPriority();
Приоритет можно изменить с помощью метода setPriority (последний, не может быть переопределен подклассами). Приоритет не может превышать диапазон значений от 1 до 10, в противном случае создается исключение IllegalArgumentException. Кроме того, если поток уже принадлежит к группе потоков (ThreadGroup), приоритет потока не может превышать приоритет группы потоков.
что такое переключение контекста
Когда ЦП обрабатывает задачи, он не всегда обрабатывает одно значение, а выделяет кванты времени ЦП каждому потоку и переключается на следующий поток, когда квант времени израсходован. Временной интервал очень короткий, обычно всего несколько десятков миллисекунд, поэтому, когда ЦП продолжает переключать потоки для выполнения, мы почти не чувствуем стагнацию задачи, что заставляет нас чувствовать, что несколько потоков выполняются одновременно.
Процессор выполняет задачи циклически с помощью алгоритма распределения временных интервалов.После того, как текущая задача выполнит временной интервал, он переключится на следующую задачу. Однако перед переключением состояние предыдущей задачи будет сохранено, чтобы при следующем переключении на эту задачу снова можно было загрузить состояние этой задачи,Процесс от сохранения задачи до перезагрузки — это переключение контекста..
Такое переключение повлияет на эффективность выполнения многопоточности.
Как уменьшить переключение контекста, чтобы повысить эффективность работы многопоточных программ
Переключение контекста потока делится наУступчивое и упреждающее переключение контекста.
Первый относится к активному высвобождению ЦП потоком выполнения, что пропорционально серьезности конкуренции блокировок, и его можно избежать, уменьшив конкуренцию блокировок.
Последнее означает, что поток вынужден отказаться от процессора из-за исчерпания выделенного кванта времени или его вытесняют другие потоки с более высоким приоритетом.Как правило, количество потоков больше, чем количество доступных ядер процессора.настроить количество потоков,Соответствующим образом уменьшите количество потоков, которых следует избегать.
- параллельное программирование без блокировок
- Уменьшите использование блокировки, используйте cas вместо блокировки в условиях многопоточной конкуренции,Блокировка и снятие блокировки вызовет больше переключений контекста и задержек планирования., вызывая проблемы с производительностью.
- Используйте минимальное количество потоков, чтобы не создавать ненужные потоки.
- Используйте сопрограммы для планирования многозадачности в одном потоке и поддерживайте переключение между несколькими задачами в одном потоке.
Когда несколько потоков используют CAS для одновременного обновления одной и той же переменной,Только один из потоков может обновить значение переменной, и другие потоки терпят неудачу, неисправный поток не приостанавливается, но получает указание потерпеть неудачу в этом соревновании, имогу попробовать еще раз(Пока квант времени, выделенный процессором потоку, не прошел, он может продолжать повторять попытку, но после кванта времени, если он все еще не увенчался успехом, также будет выполнено переключение контекста, поэтому переключение контекста только уменьшенный).