процесс и поток
Прежде чем говорить о многопоточности, я думаю, необходимо поговорить о взаимосвязи и различии между процессами и потоками.
1. Процесс — это наименьшая единица распределения ресурсов, а поток — наименьшая единица выполнения программы (наименьшая единица планирования ресурсов);
2. Процесс имеет свое собственное независимое адресное пространство. Каждый раз, когда запускается процесс, система выделит адресное пространство для него, и установить таблицу данных для поддержания сегмента данных, сегмент стека и сегмент данных. Эта операция очень дорого ;
Потоки совместно используют данные в процессе и используют одно и то же адресное пространство, поэтому стоимость переключения потока ЦП намного меньше, чем у процесса, и стоимость создания потока также намного меньше, чем у процесса;
3. Связь между потоками более удобна.Потоки в рамках одного и того же процесса совместно используют данные, такие как глобальные переменные и статические переменные, и связь между процессами должна осуществляться посредством связи (IPC). Однако как быть с синхронизацией и взаимоисключением — это сложность написания многопоточных программ;
4. Но многопроцессорные программы более надежны.Пока умирает один поток многопоточной программы, умирает весь процесс, и смерть одного процесса не повлияет на другой процесс, потому что у процесса есть свой независимый адрес пространство.
С точки зрения непрофессионала, процесс похож на QQ, Chrome, Netease Cloud Music в диспетчере задач, а поток похож на задачу в середине процесса, например, вы нажимаете, чтобы переключить музыку, чат для отправки информации и т. д.
Реализация многопоточности
В Java существует три формы реализации многопоточности, здесь упоминаются только первые две, наследующие класс Thread и реализующие интерфейс Runnable.
1 Наследовать класс Thread
//继承Thread实现多线程
class Thread1 extends Thread{
private String name;
public Thread1(String name) {
this.name=name;
}
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(name + "运行 : " + i);
try {
sleep((int) Math.random() * 10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Main {
public static void main(String[] args) {
Thread1 mTh1=new Thread1("A");
Thread1 mTh2=new Thread1("B");
mTh1.start();
mTh2.start();
}
}
Вышеуказанные два класса,Thread1
Класс наследует родительский класс Thread и переопределяет внутренний класс.run
метод. Реализовал методы в многопоточности и создал два экземпляра в основной функции.mTh1
,mTh2
две нити.
Запустите основную функцию:
输出:
A运行 : 0
B运行 : 0
A运行 : 1
A运行 : 2
A运行 : 3
A运行 : 4
B运行 : 1
B运行 : 2
B运行 : 3
B运行 : 4
Запустите его снова:
A运行 : 0
B运行 : 0
B运行 : 1
B运行 : 2
B运行 : 3
B运行 : 4
A运行 : 1
A运行 : 2
A运行 : 3
A运行 : 4
Видно, что результаты двух прогонов не совпадают.
иллюстрировать
Когда программа запускает основную функцию, виртуальная машина Java уже запустила основной поток для выполнения основной функции.
mTh1
,mTh2
Когда метод запускаmTh1
дочерний поток, операции в этом потоке, в этом потокеsleep()
метод, цель вызова метода Thread.sleep() состоит в том, чтобы предотвратить занятие текущим потоком ресурсов ЦП, полученных только процессом, чтобы оставить определенное количество времени для выполнения других потоков.
На самом деле порядок выполнения всех потоков неизвестен: получение ресурсов ЦП полностью зависит от того, кто первым запустит два потока.mTh1
После вытеснения потока запуститеrun
код в методе, чтобыsleep()
Метод переходит в состояние сна, которое является состоянием блокировки, а затемCPU
ресурсы будут освобождены,A
,B
упреждать сноваCPU
Операции с ресурсами, прерываемые при продолжении выполнения. Вы также можете увидеть это явление в бегущих результатах.
Уведомление:
Метод start() экземпляра нельзя вызывать повторно, иначе он появитсяjava.lang.IllegalThreadStateException
аномальный.
2 Реализацияjava.lang.Runnable
интерфейс
Также очень часто используется Runnable, нам нужно только переписать метод запуска. Давайте также рассмотрим пример.
class Thread2 implements Runnable{
private String name;
public Thread2(String name) {
this.name=name;
}
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(name + "运行 : " + i);
try {
Thread.sleep((int) Math.random() * 10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Main {
public static void main(String[] args) {
new Thread(new Thread2("C")).start();
new Thread(new Thread2("D")).start();
}
}
Существует небольшая разница между целым и наследованием Thread, потому что интерфейс Runnable также наследуется в классе Thread.
Выходной цикл:
C运行 : 0
D运行 : 0
D运行 : 1
C运行 : 1
D运行 : 2
C运行 : 2
D运行 : 3
C运行 : 3
D运行 : 4
C运行 : 4
проиллюстрировать:
Thread2
класс путем реализацииRunnable
Благодаря интерфейсу этот класс имеет характеристики многопоточного класса.run()
Методы являются соглашением для многопоточных программ. Весь многопоточный код находится вrun
внутри метода.Thread
Класс фактически реализуетRunnable
Класс интерфейса.При запуске многопоточности вам нужно сначала пройти
Thread
конструктор классаThread(Runnable target)
Создайте объект, а затем вызовите метод start() объекта Thread для запуска многопоточного кода.Практически весь многопоточный код выполняется запуском
Thread
изstart()
метод работы. Следовательно, либо расширение класса Thread, либо реализацияRunnable
интерфейс для достижения многопоточности, и в конечном итоге черезThread
объектAPI
Для управления потоком, знакомым сThread
КатегорияAPI
Это основа многопоточного программирования.
Разница между классом Thread и интерфейсом Runnable
Если класс наследуетThread
, он не подходит для совместного использования ресурсов. Но если осознатьRunable
Если используется интерфейс, легко реализовать совместное использование ресурсов.
Суммировать:
выполнитьRunnable
интерфейс, чем наследованиеThread
Преимущества занятий:
1): подходит для нескольких потоков одного и того же программного кода для обработки одного и того же ресурса.
2): можно избежатьjava
Ограничения одиночного наследования в
3): Увеличьте надежность программы, код может совместно использоваться несколькими потоками, код и данные независимы.
4): Пул потоков можно поместить только в реализациюRunable
илиcallable
поток класса, не может быть напрямую передан в наследованиеThread
тип
Напоминаем всем: основной метод на самом деле является потоком. существуетjava
Все потоки запускаются одновременно, а когда, какой из них выполняется первым, полностью зависит от того, кто первым получит ресурсы процессора.
существуетjava
, запускайте как минимум 2 потока при каждом запуске программы. одинmain
поток, один из которых является потоком сборки мусора. потому что всякий раз, когда вы используетеjava
Когда команда выполняет класс, она фактически запускаетJVM
,КаждыйJVM
Стажировка запускает процесс в операционной системе.
состояние потока
Давайте сначала поместим диаграмму отображения потока
1: Новое состояние (New): new Thread(), создается новый поток;
2: Состояние готовности (Runnable): после того, как новое создание завершено, основной поток (метод main()) вызывает метод start() потока. ЦП в настоящее время выполняет другие задачи или потоки, и созданный поток войдет в состояние готовности Ожидание ресурсов ЦП для запуска программы, она находится в состоянии готовности в течение периода перед запуском;
3: Состояние выполнения (Running): буквально после того, как поток вызывает метод start() и вытесняет ресурсы ЦП, он запускает программный код в методе run;
4: Заблокированное состояние (Blocked): В заблокированном состоянии поток приостанавливается из-за некоторых операций во время запущенного процесса, отказываясь от права использовать ЦП, переходя в состояние готовности и вытесняя следующий ресурс ЦП вместе с другими потоками.
При возникновении следующих условий поток перейдет в состояние блокировки.
① Поток вызывает метод sleep(), чтобы добровольно отказаться от занятых ресурсов процессора
② Поток вызывает блокирующий метод ввода-вывода, и поток блокируется до возврата метода
③ Поток пытался получить монитор синхронизации, но монитор синхронизации удерживается другим потоком. О знании монитора синхронизации будет подробное введение позже.
④ Поток ожидает уведомления (уведомить)
⑤ Программа вызывает метод suspend() потока, чтобы приостановить поток.Но этот метод склонен к взаимоблокировке, поэтому его следует по возможности избегать.
После того, как текущий исполняемый поток заблокирован, другие потоки могут получить возможность выполнения. Заблокированный поток повторно войдет в состояние готовности в соответствующее время, обратите внимание на состояние готовности, а не на состояние выполнения. То есть после того, как заблокированный поток будет разблокирован, он должен ждать, пока планировщик потоков снова запланирует его.
разблокировать
Для описанных выше ситуаций, когда возникают следующие конкретные ситуации, вышеуказанная блокировка может быть разблокирована, и поток повторно входит в состояние готовности:
① Поток, вызывающий метод sleep(), прошел указанное время.
② Метод блокировки ввода-вывода, вызванный потоком, вернулся.
③ Поток успешно получил монитор синхронизации, который пытался получить.
④ Когда поток ожидает уведомления, другие потоки отправляют уведомление.
⑤ Тема в приостановленном состоянии переведенаresdme()
Метод восстановления (вызовет взаимоблокировку, старайтесь не использовать его).
5: Мертвое состояние (Dead): выполнение программы потока завершено или метод run() прерывается из-за исключения, и жизненный цикл потока заканчивается.
планирование потоков
1: настроить приоритет потока:Java
У потоков есть приоритеты, а потоки с более высоким приоритетом получат больше возможностей для выполнения.
Java
Приоритет потока представлен целым числом, а диапазон значений составляет от 1 до 10.Thread
Класс имеет следующие три статические константы:
static int MAX_PRIORITY
Максимальный приоритет, который может иметь поток, равен 10.
static int MIN_PRIORITY
Самый низкий приоритет, который может иметь поток, равный 1.
static int NORM_PRIORITY
Приоритет по умолчанию, назначенный потоку, принимает значение 5.
Thread
КатегорияsetPriority()
а такжеgetPriority()
Методы используются для установки и получения приоритета потока соответственно.
Каждый поток имеет приоритет по умолчанию. Приоритет основного потока по умолчанию:Thread.NORM_PRIORITY
.
Приоритет потоков наследуется, например, если поток B создан в потоке A, то B будет иметь тот же приоритет, что и A.
JVM
Предусмотрено 10 приоритетов потоков, но они плохо сочетаются с распространенными операционными системами. Если вы хотите, чтобы ваша программа была переносимой между операционными системами, вы должны использовать толькоThread
Класс имеет следующие три статические константы в качестве приоритета, что гарантирует использование одного и того же метода планирования с одинаковым приоритетом.
2,Тема сна:Thread.sleep(long millis)
метод, чтобы заставить поток перейти в состояние блокировки.millis
Параметр устанавливает время ожидания в миллисекундах. Когда сон закончился, он переходит в состояние готовности (Runnable
)условие.sleep()
Портативность платформы хорошая.
3.Тема ожидания:Object
в классеwait()
метод, который заставляет текущий поток ждать, пока другой поток не вызовет этот объектnotify()
метод илиnotifyAll()
метод пробуждения. Два метода пробуждения такжеObject
Метод в классе, который ведет себя так, как будто вызывает ожидание (0).
4.Результат потока:Thread.yield()
метод, который приостанавливает текущий исполняемый объект потока и предоставляет возможность выполнения потоку с таким же или более высоким приоритетом.
5.Присоединение к теме:join()
метод, ожидающий завершения других потоков. вызвать другой потокjoin()
метод, текущий поток переходит в состояние блокировки до тех пор, пока не завершится выполнение другого процесса, и текущий поток не переходит из состояния блокировки в состояние готовности.
6. **Пробуждение потока: **Класс объектаnotify()
метод, который пробуждает один поток, ожидающий на мониторе этого объекта. Если все потоки ожидают этого объекта, один из них будет выбран для пробуждения. Выбор произволен и происходит, когда принимается решение о реализации. Поток ожидает на мониторе объекта, вызывая один из методов ожидания. Выполнение пробужденного потока не может продолжаться до тех пор, пока текущий поток не снимет блокировку с этого объекта. Пробужденный поток будет конкурировать обычным образом со всеми другими потоками, активно синхронизирующими этот объект; например, у пробужденного потока нет надежной привилегии или недостатка в том, чтобы быть следующим потоком, который заблокирует этот объект. Есть похожий методnotifyAll()
, пробуждает все потоки, ожидающие этого монитора объектов.
Примечание. В JDK1.5 два метода suspend() и возобновить() в Thread были упразднены и больше не будут представлены. Из-за склонности к тупику.
Описание общей функции
1:sleep(long millis)
: приостановить выполнение текущего потока на указанное количество миллисекунд (приостановить выполнение);
sleep() переводит текущий поток в застойное состояние (блокирует текущий поток) и отказывается от использования ЦП. Цель состоит в том, чтобы предотвратить занятие текущим потоком ресурсов ЦП, полученных процессом в одиночку, чтобы оставить определенное время для других потоков, чтобы выполнить возможность;
sleep() — это статический метод класса Thread, поэтому он не может изменить машинную блокировку объекта, поэтому, когда метод Sleep() вызывается в синхронизированном блоке, хотя поток спит, машинная блокировка объекта не работает. не существует. освобождается, и другие потоки не могут получить доступ к объекту (удерживать блокировку объекта даже в спящем режиме).
По истечении времени ожидания sleep() поток может не выполняться немедленно, поскольку другие потоки могут быть запущены и не запланированы для отказа, если только поток не имеет более высокий приоритет.
2:join()
: относится к ожиданию завершения потока t.
Thread t = new AThread(); t.start(); t.join();
Зачем использовать метод join()
Во многих случаях основной поток генерирует и запускает подпотоки.Если в подпотоках должно быть выполнено большое количество трудоемких операций, основной поток обычно завершается раньше подпотоков.Однако, если основной поток завершает другие транзакции, он должен использовать К результату обработки дочернего потока, то есть основной поток должен дождаться завершения выполнения дочерним потоком перед завершением.В это время используется метод join().
не добавлятьjoin()
метод:
class Thread1 extends Thread{
private String name;
public Thread1(String name) {
super(name);
this.name=name;
}
public void run() {
System.out.println(Thread.currentThread().getName() + " 线程运行开始!");
for (int i = 0; i < 5; i++) {
System.out.println("子线程"+name + "运行 : " + i);
try {
sleep((int) Math.random() * 10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + " 线程运行结束!");
}
}
public class Main {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName()+"主线程运行开始!");
Thread1 mTh1=new Thread1("A");
Thread1 mTh2=new Thread1("B");
mTh1.start();
mTh2.start();
System.out.println(Thread.currentThread().getName()+ "主线程运行结束!");
}
}
Выходной результат:
main主线程运行开始!
main主线程运行结束!
B 线程运行开始!
子线程B运行 : 0
A 线程运行开始!
子线程A运行 : 0
子线程B运行 : 1
子线程A运行 : 1
子线程A运行 : 2
子线程A运行 : 3
子线程A运行 : 4
A 线程运行结束!
子线程B运行 : 2
子线程B运行 : 3
子线程B运行 : 4
B 线程运行结束!
Нашелmain
Основной поток функции заканчивается раньше, чем подпотоки A и B.
Присоединяйсяjoin()
метод:
(Метод резьбы тот же и повторяться не будет)
public class Main {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName()+"主线程运行开始!");
Thread1 mTh1=new Thread1("A");
Thread1 mTh2=new Thread1("B");
mTh1.start();
mTh2.start();
try {
mTh1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
mTh2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+ "主线程运行结束!");
}
}
результат операции:
main主线程运行开始!
A 线程运行开始!
子线程A运行 : 0
B 线程运行开始!
子线程B运行 : 0
子线程A运行 : 1
子线程B运行 : 1
子线程A运行 : 2
子线程B运行 : 2
子线程A运行 : 3
子线程B运行 : 3
子线程A运行 : 4
子线程B运行 : 4
A 线程运行结束!
main主线程运行结束!
Основной поток должен дождаться завершения дочерних потоков, прежде чем завершиться.
3:yield():
Приостановить выполнение текущего объекта потока и выполнить другие потоки.
Thread.yield()
Функция метода состоит в том, чтобы приостановить текущий исполняемый объект потока и выполнить другие потоки.
yield()
Что нужно сделать, так это вернуть текущий запущенный поток в работоспособное состояние, чтобы позволить другим потокам с таким же приоритетом получить шанс запуститься. Таким образом, цель использования yield() состоит в том, чтобы разрешить соответствующее циклическое выполнение между потоками с одинаковым приоритетом. Однако на практике нет никаких гарантийyield()
Цель уступки достигнута, потому что уступивший поток все еще может быть повторно выбран планировщиком потоков.
В заключение:yield()
Никогда не заставляйте поток переходить в состояние ожидания/сна/блокировки. В большинстве случаевyield()
Заставит поток перейти из состояния выполнения в состояние готовности к выполнению, но может не иметь никакого эффекта. См. рисунок выше.
class ThreadYield extends Thread{
public ThreadYield(String name) {
super(name);
}
@Override
public void run() {
for (int i = 1; i <= 50; i++) {
System.out.println("" + this.getName() + "-----" + i);
// 当i为30时,该线程就会把CPU时间让掉,让其他或者自己的线程执行(也就是谁先抢到谁执行)
if (i ==30) {
this.yield();
}
}
}
}
public class Main {
public static void main(String[] args) {
ThreadYield yt1 = new ThreadYield("张三");
ThreadYield yt2 = new ThreadYield("李四");
yt1.start();
yt2.start();
}
}
результат операции:
Первый случай: Li Si (поток) отдаст процессорное время, когда выполнение достигнет 30. В это время Zhang San (поток) захватывает процессорное время и выполняет его.
Второй случай: Li Si (поток) отдаст процессорное время, когда выполнение достигнет 30. В это время Li Si (поток) захватывает процессорное время и выполняет его.
Разница между sleep() и yield()
sleep() переводит текущий поток в застойное состояние, а точнее в заблокированное состояние.По истечении времени, заданного sleep(), поток продолжит выполняться, а другие потоки будут выполняться в течение застойного времени , Метод yield() предназначен для прямой остановки Поток затем переводит поток из состояния выполнения в состояние готовности и конкурирует с другими потоками за ресурсы ЦП. Возможно, что он немедленно захватит ресурсы ЦП и продолжит выполнение нить.
Метод sleep переводит текущий поток в спящий режим на некоторое время и переходит в неработоспособное состояние.Продолжительность этого периода времени устанавливается программой.Метод yield заставляет текущий поток отказаться от владения ЦП, но заданное время установить нельзя. . На самом деле метод yield() соответствует следующим операциям: сначала проверить, есть ли в данный момент потоки с таким же приоритетом в том же рабочем состоянии, если да, передать право собственности на процессор этому потоку, в противном случае продолжить выполнение исходного потока. . Поэтому метод yield() называется "уступчивостью", что дает возможность запускать другие потоки с таким же приоритетом.
Кроме того, метод sleep позволяет потокам с более низким приоритетом получить шанс на выполнение, но когда выполняется метод yield(), текущий поток все еще находится в состоянии выполнения, поэтому невозможно отказаться от потока с более низким приоритетом. чтобы получить право собственности на ЦП. В работающей системе, если поток с более высоким приоритетом не вызывает метод сна и не блокируется вводом-выводом, то поток с более низким приоритетом может только дождаться завершения всех потоков с более высоким приоритетом, прежде чем у них появится шанс запуститься. .
4:setPriority()
: изменить приоритет потока. МИН_ПРИОРИТЕТ = 1
НОРМ_ПРИОРИТЕТ = 5
МАКСИМАЛЬНЫЙ ПРИОРИТЕТ = 10
5:interrupt()
:
interrupt()
Метод заключается не в том, чтобы прервать поток, а в том, чтобы послать сигнал прерывания в поток, чтобы поток мог выдать исключение, когда он ждет бесконечно (например, взаимоблокировка), тем самым завершая поток, но если вы съедите это исключение, то нить все равно не прервется!
(О прерывании я напишу специальную статью.interrupt,isInterrupted,interrupted
. Также ликвидированыstop,suspend
Почему метод ликвидируется)
6: Другие методы
а такжеwait(),notify(),notifyAll()
Эти методы, потому что эти три метода необходимо объяснить в сочетании с блокировками потоков, поэтому в следующий раз мы объясним их с многопоточными блокировками. Существует также концепция пула потоков Java, разница в блокировках и так далее.
передача данных потока
В традиционном режиме синхронной разработки, когда мы вызываем функцию, данные передаются через параметры функции, а окончательный результат вычисления возвращается через возвращаемое значение функции. Но в многопоточном асинхронном режиме разработки существует большая разница между передачей и возвратом данных и синхронным режимом разработки. Поскольку запуск и завершение потоков непредсказуемы, при передаче и возврате данных невозможно вернуть данные через параметры функции и операторы возврата, подобные функциям.
1: передать данные через конструктор
При создании потока необходимо создать экземпляр класса Thread или его подклассов. Поэтому нам нетрудно продумать передачу данных в поток через конструктор класса потока перед вызовом метода запуска. И сохранить входящие данные, используя переменные класса для использования потоком (на самом деле, он используется в методе запуска). Следующий код демонстрирует, как передавать данные через конструктор:
package mythread;
public class MyThread1 extends Thread
{
private String name;
public MyThread1(String name)
{
this.name = name;
}
public void run()
{
System.out.println("hello " + name);
}
public static void main(String[] args)
{
Thread thread = new MyThread1("world");
thread.start();
}
}
Поскольку этот метод передает данные одновременно с созданием объекта потока, данные уже находятся на месте до запуска потока, так что явление передачи данных после запуска потока не будет вызвано. Если вы хотите передавать более сложные данные, вы можете использовать структуры данных, такие как коллекции, классы и т. д. Хотя безопаснее использовать конструктор для передачи данных, это вызовет массу неудобств, если нужно передать много данных. Поскольку в Java нет параметров по умолчанию, для достижения эффекта, аналогичного параметрам по умолчанию, необходимо использовать перегрузку, которая не только усложняет сам конструктор, но и сильно увеличивает количество конструкторов. Поэтому, чтобы избежать этой ситуации, вам придется передавать данные через методы класса или переменные класса.
2: Передача данных через переменные и методы
Как правило, есть две возможности передать данные в объект: первая возможность — передать данные через конструктор при создании объекта, а вторая — определить ряд общедоступных методов или переменных в классе (также известных как что касается поля). Затем после создания объекта присваивайте значения по одному через экземпляр объекта. Следующий код представляет собой модификацию класса MyThread1, использующую метод setName для установки переменной name:
package mythread;
public class MyThread2 implements Runnable
{
private String name;
public void setName(String name)
{
this.name = name;
}
public void run()
{
System.out.println("hello " + name);
}
public static void main(String[] args)
{
MyThread2 myThread = new MyThread2();
myThread.setName("world");
Thread thread = new Thread(myThread);
thread.start();
}
}
3: передать данные через функцию обратного вызова
Два рассмотренных выше метода передачи данных в поток являются наиболее распространенными. Но эти два метода активно передают данные в класс потока в основном методе. Это делается для того, чтобы поток пассивно получал эти данные. Однако в некоторых приложениях необходимо получать данные динамически во время работы потока, например, в методе run следующего кода генерируются три случайных числа, а затем вычисляется сумма этих трех случайных чисел. process класса Work и вернуть результат через значение класса Data. Как видно из этого примера, перед возвратом значения необходимо получить три случайных числа. То есть это значение не может быть передано в класс потока заранее.
package mythread;
class Data
{
public int value = 0;
}
class Work
{
public void process(Data data, Integer numbers)
{
for (int n : numbers)
{
data.value += n;
}
}
}
public class MyThread3 extends Thread
{
private Work work;
public MyThread3(Work work)
{
this.work = work;
}
public void run()
{
java.util.Random random = new java.util.Random();
Data data = new Data();
int n1 = random.nextInt(1000);
int n2 = random.nextInt(2000);
int n3 = random.nextInt(3000);
work.process(data, n1, n2, n3); // 使用回调函数
System.out.println(String.valueOf(n1) + "+" + String.valueOf(n2) + "+"
+ String.valueOf(n3) + "=" + data.value);
}
public static void main(String[] args)
{
Thread thread = new MyThread3(new Work());
thread.start();
}
}
Суммировать
В этой статье в основном рассказывается об основных частях многопоточности Java, и будет синхронизация потоков (блокировки), как правильно прерывать потоки, пулы потоков и т. д. Многопоточная часть Java более сложна, и ее можно запомнить и применить к реальным проектам, только если вы чаще смотрите и практикуетесь. Взаимное поощрение~