Thread’ом java не испортишь: часть v
Содержание:
- Методы wait и notify
- Дополнительные материалы
- Пример создания потока. Наследуем класс Thread
- Создание потока с интерфейсом Runnable
- Как выбирать производителя твердотельных накопителей
- Looper
- Остались вопросы? Бесплатная консультация по телефону:
- Нет необходимости писать свое
- Запуск задач с помощью java.util.concurrent.ExecutorService
- Java Thread Join(). Теория
- Новые возможности пакета java.uti.concurrent
- 2 Класс InputStream
- 1 Нововведения в Java 8: Функциональное программирование
- Конкуренция и параллелизм
Методы wait и notify
Последнее обновление: 27.04.2018
Иногда при взаимодействии потоков встает вопрос о извещении одних потоков о действиях других. Например, действия одного потока зависят от результата действий другого потока,
и надо как-то известить один поток, что второй поток произвел некую работу. И для подобных ситуаций у класса Object определено ряд методов:
- 
wait(): освобождает монитор и переводит вызывающий поток в состояние ожидания до тех пор, пока другой поток не вызовет метод 
- 
notify(): продолжает работу потока, у которого ранее был вызван метод 
- 
notifyAll(): возобновляет работу всех потоков, у которых ранее был вызван метод 
Все эти методы вызываются только из синхронизированного контекста — синхронизированного блока или метода.
Рассмотрим, как мы можем использовать эти методы. Возьмем стандартную задачу из прошлой темы — «Производитель-Потребитель» («Producer-Consumer»):
пока производитель не произвел продукт, потребитель не может его купить. Пусть производитель должен произвести 5 товаров, соответственно потребитель
должен их все купить. Но при этом одновременно на складе может находиться не более 3 товаров.
Для решения этой задачи задействуем методы  и :
public class Program {
 
    public static void main(String[] args) {
         
        Store store=new Store();
        Producer producer = new Producer(store);
        Consumer consumer = new Consumer(store);
        new Thread(producer).start();
        new Thread(consumer).start();
    }
}
// Класс Магазин, хранящий произведенные товары
class Store{
   private int product=0;
   public synchronized void get() {
      while (product<1) {
         try {
            wait();
         }
         catch (InterruptedException e) {
         }
      }
      product--;
      System.out.println("Покупатель купил 1 товар");
      System.out.println("Товаров на складе: " + product);
      notify();
   }
   public synchronized void put() {
       while (product>=3) {
         try {
            wait();
         }
         catch (InterruptedException e) { 
         } 
      }
      product++;
      System.out.println("Производитель добавил 1 товар");
      System.out.println("Товаров на складе: " + product);
      notify();
   }
}
// класс Производитель
class Producer implements Runnable{
 
    Store store;
    Producer(Store store){
       this.store=store; 
    }
    public void run(){
        for (int i = 1; i < 6; i++) {
            store.put();
        }
    }
}
// Класс Потребитель
class Consumer implements Runnable{
     
     Store store;
    Consumer(Store store){
       this.store=store; 
    }
    public void run(){
        for (int i = 1; i < 6; i++) {
            store.get();
        }
    }
}
Итак, здесь определен класс магазина, потребителя и покупателя. Производитель в методе  добавляет в объект Store с помощью его метода
 6 товаров. Потребитель в методе  в цикле обращается к методу  объекта Store для получения
