1. Многопользовательское введение
В программировании мы не можем избежать проблем с многопоточным программированием, потому что в большинстве бизнес-систем требуется одновременная обработка. Если это в одновременных сценариях, очень важно многопоточное. Кроме того, во время нашего интервью интервьюер обычно задает нам вопросы о мультипотчике, таких как: как создать ветку? Мы обычно отвечаем так, есть два основных метода. Первое: наследуйте класс потоков и переписывайте метод запуска; Второе: реализуйте запускаемый интерфейс и перепишите метод запуска. Тогда интервьюер определенно спросит, каковы преимущества и недостатки этих двух методов. Независимо от того, что, мы придем к выводу, то есть второй способ использования, потому что объектно-ориентированные сторонники меньше наследия и пытаемся использовать комбинации как можно больше.
В настоящее время мы также можем подумать о том, что делать, если хотим получить обратную стоимость многопоточных? Основываясь на знаниях, которые мы узнали больше, мы подумаем о внедрении подводного интерфейса и переписывании метода вызова. Как использовать так много потоков в реальных проектах? Сколько у них способов?
Во -первых, давайте посмотрим на пример:
Это простой метод создания многопоточных, которые легко понять. В примере, согласно различным бизнес -сценариям, мы можем передать различные параметры в Thread () для реализации различной бизнес -логики. Тем не менее, проблема, выявленная этим методом создания многопоточных, заключается в неоднократном создании потоков, и ее необходимо уничтожить после создания потоков. Если требования к параллельным сценариям низкие, этот метод, кажется, в порядке, но в сценариях с высоким содержанием параллелистики этот метод невозможно, потому что создание потоков очень получает ресурсы. Таким образом, в соответствии с опытом, правильным способом является использование технологии пула потоков. JDK предоставляет множество типов бассейнов потоков для нас на выбор. Для конкретных методов вы можете проверить документацию JDK.
В этом коде нам нужно отметить, что параметры, передаваемые в том, что представляют количество потоков, которые мы настроили. Чем больше, тем лучше? Конечно, нет. Потому что при настройке количества потоков мы должны полностью рассмотреть производительность сервера. Если есть больше конфигураций потоков, производительность сервера может быть не превосходной. Обычно расчеты, выполненные машиной, определяются количеством потоков. Когда количество потоков достигает пика, расчет не может быть выполнен. Если это бизнес -логика, которая потребляет ЦП (больше расчетов), количество потоков и ядер достигнет своего пика. Если это бизнес -логика, которая потребляет ввод -вывод (операционные базы данных, загрузка файлов, загрузка и т. Д.), Чем больше потоков, тем больше потоков, это поможет повысить производительность в определенном смысле.
Другая формула для установки количества потоков:
Y = n*((a+b)/a), где n: количество ядер ЦП, a: время расчета программы при выполнении потока, b: время блокировки программы при выполнении потока. С помощью этой формулы конфигурация количества потоков пула потоков будет ограничена, и мы можем гибко настроить ее в соответствии с фактической ситуацией машины.
2. Многопользовательская оптимизация и сравнение производительности
Технология потока использовалась в недавних проектах, и я столкнулся с множеством проблем во время использования. Воспользовавшись популярностью, я разберусь сравнениями производительности нескольких многопоточных рамок. Те, которые мы освоили, примерно разделены на три типа: первый тип: ThreadPool (Pool Rate Pool) + Countdownlatch (счетчик программы), второй тип: Fork/Join Framework и третий тип параллельного потока JDK8. Вот сравнительная сводка многопоточных производительности этих методов.
Во -первых, предположим, что бизнес -сценарий, в котором в памяти генерируются несколько объектов файла. Здесь 30 000 поточных сна преднамеренно определяется для моделирования бизнес-логики, чтобы сравнить многопоточные результаты этих методов.
1) Одиночная резьба
Этот метод очень прост, но программа очень трудоемкая во время обработки и будет использоваться в течение длительного времени, потому что каждый поток ожидает выполнения текущего потока до его выполнения. Это имеет мало общего с многопоточными, поэтому эффективность очень низкая.
Сначала создайте объект файла, код выглядит следующим образом:
public class fileinfo {private String filename; // Имя файла Private String filetype; // Тип файла Private String Filessize; // Размер файла Private String fileMd5; // код MD5 Private String fileVersionNo; // Номер версии файла public fileInfo () {super (); } public fileInfo (String FileName, String fileType, String FileSize, String fileMD5, String FileVersionNo) {super (); this.filename = имя файла; this.filetype = filetype; this.filesize = filesize; this.filemd5 = filemd5; this.fileversionno = fileversionno; } public String getFilEname () {return filename; } public void setFileName (String fileName) {this.filename = filename; } public String getFileType () {return fileType; } public void setFileType (String fileType) {this.fileType = fileType; } public String getFilesize () {return FileSize; } public void setFilesize (строки файлов размером) {this.Filesize = fileSize; } public String getFileMd5 () {return fileMd5; } public void setFileMd5 (string fileMd5) {this.filemd5 = fileMd5; } public String getFileVersionNo () {return fileVersionNo; } public void setFileVersionNo (String fileVersionNo) {this.fileversionno = fileversionno; }Затем имитируйте бизнес -обработку, создайте 30 000 файловых объектов, спят по потоку на 1 мс и устанавливает 1000 мс ранее, и обнаруживает, что время очень длинное, и все затмение застряло, поэтому измените время на 1 мс.
открытый тест класса {частный статический список <fieInfo> fileList = new ArrayList <FileInfo> (); public static void main (string [] args) бросает прерывание {createFileInfo (); long starttime = System.currentTimeMillis (); for (fileinfo fi: filelist) {thread.sleep (1); } long EndTime = System.CurrentTimeMiLlis (); System.out.println ("One Thread Time-Contring:"+(EndTime-starttime)+"ms"); } private static void createfileinfo () {for (int i = 0; i <30000; i ++) {filelist.add (new fileInfo («Фронтальная фотография идентификационной карты», «jpg», «101522», «md5»+i, «1»)); }}}Результаты теста следующие:
Можно видеть, что генерирование 30 000 файловых объектов занимает много времени, почти 1 минута, и эффективность относительно низкая.
2) Threadpool (пул потоков) +countdownlatch (счетчик программы)
Как следует из названия, Countdownlatch - счетчик потока. Его процесс выполнения заключается в следующем: во -первых, метод wait () вызывается в основном потоке, а основной поток блокируется, а затем счетчик программы передается в объект потока в качестве параметра. Наконец, после того, как каждый поток завершает выполнение задачи, метод обратного отсчета () вызывается для обозначения выполнения задачи. После того, как Countdown () выполняется несколько раз, ожидание основного потока () будет недействительным. Процесс реализации выглядит следующим образом:
public class test2 {private static executorservice executor = executors.newfixedthreadpool (100); Частный статический обратный отсчет, countdownlatch = new Countdownlatch (100); Частный статический список <FieLinfo> fileList = new ArrayList <FieleInfo> (); Частный статический список <list <fileinfo >> list = new Arraylist <> (); public static void main (string [] args) бросает прерывание {createFileInfo (); addlist (); long starttime = System.currentTimeMillis (); int i = 0; for (list <fileinfo> fi: list) {executor.submit (new filerunnable (countdownlatch, fi, i)); i ++; } countdownlatch.await (); Long EndTime = System.CurrentTimeMillis (); Исполнитель.shutdown (); System.out.println (i+"Потоки занимают время:"+(endtime-starttime)+"ms"); } private static void createFileInfo () {for (int i = 0; i <30000; i ++) {fileList.Add (new FileInfo («Фотография передней идентификационной карты», «jpg», «101522», «md5»+i, «1»)); }} private static void addlist () {for (int i = 0; i <100; i ++) {list.add (fileList); }}}Filerunnable Class:
/** * Многопользовательская обработка * @author wangsj * * @param <t> */public class filerunnable <t> реализует runnable {private countdownlatch countdownlatch; частный список <T> список; частный int i; public filerunnable (countdownlatch countdownlatch, list <t> list, int i) {super (); this.countdownlatch = countdownlatch; this.list = list; this.i = i; } @Override public void run () {for (t t: list) {try {thread.sleep (1); } catch (прерванное искусство e) {e.printstacktrace (); } countdownlatch.countdown (); }}}Результаты теста следующие:
3) Форма/соединение
JDK начал с версии 7, и появилась фрейк -фрейк/соединение. С буквальной точки зрения, Форк разделяет, а присоединение - это слияние, поэтому идея этой структуры такова. Разделите задачу через вилку, а затем присоединяйтесь, чтобы объединить результаты после выполнения и обобщенных символов. Например, мы хотим рассчитать несколько чисел, которые добавляются непрерывно, 2+4+5+7 =? , Как мы используем структуру вилки/соединения, чтобы завершить его? Идея состоит в том, чтобы разделить молекулярные задачи. Мы можем разделить эту операцию на две подзадачи, один рассчитывает 2+4, а другой вычисляет 5+7. Это процесс вилки. После завершения расчета результаты расчета этих двух подзадач обобщены и получена сумма. Это процесс соединения.
Идея выполнения кадры вилки/соединения: во -первых, разделите задачи и используйте класс вилки, чтобы разделить большие задачи на несколько подзадач. Этот процесс сегментации должен быть определен в соответствии с фактической ситуацией, пока разделенные задачи не станут достаточно малыми. Затем класс соединения выполняет задачу, а разделенные подзадачи находятся в разных очередах. Несколько потоков получают задачи из очереди и выполняют их. Результаты выполнения помещаются в отдельную очередь. Наконец, поток запускается, результаты получены в очереди, и результаты объединены.
Несколько классов используются для использования структуры вилки/соединения. Для использования класса вы можете обратиться к JDK API. Используя эту структуру, вам нужно унаследовать класс Forkjointask. Обычно вам нужно только унаследовать его подклассу Recurrusivetask или рецирсивизацию. Recursivetask используется для сцен с результатами возврата, а рецирсивизация используется для сцен без результатов возврата. Для выполнения Forkjointask требуется выполнение Forkjoinpool, которое используется для поддержания разделенных подзадач, добавленных в разные очереди задач.
Вот код реализации:
Общедоступный класс Test3 {Private Static List <fileInfo> fileList = new ArrayList <FileInfo> (); // частное статическое статическое форкжуинпул forkjoinpool = new forkjoinpool (100); // private static aboge <fileinfo> job = new Job <> (filelist.size ()/100, filelist); public static void main (string [] args) {createfileinfo (); long starttime = System.currentTimeMillis (); Forkjoinpool forkjoinpool = new forkjoinpool (100); // разделить задание задания <FileInfo> job = new Job <> (fileList.Size ()/100, fileList); // отправить задачу и вернуть результат forkjointask <Integer> fjtresult = forkjoinpool.submit (job); // block while (! Job.isdone ()) {System.out.println ("Задача завершена!"); } long EndTime = System.CurrentTimeMiLlis (); System.out.println ("fork/join framework trime-contring:"+(endtime-starttime)+"ms"); } private static void createFileInfo () {for (int i = 0; i <30000; i ++) {fileList.Add (new FileInfo («Фотография передней идентификационной карты», «jpg», «101522», «md5»+i, «1»)); }}}/** * Выполнить класс задачи * @author wangsj * */job public class <t> extends recurrysivetask <integer> {private static final long serialversionuid = 1l; частный int count; частный список <T> joblist; публичная работа (int count, list <t> joblist) {super (); this.count = count; this.joblist = jublist; } /*** Выполните задачу, аналогично методу выполнения, который реализует выполняемый интерфейс* /@Override Protected Integer Compute () {// разделить задачу if (joblist.size () <= countJob (); return jublist.size (); } else {// продолжать создавать задачу, пока она не может быть разложена и выполнена списка <recurusivetask <long >> fork = new LinkedList <recurusivetask <long >> (); // разделить нуклеическую задачу, здесь используется метод дихотомии int countjob = joblist.size ()/2; List <T> левый список = joblist.sublist (0, countjob); Список <t> rightlist = joblist.sublist (countjob, jublist.size ()); // присваивать задачи задание левая Job rightjob = новая работа <> (count, rightlist); // выполнить задачу Leatsjob.fork (); rightjob.fork (); return integer.parseint (leftjob.join (). toString ()) +integer.parseint (rightjob.join (). toString ()); }} / *** Выполнить метод задачи* / private void executjob () {for (t job: joblist) {try {thread.sleep (1); } catch (прерванное искусство e) {e.printstacktrace (); }}}Результаты теста следующие:
4) параллельная потоковая передача JDK8
Параллельный поток - одна из новых особенностей JDK8. Идея состоит в том, чтобы превратить поток, выполненный последовательно в параллельный поток, который реализуется путем вызова метода Parallel (). Параллельный поток делит поток на несколько блоков данных, использует разные потоки для обработки потоков различных блоков данных и, наконец, объединяет результаты обработки каждого блока потока данных, аналогично структуре вилки/соединения.
Параллельный поток по умолчанию использует общедоступный пул потоков. Количество потоков - это значение по умолчанию. Согласно количеству ядер машины, мы можем правильно настроить размер потоков. Регулировка количества потоков достигается следующими способами.
System.setProperty ("java.util.concurrent.forkjoinpool.common.parallelism", "100");Ниже приведен процесс реализации кода, который очень просто:
public class test4 {private Static List <fieinfo> fileList = new ArrayList <FieLinfo> (); public static void main (string [] args) {// system.setProperty ("java.util.concurrent.forkjoinpool.common.parallelism", "100"); createfileinfo (); long starttime = System.currentTimeMillis (); filelist.parallelstream (). Foreach (e -> {try {thread.sleep (1);} catch (прерывание Exception f) {f.printstackTrace ();}}); Long EndTime = System.CurrentTimeMillis (); System.out.println ("jdk8 Параллельное время потоковой передачи:"+(endtime-starttime)+"ms");} private static void createfileinfo () {for (int i = 0; i <30000; i ++) {filelist.add (new fileinfo ("Front Photo of Id) карта "," jpg "," 101522 "," md5 "+i," 1 ")); }}}Ниже приведен тест. Количество пулов потоков не установлено в первый раз. По умолчанию используется. Результаты теста следующие:
Мы видели, что результат не очень идеален и занимает много времени. Затем установите количество пулов потоков, то есть добавьте следующий код:
System.setProperty ("java.util.concurrent.forkjoinpool.common.parallelism", "100");Затем тест был проведен, и результаты были следующими:
На этот раз это занимает меньше времени и идеально.
3. Резюме
Подводя итог в приведенных выше ситуациях, используя один поток в качестве эталона, самым длинным трудоемким является структура нативного вилка/соединения. Несмотря на то, что здесь настроено количество пулов потоков, параллельный поток JDK8 с количеством пулов потоков является беднее. Параллельная потоковая реализация, код прост и прост в понимании, и нам не нужно писать дополнительно для петли. Мы можем завершить все методы ParallelStream, и количество кода значительно уменьшено. Фактически, основным уровнем параллельной потоковой передачи все еще является структура вилки/соединения, которая требует, чтобы мы гибко использовали различные технологии в процессе разработки, чтобы отличить преимущества и недостатки различных технологий, чтобы лучше обслуживать нас.