предисловие
Эта статья познакомит с основами многопоточности и параллелизма в Java, которая подходит для начинающих.Если вы хотите увидеть немного более продвинутый контент о многопоточности и параллелизме, вы можете прочитать мой другой блог-"Замок"
разница между потоком и процессом
В первые дни развития компьютеров каждый компьютер выполнял задачи последовательно. Если он сталкивается с местом, где требуется ввод-вывод, ему нужно долго ждать пользовательского ввода-вывода. Позже, спустя некоторое время, был разработан пакетный компьютер, который может пакетироватьсериалОн может обрабатывать инструкции пользователя, но по своей природе является последовательным и не может выполняться одновременно.Как решить проблему одновременного выполнения?Так введенопроцессКонцепция чего-либо,Каждый процесс имеет эксклюзивное пространство памяти, а процесс — это наименьшая единица выделения памяти, он работает, не мешая друг другу и может переключаться друг с другом., теперь мы видим несколько процессов, работающих «одновременно», что на самом деле является эффектом высокоскоростного переключения процессов.
Итак, после наличия потоков наша компьютерная система кажется идеальной, так зачем вводить потоки? Если процесс имеет несколько подзадач, процесс часто должен выполнять эти подзадачи одну за другой, но эти подзадачи часто независимы друг от друга и могут выполняться одновременно, поэтому требуется более точное переключение ЦП. Поэтому вводится понятие потоков, принадлежащих определенному процессу, они совместно используют ресурсы памяти процесса и быстрее переключаются между ними.
разница между процессом и потоком:
1. Процесс — это наименьшая единица распределения ресурсов, а поток — наименьшая единица планирования ЦП. Все ресурсы, связанные с процессом, записываются в печатной плате.
2. Поток принадлежит процессу и разделяет ресурсы процесса, которому он принадлежит. Поток состоит только из регистров стека, счетчика программ и TCB.
3. Процесс можно рассматривать как независимое приложение, а поток нельзя рассматривать как независимое приложение.
4. Процессы имеют независимые адресные пространства и не влияют друг на друга, а потоки — это просто разные пути выполнения процесса, если поток зависнет, то и процесс зависнет. Таким образом, многопроцессорные программы более надежны, чем многопоточные программы, но переключение потребляет больше ресурсов.
Отношения между процессом и потоком в Java:
1. Запуск программы создает процесс, и этот процесс содержит по крайней мере один поток.
2. Каждый процесс соответствует экземпляру JVM, и несколько потоков совместно используют кучу в JVM.
3. Java использует однопоточную модель программирования, и программа автоматически создает основной поток. .
4. Основной поток может создавать подпотоки, в принципе выполнение подпотоков должно быть завершено позже.
Разница между методом запуска и методом запуска потока
-
разница
Существует два способа создания потока в Java: используете ли вы метод наследования Thread или метод реализации интерфейса Runnable, вам необходимо переопределить метод run.Вызов метода запуска создаст новый поток и запустит его, а метод запуска — это просто функция обратного вызова после запуска потока., если вызывается метод run, поток, выполняющий метод run, не будет вновь созданным потоком, а если используется метод start, потоком, выполняющим метод run, будет поток, который мы только что запустили.
-
Верификация программы
public class Main { public static void main(String[] args) { Thread thread = new Thread(new SubThread()); thread.run(); thread.start(); } } class SubThread implements Runnable{ @Override public void run() { // TODO Auto-generated method stub System.out.println("执行本方法的线程:"+Thread.currentThread().getName()); } }
Связь между Thread и Runnable
-
Исходный код темы
-
Запускаемый исходный код
-
разница
Из приведенной схемы исходного кода нетрудно увидеть, что Thread — это класс, а Runnable — интерфейс, в интерфейсе Runnable есть только один нереализованный метод run, видно, что Runnable не может открыть поток самостоятельно, но полагается на класс Thread для создания Thread, выполнения собственного метода run, для выполнения соответствующей бизнес-логики, так что этот класс имеет характеристики многопоточности.
-
Создавайте дочерние потоки, наследуя Thread и реализуя интерфейс Runable соответственно.
-
Создание дочерних потоков путем наследования от класса Thread
public class Main extends Thread{ public static void main(String[] args) { Main main = new Main(); main.start(); } @Override public void run() { System.out.println("通过继承Thread接口方式创建子线程成功,当前线程名:"+Thread.currentThread().getName()); } }
результат операции:
-
Создайте дочерний поток, реализуя интерфейс Runnable.
public class Main{ public static void main(String[] args) { SubThread subThread = new SubThread(); Thread thread = new Thread(subThread); thread.start(); } } class SubThread implements Runnable{ @Override public void run() { // TODO Auto-generated method stub System.out.println("通过实现Runnable接口创建子线程成功,当前线程名:"+Thread.currentThread().getName()); } }
результат операции:
-
Создание дочерних потоков с использованием анонимных внутренних классов
public class Main{ public static void main(String[] args) { Thread thread = new Thread(new Runnable() { @Override public void run() { // TODO Auto-generated method stub System.out.println("使用匿名内部类方式创建线程成功,当前线程名:"+Thread.currentThread().getName()); } }); thread.start(); } }
результат операции:
-
-
связь
1.Thread — это класс, который реализует интерфейс Runnable, благодаря чему run поддерживает многопоточность. 2
2. В связи с принципом одиночного наследования классов рекомендуется использовать интерфейс Runnable, что может сделать программу более гибкой.
Как реализовать многопоточные возвращаемые значения
Благодаря только что проведенному исследованию мы знаем, что логика многопоточности должна выполняться в методе запуска, а метод запуска не имеет возвращаемого значения, поэтому трудно решить ситуацию, требующую возвращаемого значения, поэтому как реализовать возвращаемое значение дочернего потока?
-
метод ожидания основного потока
Заставив основной поток дождаться завершения дочернего потока.
Метод реализации:
public class Main{ static String str; public static void main(String[] args) { Thread thread = new Thread(new Runnable() { @Override public void run() { str="子线程执行完毕"; } }); thread.start(); //如果子线程还未对str进行赋值,则一直轮转 while(str==null) {} System.out.println(str); } }
-
Используйте метод join() в Thread
Метод join() может заблокировать текущий поток, чтобы дождаться завершения обработки дочерним потоком.
Метод реализации:
public class Main{ static String str; public static void main(String[] args) { Thread thread = new Thread(new Runnable() { @Override public void run() { str="子线程执行完毕"; } }); thread.start(); //如果子线程还未对str进行赋值,则一直轮转 try { thread.join(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(str); } }
Метод соединения может делать больше, чем метод ожидания основного потока.точныйуправления, но метод соединенияУровень детализации управления недостаточен. Например, когда мне нужно управлять дочерним потоком, чтобы присвоить определенное значение строке, а затем выполнить основной поток, нет возможности сделать это с помощью метода соединения.
-
Реализовано через интерфейс Callable: получено через FutureTask или пул потоков
До JDK1.5 потоки не имели возвращаемого значения.Обычно программистам нужно было получить возвращаемое значение подпотоков.Теперь в Java есть свой собственный поток возвращаемых значений, а именноРеализует вызываемый интерфейсПосле выполнения потока, реализующего интерфейс Callable, можно получить объект Future.Вызывая метод get для объекта, можно выполнить логику дочернего потока и получить возвращаемый объект.
Метод реализации 1 (прямое получение этого метода — неверный метод):
public class Main implements Callable<String>{ @Override public String call() throws Exception { // TODO Auto-generated method stub String str = "我是带返回值的子线程"; return str; } public static void main(String[] args) { Main main = new Main(); try { String str = main.call(); /*这种方式为什么是错误方式? 和上文说的一样,run()方法和start()方法的区别就在于 run()方法是线程启动后的回调方法,如果直接调用,相当于没有创建这个线程 还是由主线程去执行。 所以这里的call也一样,如果直接调用call,并没有子线程被创建, 而是相当于直接调用了类中的实例方法,获取了返回值, 从头到尾并没有子线程的存在。*/ System.out.println(str); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
результат операции:
Реализация 2 (с использованием FutureTask):
public class Main implements Callable<String>{ @Override public String call() throws Exception { // TODO Auto-generated method stub String str = "我是带返回值的子线程"; return str; } public static void main(String[] args) { FutureTask<String> task = new FutureTask<String>(new Main()); new Thread(task).start(); try { if(!task.isDone()) { System.out.println("任务没有执行完成"); } System.out.println("等待中..."); Thread.sleep(3000); System.out.println(task.get()); } catch (InterruptedException | ExecutionException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
результат операции:
Метод реализации 3 (использование пула потоков для взаимодействия с приобретением Future):
public class Main implements Callable<String>{ @Override public String call() throws Exception { // TODO Auto-generated method stub String str = "我是带返回值的子线程"; return str; } public static void main(String[] args) throws InterruptedException, ExecutionException { ExecutorService newCacheThreadPool = Executors.newCachedThreadPool(); Future<String> future = newCacheThreadPool.submit(new Main()); if(!future.isDone()) { System.out.println("线程尚未执行结束"); } System.out.println("等待中"); Thread.sleep(300); System.out.println(future.get()); newCacheThreadPool.shutdown(); } }
результат операции:
состояние потока
Потоки Java в основном делятся на следующие шесть состояний:Новое состояние (новый),Запускаемый,Ожидание бесконечно (Ожидание),Время ожидания,Заблокировано,Прекращено.
-
новый (новый)
Новое состояние состоит в том, что поток находится всоздан, но не запущенСостояние, в котором поток только что создан, но не приступает к выполнению своей внутренней логики.
-
Запускаемый
Состояние работы делится наReadyиRunning, когда поток вызывает метод запуска, он не выполняется сразу, а конкурирует за CPU.Когда поток не начинает выполнение, его состояние Ready, а когда поток получает квант времени CPU, он переходит из состояния Ready в рабочее состояние.
-
Ожидающий
Поток в состоянии ожидания не будет просыпаться автоматически, а только будет ждать, пока его разбудят другие потоки.В состоянии ожидания потоку не будет выделяться время ЦП, и он всегда будет заблокирован. Следующие операции заставят поток ждать:
1. Нет объекта. Wait () метод, который устанавливает параметр Timeout.
2. Метод Thread.join() без установки параметра таймаута.
3. Метод LockSupport.park() (На самом деле метод park не предусмотрен LockSupport, а в Unsafe, LockSupport просто инкапсулирует его, можете посмотреть в другом моем блоге."Замок", в котором упоминается этот метод анализа исходного кода ReentrantLock.).
-
Время ожидания
ЦП также не выделяет квант времени для потока, ожидающего крайнего срока, но поток, ожидающий крайнего срока, не должен быть явно разбужен другими потоками, но система автоматически просыпается после истечения времени ожидания. Следующие операции заставят поток ожидать в течение ограниченного времени:
1. Метод Thread.sleep().
2. Метод Object.wait() с установленным параметром timeout.
3. Метод Thread.join() с установленным параметром timeout.
4. Метод LockSupport.parkNanos().
5. Метод LockSupport.parkUntil().
-
Заблокировано
Когда несколько потоков входят в одну и ту же общую область, такую как блок Synchronized, область, контролируемую ReentrantLock и т. д., они захватывают блокировку, и поток, который успешно получает блокировку, продолжает выполняться, в то время как поток, который не получил блокировку замок перейдет в состояние блокировки, ожидая получения блокировки.
-
Прекращено
Состояние потока завершенного потока, выполнение потока завершено.
Разница между сном и ожиданием
Оба метода Sleep и Wait могут заставить потоквремя ожидания, так в чем разница между двумя методами?
1. Метод сна предоставляется Thread, а метод ожидания предоставляется Object.
2. Метод ожидания можно использовать где угодно, а метод ожидания можно использовать только в синхронизированном блоке или синхронизированном методе (поскольку метод ожидания должен быть получен для освобождения блокировки, а блокировка может быть снята только тогда, когда блокировка снята). приобретенный).
3.Метод сна только отдаст ЦП и не снимет блокировку, в то время как метод ожидания не только отдаст ЦП, но и снимет блокировку..
Тестовый код:
public class Main{
public static void main(String[] args) {
Thread threadA = new Thread(new ThreadA());
Thread threadB = new Thread(new ThreadB());
threadA.setName("threadA");
threadB.setName("threadB");
threadA.start();
threadB.start();
}
public static synchronized void print() {
System.out.println("当前线程:"+Thread.currentThread().getName()+"执行Sleep");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("当前线程:"+Thread.currentThread().getName()+"执行Wait");
try {
Main.class.wait(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("当前线程:"+Thread.currentThread().getName()+"执行完毕");
}
}
class ThreadA implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
Main.print();
}
}
class ThreadB implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
Main.print();
}
}
Результаты:
Из приведенных выше результатов можно проанализировать, что когда поток A выполняет спящий режим, он ждет одну секунду, чтобы проснуться, а затем продолжает удерживать блокировку, после этого выполняет код и сразу же освобождает блокировку после выполнения ожидания, не только отказываясь процессор, но также отказывается от блокировки, а затем поток B немедленно удерживает блокировку, чтобы начать выполнение, и выполняет те же шаги, что и поток A. Когда поток B выполняет метод ожидания, блокировка освобождается, а затем поток A получает блокировку и печатает первое выполнение, а затем поток B печатает Finished.
Разница между уведомлением и уведомлением обо всем
-
notify
notify может разбудить поток в состоянии ожидания, приведенный выше код:
public class Main{ public static void main(String[] args) { Object lock = new Object(); Thread threadA = new Thread(new Runnable() { @Override public void run() { synchronized (lock) { try { lock.wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } print(); } } }); Thread threadB = new Thread(new Runnable() { @Override public void run() { synchronized (lock) { print(); lock.notify(); } } }); threadA.setName("threadA"); threadB.setName("threadB"); threadA.start(); threadB.start(); } public static void print() { System.out.println("当前线程:"+Thread.currentThread().getName()+"执行print"); try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println("当前线程:"+Thread.currentThread().getName()+"执行完毕"); } }
Результаты:
объяснение кода: Поток A вызывает ожидание, чтобы войти в состояние бесконечного ожидания сразу после начала выполнения. Если нет другого потока, который мог бы разбудить его, он будет ждать вечно, поэтому в это время B удерживает блокировку, чтобы начать выполнение, и вызывает уведомление. метод, когда выполнение завершено.Этот метод может разбудить поток A в состоянии ожидания, поэтому поток A просыпается и начинает выполнение остальной части кода.
-
notifyAll
notifyAll можно использовать для пробуждения всех ожидающих потоков, чтобы все потоки в состоянии ожидания снова были готовы бороться за блокировку.
public class Main{ public static void main(String[] args) { Object lock = new Object(); Thread threadA = new Thread(new Runnable() { @Override public void run() { synchronized (lock) { try { lock.wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } print(); } } }); Thread threadB = new Thread(new Runnable() { @Override public void run() { synchronized (lock) { print(); lock.notifyAll(); } } }); threadA.setName("threadA"); threadB.setName("threadB"); threadA.start(); threadB.start(); } public static void print() { System.out.println("当前线程:"+Thread.currentThread().getName()+"执行print"); try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println("当前线程:"+Thread.currentThread().getName()+"执行完毕"); } }
Результаты:
Чтобы разбудить поток A в предыдущем примере, это может сделать не только метод notify, но и метод notifyAll, так в чем же разница между ними?
-
разница
Чтобы прояснить разницу между ними, прежде всего, мы должны кратко рассказать о некоторых принципах синхронизации Java.Если вы посмотрите на исходный код java в openjdk, вы можете увидеть, что в объекте java есть блокировка монитора, а объект монитора содержитзамок пулипул ожидания(Подробности этой части в другой статье"Замок"Есть подробное введение, вот краткое описание)
замок пул, если предположить, что несколько объектов входят в синхронизированный блок, чтобы конкурировать за блокировку, и один объект уже получил блокировку в это время, то оставшиеся объекты, которые конкурируют за блокировку, попадут непосредственно в пул блокировок.
пул ожидания, Предположим, поток вызывает метод ожидания объекта, тогда поток напрямую войдет в пул ожидания, и объекты в пуле ожидания не будут конкурировать за блокировки, а будут ждать пробуждения.
Следующее может сказать разницу между notify и notifyAll:
notifyAll позволит всем потокам в пуле ожидания войти в пул блокировок, чтобы конкурировать за блокировки, в то время как notify позволит только случайным образом конкурировать за блокировки одному из потоков..
метод доходности
-
концепция
/** * A hint to the scheduler that the current thread is willing to yield * its current use of a processor. The scheduler is free to ignore this * hint. * * <p> Yield is a heuristic attempt to improve relative progression * between threads that would otherwise over-utilise a CPU. Its use * should be combined with detailed profiling and benchmarking to * ensure that it actually has the desired effect. * * <p> It is rarely appropriate to use this method. It may be useful * for debugging or testing purposes, where it may help to reproduce * bugs due to race conditions. It may also be useful when designing * concurrency control constructs such as the ones in the * {@link java.util.concurrent.locks} package. */ public static native void yield();
Существует длинный комментарий к исходному коду yield о том, что:Когда текущий поток вызывает метод yield, он дает планировщику текущего потока подсказку о том, что текущий поток готов отказаться от использования ЦП, но его роль должна сочетаться с подробным анализом и тестированием, чтобы убедиться, что ожидаемый эффект достигнуто, т.к. планировщик может проигнорировать эту подсказку, не совсем уместно использовать этот метод, возможно, было бы лучше использовать его в тестовой среде.
контрольная работа:
public class Main{ public static void main(String[] args) { Thread threadA = new Thread(new Runnable() { @Override public void run() { System.out.println("ThreadA正在执行yield"); Thread.yield(); System.out.println("ThreadA执行yield方法完成"); } }); Thread threadB = new Thread(new Runnable() { @Override public void run() { System.out.println("ThreadB正在执行yield"); Thread.yield(); System.out.println("ThreadB执行yield方法完成"); } }); threadA.setName("threadA"); threadB.setName("threadB"); threadA.start(); threadB.start(); }
Результаты теста:
Видно, что результаты тестов разные, вот два выбранных.
Первый результат: поток A завершает выполнение метода yield и передает процессор потоку B для выполнения. Затем оба потока продолжают выполнение остальной части кода.
Второй результат: поток A выполняет метод yield и передает процессор потоку B для выполнения, но поток B не отдает процессор после выполнения метода yield, а продолжает выполняться, что являетсяСистема проигнорировала подсказку.
метод прерывания
-
прервать поток
функция прерывания можетпрервать нить, Перед прерыванием метод остановки обычно используется для завершения потока, но метод остановки слишком насильственный.Его характеристика заключается в том, что независимо от того, в каком состоянии был прерванный поток до этого, он будет безоговорочно прерван, что вызовет прерванный поток Последующие действия Некоторые работы по очистке не могут быть выполнены гладко, вызывая ненужные исключения и скрытые опасности, а также могут вызывать проблемы с синхронизацией данных.
-
щадящий метод прерывания
Принцип метода прерывания намного мягче, чем у метода остановки.Когда метод прерывания вызывается для завершения потока, он не завершает поток принудительно, а сообщает потоку, что его следует прервать. также намек на то, что прерванный поток сам решает, следует ли его прерывать. Когда метод прерывания вызывается в потоке:
1. Если поток находится в заблокированном состоянии, немедленно выйдите из заблокированного состояния и сгенерируйте InterruptedException.
2. Если поток находится в состоянии выполнения, установите бит флага прерывания потока в значение true, и заданный поток продолжает выполняться без каких-либо последствий.Когда выполнение заканчивается, поток решает, следует ли его прервать.
Пул потоков
Введение пула потоков используется для решения проблемы многопоточной разработки в повседневной разработке.Если разработчикам необходимо использовать много потоков, эти потоки будут иметь определенное влияние на систему, когда эти потоки часто создаются и уничтожаются. Возможно, системе потребуется больше времени для создания и уничтожения этих потоков, чем это действительно необходимо. Кроме того, в случае многих потоков управление потоками стало большой проблемой.Разработчики обычно переключают свое внимание с функций на управление неорганизованными потоками, что на самом деле очень затратно и энергично.
-
Используйте Executors для создания различных пулов потоков для удовлетворения потребностей различных сценариев.
-
newFixThreadPool(int nThreads)
Пул потоков, указывающий количество рабочих потоков.
-
newCachedThreadPool()
Пул потоков, который обрабатывает большое количество рабочих задач, связанных с прерыванием,
1. Попытка кэширования потоков и повторного использования, когда нет доступных кэшированных потоков, создаются новые рабочие потоки.
2. Если поток бездействует дольше порогового значения, он будет завершен и удален из кеша.
3. Когда система простаивает в течение длительного времени, она не будет потреблять никаких ресурсов.
-
newSingleThreadExecutor()
Создайте уникальный рабочий поток для выполнения задачи, и если поток завершится аварийно, его место займет другой поток. Порядок выполнения задач гарантируется.
-
newSingleThreadScheduledExecutor() и newScheduledThreadPool(int corePoolSize)
Временное или периодическое планирование работы, разница между ними заключается в том, что первый представляет собой один рабочий поток, а второй — многопоточный.
-
newWorkStealingPool()
ForkJoinPool строится внутри, а алгоритм work-stealing используется для параллельной обработки задач, и порядок обработки не гарантируется.
**Структура разделения/объединения.** Среда, которая делит большую задачу на несколько небольших задач для параллельного выполнения и, наконец, суммирует каждую маленькую задачу, чтобы получить результат большой задачи.
-
-
Зачем использовать пулы потоков
Потоки - дефицитный ресурс. Если потоки создаются без ограничений, они будут потреблять системные ресурсы, а пул потоков может заменить разработчика для управления потоками. После завершения выполнения потока он не уничтожит поток, а вернет поток в пул потоков, а затем управляйте им, чтобы можно было повторно использовать потоки.
Таким образом, пул потоков может не только снизить потребление ресурсов, но и улучшить управляемость потоков.
-
Запустите поток, используя пул потоков
public class Main{ public static void main(String[] args) { ExecutorService newFixThreadPool = Executors.newFixedThreadPool(10); newFixThreadPool.execute(new Runnable() { @Override public void run() { // TODO Auto-generated method stub System.out.println("通过线程池启动线程成功"); } }); newFixThreadPool.shutdown(); } }
-
Суждение после выполнения новой задачи выполнить
Чтобы понять этот момент, мы должны сначала поговорить о конструкторе ThreadPoolExecutor, который имеет несколько параметров:
1.corePoolSize: количество основных потоков.
2.maximumPoolSize: максимальное количество потоков, которое может быть создано, когда потоков недостаточно.
3.workQueue: очередь ожидания.
Затем после отправки новой задачи будут вынесены следующие суждения:
1. Если количество запущенных потоков меньше, чем corePoolSize, для обработки задачи создается новый поток, даже если другие потоки в пуле потоков простаивают.
2. Если число в пуле потоков больше или равно corePoolSize и меньше maxPoolSize, новый поток будет создан для обработки задачи только тогда, когда workQueue будет заполнен.
3. Если установленные значения corePoolSize и maxPoolSize совпадают, размер создаваемого пула потоков фиксируется.Если в это время отправляется новая задача, если workQueue не заполнена, она будет помещена в workQueue и ожидает обработки. обработанный.
4. Если количество запущенных потоков больше или равно maxPoolSize, maxPoolSize, то при заполнении workQueue задача будет обрабатываться по стратегии, заданной обработчиком.
-
политика насыщения пула потоков обработчика
ABORTPOLICY: бросает исключение напрямую, по умолчанию.
CallerRunsPolicy: используйте поток, в котором находится вызывающий объект, для выполнения задачи.
DiscardOldestPolicy: отменить верхнюю задачу в очереди и выполнить текущую задачу.
DiscardPolicy: отбрасывать задачи напрямую
настроить.
-
Как выбрать размер пула потоков
Эта проблема не является секретом.В Интернете есть статьи на крупных технологических сайтах.Я напишу наиболее узнаваемую.
Интенсивность ЦП: количество потоков = количество ядер или количество ядер + 1
Интенсивный ввод-вывод: количество потоков = количество ядер ЦП * (1 + среднее время ожидания / среднее время работы)
Конечно, на эту формулу нельзя полностью положиться, и многое зависит от обычного опыта работы, эта формула предназначена только для справки.
Эпилог
В этой статье представлены некоторые из самых базовых знаний о многопоточности и параллелизме Java. Она подходит для начинающих, чтобы получить некоторые базовые знания о многопоточности Java. Если вы хотите узнать больше о параллелизме, вы можете прочитать мой другой блог."Замок".
Добро пожаловать в мой личный блог:Object's Blog