Основные понятия потоков

Java задняя часть GitHub Операционная система

По плану с начала этой статьи мы начнем краткое изложение серии «concurrency», начиная с треда в этой статье, до пула тредов, до анализа исходного кода нескольких параллельных коллекций. знания — это препятствие, которое вы не можете обойти в своей карьере, а любой проект предполагает более или менее одновременную обработку данных.

Эту серию статей можно рассматривать только как краткое изложение и введение в базовые теоретические знания о параллелизме.Если вы хотите стать мастером параллелизма, вы должны уметь применять онлайн-сценарии посредством крупномасштабного параллельного доступа.Может быть, у меня будут соответствующие опыт в будущем Позвольте мне поделиться с вами.

Основные понятия потока процесса

Процесс и поток — это два очень основных и важных понятия в операционной системе.Процесс — это основная единица защиты и распределения ресурсов в операционной системе., операционная система выделяет ресурсы процессу в качестве базовой единицы.Поток — это компонент процесса, который представляет собой последовательный поток выполнения.

Модель потока процесса в системе выглядит следующим образом:

image

Процесс получает основное пространство памяти от операционной системы, и все потоки совместно используют адресное пространство памяти процесса. Конечно, у каждого потока также будет свой собственный диапазон адресов в частной памяти, к которому другие потоки не могут получить доступ.

Поскольку все потоки совместно используют адресное пространство памяти процесса, взаимодействие между потоками значительно упрощается, чего можно добиться путем совместного использования глобальных переменных уровня процесса.

В то же время до того, как была введена концепция многопоточности, между процессами происходил так называемый «параллелизм», каждое переключение контекста процесса приводило к срабатыванию системного алгоритма планирования и сохранению различной контекстной информации процессора, что было очень долго.. Параллелизм на уровне потоков не имеет шага системного планирования Процесс выделяет время использования ЦП и использует его для каждого потока внутри него.

В системе с разделением времени каждый поток в процессе имеет квант времени, в конце которого ЦП и контекст потока сохраняются в регистре, а ЦП передается для завершения переключения между потоками. Конечно, когда использование процессорного времени процесса заканчивается, все потоки должны быть заблокированы.

JAVA абстракция концепции потока

Класс Thread используется для абстрактного описания потока в JAVA API.Поток имеет несколько состояний:

  • НОВИНКА: Тема только что создана
  • RUNNABLE: поток находится в исполняемом состоянии.
  • ЗАБЛОКИРОВАНО, ОЖИДАНИЕ: Тема заблокирована, конкретная разница будет описана позже.
  • TERMINATED: выполнение потока завершается и прекращается

Готовность к выполнению указывается потоками, но не означает, что поток должен быть перепланирован системой из-за окончания временного отрезка. Заблокировано, ожидание временно заблокировано из-за определенных условий во время выполнения потока.После ожидания они вернутся в статус Runnable, чтобы повторно конкурировать с ЦП.

Кроме того, в классе Thread есть некоторые свойства, описывающие объект потока:

  • private long tid: порядковый номер потока
  • private volatile char name[]: имя потока
  • private int priority: приоритет потока
  • private boolean daemon = false: это поток демона
  • private Runnable target: метод, который поток должен выполнить

Среди них tid — это поле с автоинкрементом.Каждый раз, когда создается новый поток, этот идентификатор будет увеличиваться на единицу. Значение приоритета находится в диапазоне от 1 до 10. Чем больше значение, тем выше приоритет.Значение по умолчанию равно пяти.

Runnable - это интерфейс, который тезисывает поток выполнения нити и определяется следующим образом:

public interface Runnable {

    public abstract void run();
}

Переопределяя метод run, вы также указываете начальную точку для вашего потока для выполнения инструкций после получения ЦП. Обычно мы передаем этот параметр при создании экземпляра Thread.

Создать и запустить цепочку

Существует два основных способа создания потока: один — передать класс реализации Runnable, а другой — напрямую переопределить метод run класса Thread. Давайте рассмотрим подробно:

1. Пользовательская реализация Runnable

public class MyThread implements Runnable{
    @Override
    public void run(){
        System.out.println("hello world");
    }
}
public static void main(String[] args) {
    Thread thread = new Thread(new MyThread());
    thread.start();
    System.out.println("i am main Thread");
}

результат операции:

i am main Thread
hello world

На самом деле класс Thread также наследует интерфейс Runnable и предоставляет реализацию метода запуска по умолчанию:

@Override
public void run() {
    if (target != null) {
       target.run();
    }
}

Target Мы сказали, что это поле исполняемых типов, и конструктор Thread инициализирует это поле Target. Таким образом, когда поток запускается, вызываемый метод RUN будет методом RUN класса реализации, который мы реализуем.