этих товаров. Оба метода Store —  и  являются синхронизированными.
Для отслеживания наличия товаров в классе Store проверяем значение переменной . По умолчанию товара нет, поэтому переменная равна .
Метод  — получение товара должен срабатывать только при наличии хотя бы одного товара. Поэтому в методе
проверяем, отсутствует ли товар:
while (product<1)
Если товар отсутсвует, вызывается метод . Этот метод освобождает монитор объекта Store и блокирует выполнение метода get, пока для этого же монитора не будет вызван
метод .
Когда в методе  добавляется товар и вызывается , то метод  получает монитор и выходит из
конструкции , так как товар добавлен. Затем имитируется получение покупателем товара. Для этого
выводится сообщение, и уменьшается значение product: . И в конце вызов метода  дает сигнал методу  продолжить работу.
В методе  работает похожая логика, только теперь метод  должен срабатывать, если в магазине не более трех товаров. Поэтому в цикле проверяется наличие товара, и если товар уже есть,
то освобождаем монитор с помощью  и ждем вызова  в методе .
И теперь программа покажет нам другие результаты:
Производитель добавил 1 товар Товаров на складе: 1 Производитель добавил 1 товар Товаров на складе: 2 Производитель добавил 1 товар Товаров на складе: 3 Покупатель купил 1 товар Товаров на складе: 2 Покупатель купил 1 товар Товаров на складе: 1 Покупатель купил 1 товар Товаров на складе: 0 Производитель добавил 1 товар Товаров на складе: 1 Производитель добавил 1 товар Товаров на складе: 2 Покупатель купил 1 товар Товаров на складе: 1 Покупатель купил 1 товар Товаров на складе: 0
Таким образом, с помощью  в методе  мы ожидаем, когда производитель добавит новый продукт. А после добавления
вызываем , как бы говоря, что на складе освободилось одно место, и можно еще добавлять.
А в методе  с помощью  мы ожидаем освобождения места на складе. После того, как место освободится, добавляем товар и
через  уведомляем покупателя о том, что он может забирать товар.
НазадВперед
Дополнительные материалы
Чтение
- Блог Алексея Шипилёва — знаю, что очевидно, но просто грех не упомянуть
- Блог Черемина Руслана — последнее время не пишет активно, нужно искать в блоге его старые записи, поверьте это стоит того — там кладезь
- Хабр Глеба Смирнова — есть отличные статьи про многопоточность и модель памяти
- Блог Романа Елизарова — заброшен, но археологические раскопки провести нужно. В целом Роман очень много сделал для просветления народа в области теории многопоточного программирования, ищите его в медиа.
Подкасты
- SDCast #62: в гостях Александр Титов и Амир Аюпов, инженеры из Intel и Алексей Маркин, программист из МЦСТ
- SDCast #63: в гостях Алексей Маркин, программист из МЦСТ
- Разбор Полетов: #107 Истории альпинистов
- Разбор Полетов: #154 Кишочки — Атака на Новый Год
Видео
- Computer Science Center — Лекция 11. Модели памяти и проблемы видимости
- Теория и практика многопоточного программирования
Пример создания потока. Наследуем класс Thread
Мы можем наследовать класс для создания собственного класса Thread и переопределить метод . Тогда мы можем создать экземпляр этого класса и вызвать метод для того, чтобы выполнить метод .
Вот простой пример того, как наследоваться от класса Thread:
Java
package ua.com.prologistic;
public class MyThread extends Thread {
    public MyThread(String name) {
        super(name);
    }
    @Override
    public void run() {
        System.out.println(«Стартуем наш поток » + Thread.currentThread().getName());
        try {
            Thread.sleep(1000);
            // для примера будем выполнять обработку базы данных
            doDBProcessing();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(«Заканчиваем наш поток » + Thread.currentThread().getName());
    }
    // метод псевдообработки базы данных
    private void doDBProcessing() throws InterruptedException {
        Thread.sleep(5000);
    }
}
| 1 | packageua.com.prologistic; publicclassMyThreadextendsThread{ publicMyThread(Stringname){ super(name); } @Override publicvoidrun(){ System.out.println(«Стартуем наш поток «+Thread.currentThread().getName()); try{ Thread.sleep(1000); // для примера будем выполнять обработку базы данных doDBProcessing(); }catch(InterruptedExceptione){ e.printStackTrace(); } System.out.println(«Заканчиваем наш поток «+Thread.currentThread().getName()); } // метод псевдообработки базы данных privatevoiddoDBProcessing()throwsInterruptedException{ Thread.sleep(5000); } } | 
Вот тестовая программа, показывающая наш поток в работе:
Java
package ua.com.prologistic;
public class ThreadRunExample {
    public static void main(String[] args){
        Thread t1 = new Thread(new HeavyWorkRunnable(), «t1»);
        Thread t2 = new Thread(new HeavyWorkRunnable(), «t2»);
        System.out.println(«Стартуем runnable потоки»);
        t1.start();
        t2.start();
        System.out.println(«Runnable потоки в работе»);
        Thread t3 = new MyThread(«t3»);
        Thread t4 = new MyThread(«t4»);
        System.out.println(«Стартуем наши кастомные потоки»);
        t3.start();
        t4.start();
        System.out.println(«Кастомные потоки в работе»);
    }
}
| 1 | packageua.com.prologistic; publicclassThreadRunExample{ publicstaticvoidmain(Stringargs){ Thread t1=newThread(newHeavyWorkRunnable(),»t1″); Thread t2=newThread(newHeavyWorkRunnable(),»t2″); System.out.println(«Стартуем runnable потоки»); t1.start(); t2.start(); System.out.println(«Runnable потоки в работе»); Thread t3=newMyThread(«t3»); Thread t4=newMyThread(«t4»); System.out.println(«Стартуем наши кастомные потоки»); t3.start(); t4.start(); System.out.println(«Кастомные потоки в работе»); } } | 
Создание потока с интерфейсом Runnable
Есть более сложный вариант создания потока. Для создания нового потока нужно реализовать интерфейс Runnable. Вы можете создать поток из любого объекта, реализующего интерфейс Runnable и объявить метод run().
Внутри метода run() вы размещаете код для нового потока. Этот поток завершится, когда метод вернёт управление.
Когда вы объявите новый класс с интерфейсом Runnable, вам нужно использовать конструктор:
В первом параметре указывается экземпляр класса, реализующего интерфейс. Он определяет, где начнётся выполнение потока. Во втором параметре передаётся имя потока.
После создания нового потока, его нужно запустить с помощью метода start(), который, по сути, выполняет вызов метода run().
Создадим новый поток внутри учебного проекта в виде вложенного класса и запустим его.
Внутри конструктора MyRunnable() мы создаём новый объект класса Thread
thread = new Thread(this, "Поток для примера");
В первом параметре использовался объект this, что означает желание вызвать метод run() этого объекта. Далее вызывается метод start(), в результате чего запускается выполнение потока, начиная с метода run(). В свою очередь метод запускает цикл для нашего потока. После вызова метода start(), конструктор MyRunnable() возвращает управление приложению. Когда главный поток продолжает свою работу, он входит в свой цикл. После этого оба потока выполняются параллельно.
Можно запускать несколько потоков, а не только второй поток в дополнение к первому. Это может привести к проблемам, когда два потока пытаюсь работать с одной переменной одновременно.
Как выбирать производителя твердотельных накопителей
Looper
Поток имеет в своём составе сущности Looper, Handler, MessageQueue.
Каждый поток имеет один уникальный Looper и может иметь много Handler.
Считайте Looper вспомогательным объектом потока, который управляет им. Он обрабатывает входящие сообщения, а также даёт указание потоку завершиться в нужный момент.
Поток получает свой Looper и MessageQueue через метод Looper.prepare() после запуска. Looper.prepare() идентифицирует вызывающий потк, создаёт Looper и MessageQueue и связывает поток с ними в хранилище ThreadLocal. Метод Looper.loop() следует вызывать для запуска Looper. Завершить его работу можно через метод looper.quit().
Используйте статический метод getMainLooper() для доступа к Looper главного потока:
Создадим два потока. Один запустим в основном потоке, а второй отдельно от основного. Нам будет достаточно двух кнопок и метки.
Обратите внимание, как запускаются потоки. Первый поток запускается с помощью метода start(), а второй — run()
Затем проверяем, в каком потоке мы находимся.
Эта тема достаточно сложная и для большинства не представляет интереса и необходимости изучать.
В Android потоки в чистом виде используются всё реже и реже, у системы есть собственные способы.
Остались вопросы? Бесплатная консультация по телефону:
Нет необходимости писать свое
Даг Ли создал отличную открытую библиотеку утилит параллельности, , которая включает объекты-мьютексы, семафоры, коллекции, такие как очереди и хэш-таблицы, хорошо работающие при параллельном доступе, и несколько реализаций рабочей очереди. Класс из этого пакета — эффективная, широко использующаяся, правильная реализация пула потоков, основанного на рабочей очереди. Прежде чем пытаться писать собственное программное обеспечение, которое вполне может оказаться неправильным, вы можете рассмотреть использование некоторых утилит в . Ссылки и дополнительную информацию смотрите в разделе .
Библиотека также служит вдохновителем для JSR 166, рабочей группы Java Community Process (JCP), которая будет производить набор параллельных утилит для включения в библиотеку классов Java в пакете , и которая готовит выпуск Java Development Kit 1.5.
Запуск задач с помощью java.util.concurrent.ExecutorService
Облегчив с помощью интерфейса Callable создание задач для параллельного выполнения, пакет java.util.concurrent также берет на себя работу по запуску и остановке потоков. Вместо объекта Thread предлагается использовать объект типа ExecutorService, с помощью которого пользователь может просто поместить задачу в очередь на выполнение и ждать получения результата. Можно сказать, что ExecutorService – это значительно усовершенствованная реализация шаблона WorkerThread.
ExecutorService – это интерфейс, поэтому для выполнения задач используются его конкретные потомки, адаптированные под требования разрабатываемого приложения. Однако программисту нет необходимости создавать собственную реализацию ExecutorService, так как в пакете java.util.concurrent уже присутствуют различные варианты реализации ExecutorService. Доступ к ним можно получить через статические методы служебного класса Executors, метод которого newFixedThreadPool возвращает объект типа ExecutorService со встроенной поддержкой шаблона ThreadPool. Также в классе Executors есть и другие методы для создания объектов ExecutorService с различными свойствами.
Наибольший интерес в ExecutorService представляет метод submit, через который задача ставится в очередь на выполнение. На вход этот метод принимает объект типа Callable или Runnable, а возвращает некий параметризованный объект типа Future. Этот объект можно использовать для доступа к результату выполнения задачи, который будет возвращен из метода call соответствующего Callable-объекта. При этом через объект Future можно проверить, закончено ли уже выполнение задачи – с помощью метода isDone и через метод get получить доступ к результату или исключительной ситуации, если в процессе выполнения задачи произошла ошибка.
Таким образом, при запуске задач с помощью классов из пакета java.util.concurrent не требуется прибегать к низкоуровневой поточной функциональности класса Thread, достаточно создать объект типа ExecutorService с нужными свойствами и передать ему на исполнение задачу типа Callable. Впоследствии можно легко просмотреть результат выполнения этой задачи с помощью объекта Future, как показано в листинге 4.
Листинг 4. Запуск задачи с помощью классов пакета java.util.concurrent
1 public class ExecutorServiceSample {
2     public static void main(String[] args) {
3         //создать ExecutorService на базе пула из пяти потоков
4         ExecutorService es1 = Executors.newFixedThreadPool(5);
5         //поместить задачу в очередь на выполнение
6         Future<String> f1 = es1.submit(new CallableSample());        
7         while(!f1.isDone()) {
8             //подождать пока задача не выполнится
9         }
10        try {
11            //получить результат выполнения задачи
12            System.out.println("task has been completed : " + f1.get());
13        } catch (InterruptedException ie) {           
14            ie.printStackTrace(System.err);
15        } catch (ExecutionException ee) {
16            ee.printStackTrace(System.err);
17        }
18        es1.shutdown();
19    }
20}
Стоит обратить внимание на строку 18, где происходит остановка объекта ExecutorService с помощью метода shutdown. Дело в том, что потоки в объекте ExecutorService не останавливаются сами, как обычно, поэтому их необходимо явно остановить с помощью этого метода, при этом если в ExecutorService находятся невыполненные задачи, то потоки будут остановлены только, когда завершится последняя задача
Java Thread Join(). Теория
. Этот метод приостановит выполнение текущего потока до тех пор, пока другой поток не закончит свое выполнение. Если поток прерывается, бросается .
: Этот метод приостановит выполнение текущего потока на указанное время в миллисекундах. Выполнение этого метода зависит от реализации ОС, поэтому Java не гарантирует, что текущий поток будет ждать указанное вами время.
: Этот метод приостановит выполнение текущего потока до тех пор, пока другой поток не закончит свое выполнение на время заданное в миллисекундах плюс наносекундах.
Вот простой пример, показывающий использование метода . Цель программы: убедиться в том, что третий поток начнет работу только тогда, когда первый закончит выполнение.
Java
package ua.com.prologistic;
public class ThreadJoinExample {
    public static void main(String[] args) {
        Thread t1 = new Thread(new MyRunnable(), «t1»);
        Thread t2 = new Thread(new MyRunnable(), «t2»);
        Thread t3 = new Thread(new MyRunnable(), «t3»);
t1.start();
        //стартуем второй поток только после 2-секундного ожидания первого потока (или когда он умрет/закончит выполнение)
        try {
            t1.join(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
t2.start();
        //стартуем 3-й поток только после того, как 1 поток закончит свое выполнение
        try {
            t1.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
t3.start();
        //даем всем потокам возможность закончить выполнение перед тем, как программа (главный поток) закончит свое выполнение
        try {
            t1.join();
            t2.join();
            t3.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(«Все потоки отработали, завершаем программу»);
    }
}
class MyRunnable implements Runnable{
    @Override
    public void run() {
        System.out.println(«Поток начал работу:::» + Thread.currentThread().getName());
        try {
            Thread.sleep(4000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(«Поток отработал:::» + Thread.currentThread().getName());
    }
}
| 1 | packageua.com.prologistic; publicclassThreadJoinExample{ publicstaticvoidmain(Stringargs){ Thread t1=newThread(newMyRunnable(),»t1″); Thread t2=newThread(newMyRunnable(),»t2″); Thread t3=newThread(newMyRunnable(),»t3″); t1.start(); //стартуем второй поток только после 2-секундного ожидания первого потока (или когда он умрет/закончит выполнение) try{ t1.join(2000); }catch(InterruptedExceptione){ e.printStackTrace(); } t2.start(); //стартуем 3-й поток только после того, как 1 поток закончит свое выполнение try{ t1.join(); }catch(InterruptedExceptione){ e.printStackTrace(); } t3.start(); //даем всем потокам возможность закончить выполнение перед тем, как программа (главный поток) закончит свое выполнение try{ t1.join(); t2.join(); t3.join(); }catch(InterruptedExceptione){ e.printStackTrace(); } System.out.println(«Все потоки отработали, завершаем программу»); } } classMyRunnableimplementsRunnable{ @Override publicvoidrun(){ System.out.println(«Поток начал работу:::»+Thread.currentThread().getName()); try{ Thread.sleep(4000); }catch(InterruptedExceptione){ e.printStackTrace(); } System.out.println(«Поток отработал:::»+Thread.currentThread().getName()); } } | 
Результат выполнения программы:
Java
Поток начал работу:::t1
Поток начал работу:::t2
Поток отработал:::t1
Поток начал работу:::t3
Поток отработал:::t2
Поток отработал:::t3
Все потоки отработали, завершаем программу
| 1 | Потокначалработу::t1 Потокначалработу::t2 Потокотработал::t1 Потокначалработу::t3 Потокотработал::t2 Потокотработал::t3 Всепотокиотработали,завершаемпрограмму | 
Следите за обновлениями раздела Многопоточность и параллелизм в Java
Новые возможности пакета java.uti.concurrent
Платформа Java постоянно развивается, и поэтому к существующей функциональности все время добавляются новые возможности. Иногда новая функциональность берется из уже существующих сторонних библиотек, при этом речь не идет о банальном копировании, а скорее о переосмыслении и доработке уже существующих решений. Подобным способом в версию Java 5 был добавлен пакет java.util.concurrent, включающий в себя множество уже проверенных и хорошо зарекомендовавших себя приемов для параллельного выполнения задач (этот пакет — только одно из множества важных нововведений, представленных в Java 5).
В рамках этой статьи интерес представляют уже готовые к использованию реализации шаблонов WorkerThread и ThreadPool, а также еще один способ реализации задач для параллельного выполнения, кроме упоминавшихся класса Thread и интерфейса Runnable. Ещё в пакете java.util.concurrent находятся два подпакета: java.util.concurrent.locks и java.util.concurrent.atomic, с которыми тоже стоит ознакомиться, так как они значительно упрощают организацию взаимодействия между потоками и параллельного доступа к данным.
2 Класс InputStream
Класс интересен тем, что является классом-родителем для сотен классов-наследников. В нем самом нет никаких данных, однако у него есть методы, которые есть у всех его классов-наследников.
Объекты-потоки вообще редко хранят в себе данные. Поток — это инструмент чтения/записи данных, но не хранения. Хотя бывают и исключения.
Методы класса и всех его классов-наследников:
| Методы | Описание | 
|---|---|
| Читает один байт из потока | |
| Читает массив байт из потока | |
| Читает все байты из потока | |
| Пропускает байт в потоке (читает и выкидывает) | |
| Проверяет, сколько байт еще осталось в потоке | |
| Закрывает поток | 
Вкратце пройдемся по этим методам:
Метод
Метод читает один байт из потока и возвращает его. Вас может сбить тип результата — , однако так было сделано, потому что тип — это стандарт всех целых чисел. Три первые байта типа будут равны нулю.
Метод
Это вторая модификация метода . Он позволяет считать из сразу массив байт. Массив для сохранения байт нужно передать в качестве параметра. Метод возвращает число — количество реально прочитанных байт.
Допустим у вас буфер на 10 килобайт, и вы читаете данные из файла с помощью класса . Если файл содержит всего 2 килобайта, все данные будут помещены в массив-буфер, а метод вернет число 2048 (2 килобайта).
Метод
Очень хороший метод. Просто считывает все данные из , пока они не закончатся, и возвращает их виде единого массива байт. Очень удобен для чтения небольших файлов. Большие файлы могут физически не поместиться в память, и метод кинет исключение.
Метод
Этот метод позволяет пропустить n первых байт из объекта . Поскольку данные читаются строго последовательно, этот метод просто вычитывает n первых байт из потока и выбрасывает их.
Возвращает число байт, которые были реально пропущены (если поток закончился раньше, чем прокрутили байт).
Метод
Метод возвращает количество байт, которое еще осталось в потоке
Метод
Метод закрывает поток данных и освобождает связанные с ним внешние ресурсы. После закрытия потока данные из него читать больше нельзя.
Давайте напишем пример программы, которая копирует очень большой файл. Его нельзя весь считать в память с помощью метода . Пример:
| Код | Примечание | 
|---|---|
| для чтения из файла для записи в файл Буфер, в который будем считывать данные Пока данные есть в потоке Считываем данные в буфер Записываем данные из буфера во второй поток | 
В этом примере мы использовали два класса: — наследник для чтения данных из файла, и класс — наследник для записи данных в файл. О втором классе расскажем немного позднее.
Еще один интересный момент — это переменная . Когда из файла будет читаться последний блок данных, легко может оказаться, что его длина меньше 64Кб. Поэтому в output нужно тоже записать не весь буфер, а только его часть: первые байт. Именно это и делается в методе .
1 Нововведения в Java 8: Функциональное программирование
Вместе с выходом Java 8 в ней появилась мощная поддержка функционального программирования. Можно даже сказать, долгожданная поддержка функционального программирования. Код стал писаться быстрее, хотя читать его стало сложнее
Перед изучением функционального программирования в Java, рекомендуем хорошо разобраться в трех вещах:
- ООП, наследование и интерфейсы (1-2 уровни квеста Java Core).
- Дефолтная реализация методов в интерфейсе.
- Внутренние и анонимные классы.
Хорошая новость заключается в том, что без знания всего этого можно пользоваться многими возможностями функционального программирования в Java. Плохая новость — понять, как именно все устроено и как все работает, без тех же внутренних анонимных классов уже сложно.
В ближайших лекциях мы сосредоточимся на том, как легко и просто пользоваться возможностями функционального программирования в Java, без глубокого понимания, как оно устроено.
Чтобы разобраться во всех нюансах функционального программирования в Java, нужны месяцы. Читать же такой код можно научиться за несколько часов. Поэтому предлагаем начать с малого. Да хоть с тех же потоков ввода-вывода.
Конкуренция и параллелизм
найдешь
- Конкуренция — это способ одновременного решения множества задач
- Параллелизм — это способ выполнения разных частей одной задачи
тут
- Наличие нескольких потоков управления (например Thread в Java, корутина в Kotlin), если поток управления один, то конкурентного выполнения быть не может
- Недетерминированный результат выполнения. Результат зависит от случайных событий, реализации и того как была проведена синхронизация. Даже если каждый поток полностью детерминированный, итоговый результат будет недетерминированным
- Необязательно имеет несколько потоков управления
- Может приводить к детерминированному результату, так например, результат умножения каждого элемента массива на число, не изменится, если умножать его по частям параллельно
- битов (например в 32-разрядных машинах сложение происходит в одно действие, параллельно обрабатывая все 4 байта 32-разрядного числа)
- инструкций (на одном ядре, в одном потоке процессор может выполнять инструкции параллельно, несмотря на то что код последовательный)
- данных (существуют архитектуры с параллельной обработкой данных (Single Instruction Multiple Data), способные выполнять одну инструкцию на большом наборе данных)
- задач (подразумевается наличие нескольких процессоров или ядер)
параллельного



 
							 
							 
							 
							 
							 
							 
							