Итак, естественно, будет второй способ творения.

2. Наследовать класс Thread

Поскольку метод run вызывается при запуске потока, мы также можем определить класс потока, переопределив метод run класса Thread.

public class MyThreadT extends Thread{
    @Override
    public void run(){
        System.out.println("hello world");
    }
}
Thread thread = new MyThreadT();
thread.start();

Эффект тот же.

Несколько часто используемых методов

Что касается работы с потоками, класс Thread также предоставляет нам некоторые методы, некоторые из которых используются чаще.

1. спать

public static native void sleep(long millis)

Это собственный метод, который блокирует текущий поток на указанное время в миллисекундах.

2. начать

public synchronized void start()

Многих этот метод может сбить с толку, почему я указываю работу потока переопределением метода run Runnable, а запускаю поток через метод start?

Это связано с тем, что запуск потока — это не просто запуск записи с помощью инструкции, операционной системе также необходимо разделить часть пространства общей памяти процесса в качестве частных ресурсов потока, создать программные счетчики, стеки и другие ресурсы, а также наконец, перейдите к методу Call the run.

3. Прерывание

public void interrupt()

Этот метод используется для прерывания текущего потока.Разумеется, разные состояния потока реагируют на прерывание по-разному, о чем мы поговорим позже.

4. Присоединяйтесь

public final synchronized void join(long millis)

Этот метод обычно вызывается в других потоках, указывая, что текущий поток должен заблокироваться в текущей позиции, ожидая выполнения всех инструкций целевого потока. Например:

Thread thread = new MyThreadT();
thread.start();

thread.join();

System.out.println("i am the main thread");

Обычно оператор print основной функции будет выполняться до выполнения метода run потока MyThreadT, а оператор соединения указывает, что основной поток должен быть заблокирован до завершения выполнения MyThreadT.

Некоторые проблемы, вызванные многопоточностью

Не будем говорить о преимуществах многопоточности, а теперь посмотрим на многопоточность, то есть какие проблемы с памятью будут при параллелизме.

1. Состояние гонки

Это тип проблемы, когда несколько потоков одновременно обращаются к одному и тому же объекту и изменяют его, конечное значение объекта часто лучше, чем ожидалось. Например:

image

Мы создаем 100 потоков, каждый поток спит в случайное время при запуске, затем добавляет один к счету и выполняет потоки в обычном порядке, и значение счетчика будет равно 100.

Но я вам скажу, сколько бы раз вы его не запускали, результат будет разный, вероятность равного 100 очень мала. Это параллелизм, и причина очень проста: операция count++ не может выполняться одной инструкцией.

Он разделен на три шага,Прочитайте значение count, увеличьте его на единицу и запишите обратно в переменную countсередина. Многопоточность не знает друг друга и все выполняют эти три шага, поэтому значение данных, считываемое в данный момент потоком, может быть не самым последним, и результат, естественно, не такой, как ожидалось.

Но это совпадение.

2, видимость памяти

Видимость памяти означает, что в некоторых случаях изменения потоком некоторых переменных ресурсов не сразу сбрасываются в память, а временно сохраняются в кэшах и регистрах.

Самая непосредственная проблема, вызванная этим, заключается в том, что изменения в общих переменных не видны другому потоку.

image

Этот код очень прост, основной поток и наш ThreadTwo имеют общий флаг глобальной переменной, последний отслеживает изменения значения этой переменной, и мы изменили значение этой переменной в основном потоке из-за проблем с видимостью памяти. , модификации основного потока в ThreadTwo не сразу отображаются в память, а временно сохраняются в кэшах или регистрах, что делает ThreadTwo неспособным узнать об изменении значения флага и продолжает цикл.

Подводя итог, можно сказать, что процесс — это основная единица распределения ресурсов системой, а поток — это часть процесса, разделяющая ресурсы в процессе, а поток — это наименьший поток выполнения, запланированный системой. В системе реального времени каждый поток получает квант времени для вызова ЦП, и несколько потоков одновременно используют ЦП Каждое переключение контекста соответствует сохранению и восстановлению «выполняемой сцены», что также требует относительно много времени. операция.

ps: Некоторое время назад я был очень занят, и это заняло много времени. Прошу прощения у всех здесь. Спасибо, что не ушли. Теперь оно официально возобновлено, и началась серия параллельных сводок ~


Весь код, изображения, файлы в статье хранятся в облаке на моем GitHub:

(Github.com/single Yam/ О...)

Добро пожаловать в официальную учетную запись WeChat: OneJavaCoder, все статьи будут синхронизированы в официальной учетной записи.

image