Глубокое понимание многопоточности Java

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

первая встреча

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

Многопоточность — это особая форма многозадачности, но многопоточность использует меньше ресурсов.

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

Многопоточность позволяет программистам писать эффективные программы для полного использования ЦП.

1. Введение в основные понятия многопоточности

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

анализировать:

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

Он содержит ресурсы (общую память, общие файлы) и потоки: мы говорим, что процесс является носителем ресурсов., который также является носителем нити. Ресурсы здесь можно понимать как память. Мы знаем, что программа должна считывать данные из памяти для запуска, поэтому каждому процессу будет выделена память при его выполнении.

3 Что такое нить?

这里写图片描述

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

В этом смысле мы говорим:

Поток — это наименьшая единица выполнения в системе; в одном процессе может быть несколько потоков; потоки совместно используют ресурсы процесса.

④ Как взаимодействуют потоки?

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

Режим взаимодействия: взаимоисключение, синхронизация

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

жизненный цикл нити

Нить проходит через различные этапы своего жизненного цикла. На рисунке ниже показан полный жизненный цикл потока.

这里写图片描述

  • Новое состояние:

использоватьnewключевые слова иThreadПосле того, как класс или его подклассы создают объект потока, объект потока находится в новом состоянии. он остается в этом состоянии до тех пор, пока программаstart()эта нить.

  • Состояние готовности:

Когда объект потока вызывает метод start(), поток переходит в состояние готовности. Поток в состоянии готовности находится в очереди готовности, ожидая планирования планировщика потоков в JVM.

  • Рабочий статус:

    Если поток в состоянии готовности получает ресурсы ЦП, он может выполнитьrun(), поток запущен. Работающий поток — самый сложный, он может стать заблокированным, готовым и мертвым.

  • Состояние блокировки:

Если поток выполняет такие методы, как sleep (сон), приостановить (suspend) и теряет занятые ресурсы, поток переходит из состояния выполнения в состояние блокировки. В состояние готовности можно снова войти после того, как время ожидания истекло или ресурсы устройства были получены.

  • статус смерти:

Поток в состоянии выполнения переключается в состояние завершения, когда он завершает задачу или когда возникают другие условия завершения.

Диаграмма перехода состояния потока

这里写图片描述

1. Новое состояние (New): объект потока создан заново.

2. Готовое состояние (Runnable): после создания объекта потока другие потоки вызывают метод start() объекта. Потоки в этом состоянии находятся в пуле исполняемых потоков и становятся работоспособными, ожидая получения права на использование ЦП.

3. Состояние выполнения (Running): Поток в состоянии готовности захватывает ЦП и выполняет программный код.

4. Заблокированное состояние (Blocked): заблокированное состояние означает, что поток по какой-то причине отказывается от права использовать ЦП и временно прекращает работу. Пока поток не перейдет в состояние готовности, нет возможности перейти в состояние выполнения. Существует три типа блокировки:

(1) Ожидание блокировки: работающий поток выполняет метод wait(), и JVM помещает поток в пул ожидания.

(2) Блокировка синхронизации: когда выполняющийся поток получает блокировку синхронизации объекта, если блокировка синхронизации занята другим потоком, JVM помещает поток в пул блокировок.

(3) Другая блокировка: когда работающий поток выполняет метод sleep() или join() или выдает запрос ввода-вывода, JVM переводит поток в заблокированное состояние. Когда время ожидания состояния sleep() истекает, join() ожидает, пока поток завершится или истечет время ожидания, или когда обработка ввода-вывода завершена, поток снова входит в состояние готовности.

5. Мертвое состояние (Dead): поток завершает выполнение или выходит из метода run() из-за исключения, и поток завершает свой жизненный цикл.

планирование потоков

1. Настройте приоритет потока:

Каждый поток Java имеет приоритет, который помогает операционной системе определять порядок планирования потоков.

Приоритет потока Java представлен целым числом от 1 до 10. Класс Thread имеет следующие три статические константы: статический интервал MAX_PRIORITY Максимальный приоритет, который может иметь поток, равен 10. статический интервал MIN_PRIORITY Самый низкий приоритет, который может иметь поток, равный 1. статический интервал NORM_PRIORITY Приоритет по умолчанию, назначенный потоку, принимает значение 5.

Методы setPriority() и getPriority() класса Thread используются для установки и получения приоритета потока соответственно. Каждый поток имеет приоритет по умолчанию. Приоритет основного потока по умолчанию — Thread.NORM_PRIORITY. Приоритет потоков наследуется, например, если поток B создан в потоке A, то B будет иметь тот же приоритет, что и A. JVM предоставляет 10 приоритетов потоков, но ни один из них не соответствует обычным операционным системам. Если вы хотите, чтобы программа была перенесена на различные операционные системы, вы должны использовать только класс Thread со следующими тремя статическими константами в качестве приоритета, чтобы можно было гарантировать один и тот же метод планирования для одного и того же приоритета.

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

2, поток в спящий режим: метод Thread.sleep (длинный миллис), поток в состояние блокировки. Параметр millis устанавливает время ожидания в миллисекундах. После окончания сна он перешел в состояние Ready (Runnable). сон () переносимость платформы это хорошо.

3, поток ожидает: метод ожидания () в классе объектов, в результате чего текущий поток ожидает, пока другие потоки не вызовут метод уведомления () этого объекта или метод пробуждения notifyAll (). Эти два метода пробуждения также являются методами класса Object, и их поведение эквивалентно вызову Wait (0).

4. Выход потока: метод Thread.yield() приостанавливает текущий исполняемый объект потока и предоставляет возможность выполнения потоку с таким же или более высоким приоритетом.

5. Присоединение к потоку: метод join(), ожидающий завершения других потоков. Если метод join() другого потока вызывается в текущем потоке, текущий поток перейдет в состояние блокировки до тех пор, пока другой процесс не завершит выполнение, и текущий поток перейдет из состояния блокировки в состояние готовности.

6. Пробуждение потока: метод notify() в классе Object пробуждает один поток, ожидающий этого монитора объекта. Если все потоки ожидают этого объекта, один из них будет выбран для пробуждения. Выбор произволен и происходит, когда принимается решение о реализации. Поток ожидает на мониторе объекта, вызывая один из методов ожидания. Выполнение пробужденного потока не может продолжаться до тех пор, пока текущий поток не снимет блокировку с этого объекта. Пробужденный поток будет конкурировать обычным образом со всеми другими потоками, активно синхронизирующими этот объект; например, у пробужденного потока нет надежной привилегии или недостатка в том, чтобы быть следующим потоком, который заблокирует этот объект. Подобный метод также имеет notifyAll(), который пробуждает все потоки, ожидающие на мониторе этого объекта.

Примечание. В JDK1.5 два метода suspend() и возобновить() в Thread были упразднены и больше не будут представлены. Из-за склонности к тупику.

7. Объяснение общих терминов резьбы Основной поток: поток, сгенерированный JVM, вызывающей программу main(). Текущая тема: Это запутанная концепция. Обычно относится к процессу, полученному с помощью Thread.currentThread(). Фоновый поток: относится к потоку, который предоставляет услуги другим потокам, также известный как поток демона. Поток сборки мусора JVM является фоновым потоком. Поток переднего плана: относится к потоку, который принимает услуги фонового потока.Фактически, передний план и фоновые потоки связаны вместе, точно так же, как отношения между марионеткой и манипулятором за кулисами. Марионетка — это поток переднего плана, а манипулятор — фоновый поток. Потоки, созданные потоками переднего плана, также являются потоками переднего плана по умолчанию. Является ли поток фоновым потоком, можно оценить и установить с помощью методов isDaemon() и setDaemon().

Некоторые часто задаваемые вопросы

1. Имя потока.У запущенного потока всегда есть имя.Имя берется из двух источников,одно имя дается самой виртуальной машиной,а другое имя дается вами. В случае, когда имя потока не указано, виртуальная машина всегда присваивает имя потоку, причем имя основного потока всегда является основным, а имя неосновного потока не определено.

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

3. Метод получения объекта текущего потока: Thread.currentThread();

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

5. Поток завершается, когда заканчивается метод run() целевого потока.

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

7. Планирование потоков является частью JVM На машине с ЦП одновременно может выполняться только один поток. Одновременно выполняется только один стек потоков. Планировщик потоков JVM решает, какой поток в рабочем состоянии фактически выполняется. Один из многих запущенных потоков будет выбран в качестве текущего потока. Порядок, в котором выбираются исполняемые потоки, не гарантируется.

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

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

2. Введение в общие методы потоков в Java

Поддержка языка Java для потоков

в основном отражается вКласс потокаа такжеРаботающий интерфейсВышеупомянутые наследуются от пакета java.lang. Все они имеют общий метод:public void run().

Метод   run дает нам код того, что поток действительно работает.

В следующей таблице перечислены некоторые важные методы класса Thread:

серийный номер Описание метода
1 public void start() заставляет поток начать выполнение; виртуальная машина Java вызывает метод run потока.
2 public void run() Если поток был создан с использованием автономного объекта Runnable run, вызывает метод run объекта Runnable; в противном случае метод ничего не делает и возвращает значение.
3 public final void setName(String name) Измените имя потока, чтобы оно совпадало с именем параметра.
4 public final void setPriority(int priority) изменяет приоритет потока.
5 public final void setDaemon(boolean on) помечает поток как поток демона или пользовательский поток.
6 public final void join(long миллисекунды) ожидает завершения потока до миллисекунд.
7 public void interrupt() прерывает поток.
8 public final boolean isAlive() Проверяет, жив ли поток.

Проверьте, жив ли поток. Вышеупомянутый метод вызывается объектом Thread. Следующий метод является статическим методом класса Thread.

серийный номер Описание метода
1 public static void yield() приостанавливает выполнение текущего объекта потока и выполняет другие потоки.
2 public static void sleep(long миллисекунды) Засыпает (приостанавливает выполнение) выполняющийся в данный момент поток на указанное количество миллисекунд, в зависимости от точности системных таймеров и планировщиков.
3 public static boolean HoldLock(Object x) Возвращает значение true тогда и только тогда, когда текущий поток удерживает блокировку монитора на указанном объекте.
4 public static Thread currentThread() возвращает ссылку на исполняемый в данный момент объект потока.
5 public static void dumpStack() выводит трассировку стека текущего потока в стандартный поток ошибок.

Общие методы Thread

这里写图片描述

3. Первый опыт работы с потоком (пример кода)

Существует два способа создания потока:

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

2. Реализуйте интерфейс Runnable

Методы в потоке более характерны, такие как: start (старт), sleep (сон), stop и т. д., несколько потоков выполняются интерактивно (процессор в определенное время. Только один поток может выполняться, когда поток спит или выполняется После завершения другой поток может занять процессор для выполнения) Поскольку это определяется структурой процессора, в определенное время процессор может выполнять только один поток, но скорость довольно высокая, и это можно считать как параллельное выполнение для людей.

В java-файле может быть несколько классов (здесь упоминаются внешние классы), но может быть только один публичный класс.

Между этими двумя методами создания потоков нет никакой разницы: один заключается в реализации интерфейса Runnable, а другой — в наследовании класса Thread.

Используйте этот метод, реализующий интерфейс Runnable:

  1.Это может избежать ограничений, связанных с функцией одиночного наследования в java;

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

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

грамматика:Thread actress=new Thread(Runnable target ,String name);

Например:

Thread actressThread=new Thread(new Actress(),"Ms.runnable");
actressThread.start();

Пример кода:

package com.study.thread;

public class Actor extends Thread{
    public void run() {
        System.out.println(getName() + "是一个演员!");
        int count = 0;
        boolean keepRunning = true;

        while(keepRunning){
            System.out.println(getName()+"登台演出:"+ (++count));
            if(count == 100){
                keepRunning = false;
            }
            if(count%10== 0){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        System.out.println(getName() + "的演出结束了!");
    }

    public static void main(String[] args) {
       Thread actor = new Actor();//向上转型:子类转型为父类,子类对象就会遗失和父类不同的方法。向上转型符合Java提倡的面向抽象编程思想,还可以减轻编程工作量
       actor.setName("Mr. Thread");
       actor.start();
       
       //调用Thread的构造函数Thread(Runnable target, String name)
       Thread actressThread = new Thread(new Actress(), "Ms. Runnable");
       actressThread.start();
    }

}
//注意:在“xx.java”文件中可以有多个类,但是只能有一个Public类。这里所说的不是内部类,都是一个个独立的外部类
class Actress implements Runnable{

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "是一个演员!");//Runnable没有getName()方法,需要通过线程的currentThread()方法获得线程名称
        int count = 0;
        boolean keepRunning = true;

        while(keepRunning){
            System.out.println(Thread.currentThread().getName()+"登台演出:"+ (++count));
            if(count == 100){
                keepRunning = false;
            }
            if(count%10== 0){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        System.out.println(Thread.currentThread().getName() + "的演出结束了!");
    }
    
}

/**
 *运行结果Mr. Thread线程和Ms. Runnable线程是交替执行的情况
 *分析:计算机CPU处理器在同一时间同一个处理器同一个核只能运行一条线程,
 *当一条线程休眠之后,另外一个线程才获得处理器时间
 */

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

这里写图片描述
Пример 2:

Класс ArmyRunnable:

package com.study.threadTest1;

/**
 * 军队线程
 * 模拟作战双方的行为
 */
public class ArmyRunnable implements Runnable {

    /* volatile关键字
     * volatile保证了线程可以正确的读取其他线程写入的值
     * 如果不写成volatile,由于可见性的问题,当前线程有可能不能读到这个值
     * 关于可见性的问题可以参考JMM(Java内存模型),里面讲述了:happens-before原则、可见性
     * 用volatile修饰的变量,线程在每次使用变量的时候,都会读取变量修改后的值
     */
    volatile boolean keepRunning = true;

    @Override
    public void run() {
        while (keepRunning) {
            //发动5连击
            for(int i=0;i<5;i++){
                System.out.println(Thread.currentThread().getName()+"进攻对方["+i+"]");
                //让出了处理器时间,下次该谁进攻还不一定呢!
                Thread.yield();//yield()当前运行线程释放处理器资源
            } 
        }
        System.out.println(Thread.currentThread().getName()+"结束了战斗!");
    }

}

Класс KeyPersonThread:

package com.study.threadTest1;


public class KeyPersonThread extends Thread {
    public void run(){
        System.out.println(Thread.currentThread().getName()+"开始了战斗!");
        for(int i=0;i<10;i++){
            System.out.println(Thread.currentThread().getName()+"左突右杀,攻击隋军...");
        }
        System.out.println(Thread.currentThread().getName()+"结束了战斗!");
    }

}

Сценический класс:

package com.study.threadTest1;

/**
 * 隋唐演义大戏舞台 6  */
public class Stage extends Thread {
    public void run(){
        System.out.println("欢迎观看隋唐演义");
        //让观众们安静片刻,等待大戏上演
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e1) {
            e1.printStackTrace();
        }
        System.out.println("大幕徐徐拉开");

        try {
            Thread.sleep(5000);
        } catch (InterruptedException e1) {
            e1.printStackTrace();
        }

        System.out.println("话说隋朝末年,隋军与农民起义军杀得昏天黑地...");
        ArmyRunnable armyTaskOfSuiDynasty = new ArmyRunnable();
        ArmyRunnable armyTaskOfRevolt = new ArmyRunnable();

        //使用Runnable接口创建线程
        Thread  armyOfSuiDynasty = new Thread(armyTaskOfSuiDynasty,"隋军");
        Thread  armyOfRevolt = new Thread(armyTaskOfRevolt,"农民起义军");

        //启动线程,让军队开始作战
        armyOfSuiDynasty.start();
        armyOfRevolt.start();

        //舞台线程休眠,大家专心观看军队厮杀
        try {
            Thread.sleep(50);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("正当双方激战正酣,半路杀出了个程咬金");

        Thread  mrCheng = new KeyPersonThread();
        mrCheng.setName("程咬金");
        System.out.println("程咬金的理想就是结束战争,使百姓安居乐业!");

        //停止军队作战
        //停止线程的方法
        armyTaskOfSuiDynasty.keepRunning = false;
        armyTaskOfRevolt.keepRunning = false;

        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        /*
         * 历史大戏留给关键人物
         */
        mrCheng.start();

        //万众瞩目,所有线程等待程先生完成历史使命
        try {
            mrCheng.join();//join()使其他线程等待当前线程终止
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("战争结束,人民安居乐业,程先生实现了积极的人生梦想,为人民作出了贡献!");
        System.out.println("谢谢观看隋唐演义,再见!");
    }

    public static void main(String[] args) {
        new Stage().start();
    }

}

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

这里写图片描述

4. Правильная остановка потоков Java

Как правильно остановить поток в Java?

метод остановки: этот метод резко останавливает поток (резко останавливается), неясно, какая работа была сделана, а какая не была сделана, и никакая работа по очистке не была сделана.

Метод остановки не является правильным способом остановки потока. Останов потока Метод остановки устарел.

Правильный путь --- установить выходной флаг

использоватьvolatileопределениеboolean running=true, Завершите поток, установив переменную флага running.

Как в этой статье:volatile boolean keepRunning=true;

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

这里写图片描述

Широко распространенный метод ошибок --- метод прерывания

这里写图片描述
Когда поток выполняется, другой поток может вызвать метод interrupt() соответствующего объекта Thread, чтобы прервать его, который просто устанавливает флаг в целевом потоке, что он был прерван, и немедленно возвращается. Здесь следует отметить, что если просто вызывается метод interrupt(), поток фактически не прерывается и продолжает выполняться.

Пример кода:

package com.study.threadStop;

/**
 * 错误终止进程的方式——interrupt
 */
public class WrongWayStopThread extends Thread {

    public static void main(String[] args) {
        WrongWayStopThread thread = new WrongWayStopThread();
        System.out.println("Start Thread...");
        thread.start();
        
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        System.out.println("Interrupting thread...");
        thread.interrupt();
        
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Stopping application...");
    }
    
    public void run() {
        while(true){
            System.out.println("Thread is running...");
            long time = System.currentTimeMillis();
            while ((System.currentTimeMillis()-time) <1000) {//这部分的作用大致相当于Thread.sleep(1000),注意此处为什么没有使用休眠的方法
                //减少屏幕输出的空循环(使得每秒钟只输出一行信息)
            }
        }
    }
}

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

这里写图片描述
Из результатов видно, что метод interrupt() не прерывает поток, и поток будет продолжать выполняться.

Описано в Java API:

这里写图片描述
Но метод interrupt() может изменить статус нашего прерывания, вы можете вызватьisInterruptedметод
这里写图片描述
Если изменить код метода RUN на такой же, программу можно завершить обычным образом.

public void run() {
        while(!this.isInterrupted()){//interrupt()可以使中断状态放生改变,调用isInterrupted()
            System.out.println("Thread is running...");
            long time = System.currentTimeMillis();
            while ((System.currentTimeMillis()-time) <1000) {//这部分的作用大致相当于Thread.sleep(1000),注意此处为什么没有使用休眠的方法
                //减少屏幕输出的空循环(使得每秒钟只输出一行信息)
            }
        }
    }

Однако используемый метод выхода по сути является методом использования флага выхода, но флаг выхода, используемый здесь, представляет собой специальный логотип «Не прерывается ли поток».

这里写图片描述
Эта часть кода эквивалентна коду, в котором поток засыпает на 1 секунду. Но почему Thread.sleep(1000) не используется. При использовании этого метода будет
这里写图片描述
Поток не завершился нормально, и после вызова метода прерывания возникло исключение. Почему такой результат?

В документации API сказано: если поток переходит в состояние блокировки из-за вызываемых методов (таких как sleep, join...), если поток снова вызывается методом прерывания, он выдает два результата: во-первых, его состояние прерывания очищается, а не устанавливается. Тогда isInterrupted не может вернуть правильное состояние прерывания, а функция while не может корректно завершиться. Во-вторых, метод сна получит InterruptedException и будет прерван.

Метод прерывания() может только установить флаг прерывания (а в случае блокировки потока флаг будет сброшен, и флаг прерывания не может быть установлен), и поток не может быть остановлен.

5. Взаимодействие потоков

Состояние гонки:

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

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

3. Состояние гонки: поток 1 заблокирован, пока поток 1 [не переназначает значение обратно], поток 2 начинает обращаться к данным, а затем изменяет их, значение перезаписывает значение, измененное потоком 2, и происходит потеря данных.

Взаимное исключение и синхронизация: экономия энергии

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

2. Из-за вышеперечисленных характеристик потоков будет существовать несколько потоков, конкурирующих за ресурсы, и возникнет явление условий гонки.

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

4. Взаимное исключение потоков (реализация блокировки):Работа потоков изолирована и не влияет друг на друга.synchronizedКлючевые слова реализуют взаимоисключающее поведение, это ключевое слово может появляться в теле метода или в теле метода в форме блока, в этом блоке кода есть действия ожидания и пробуждения потока для поддержки управления синхронизацией потоков.

5. Синхронизация потоков (ожидание и пробуждение потока: wait()+notifyAll()):Запуск потоков контролируется взаимным общением, после запуска одного правильно запустите другой.

6. Концепция блокировки: например, закрытый конечный объект lockObj=new Object();

7. Реализация взаимного исключения: синхронизированное ключевое слово

synchronized(lockObj){---выполнить код----} операция блокировки

lockObj.wait(); Поток входит в состояние ожидания, чтобы избежать продолжения подачи заявок на блокировку без конкуренции за ресурсы процессора.

lockObj.notifyAll(); пробуждает все потоки, ожидающие объектов lockObj

8. Операция блокировки потребует системных ресурсов и снизит эффективность

Возник вопрос синхронизации

Синхронизация потоков предназначена для предотвращения повреждения данных при доступе нескольких потоков к объекту данных. Например: два потока ThreadA и ThreadB работают с одним и тем же объектом Foo и изменяют данные в объекте Foo.

public class Foo { 
    private int x = 100; 

    public int getX() { 
        return x; 
    } 

    public int fix(int y) { 
        x = x - y; 
        return x; 
    } 
}
public class MyRunnable implements Runnable { 
    private Foo foo = new Foo(); 

    public static void main(String[] args) { 
        MyRunnable r = new MyRunnable(); 
        Thread ta = new Thread(r, "Thread-A"); 
        Thread tb = new Thread(r, "Thread-B"); 
        ta.start(); 
        tb.start(); 
    } 

    public void run() { 
        for (int i = 0; i < 3; i++) { 
            this.fix(30); 
            try { 
                Thread.sleep(1); 
            } catch (InterruptedException e) { 
                e.printStackTrace(); 
            } 
            System.out.println(Thread.currentThread().getName() + " : 当前foo对象的x值= " + foo.getX()); 
        } 
    } 

    public int fix(int y) { 
        return foo.fix(y); 
    } 
}

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

Thread-A : 当前foo对象的x值= 40 
Thread-B : 当前foo对象的x值= 40 
Thread-B : 当前foo对象的x值= -20 
Thread-A : 当前foo对象的x值= -50 
Thread-A : 当前foo对象的x值= -80 
Thread-B : 当前foo对象的x值= -80 

Process finished with exit code 0

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

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

В конкретном коде Java необходимо выполнить следующие две операции: Пометить переменную x класса ресурсов Foo, которая конкурирует за доступ, как частную; Чтобы синхронизировать код, который изменяет переменные, используйте ключевое слово synchronized для синхронизации методов или кода.

синхронизировать и заблокировать

1. Принцип блокировки

Каждый объект в Java имеет встроенную блокировку. Когда программа запускается для нестатического синхронизированного синхронизированного метода, автоматически устанавливается блокировка, связанная с текущим экземпляром (этим экземпляром) исполняемого класса кода. Получение блокировки объекта также называется получением блокировки, блокировкой объекта, блокировкой объекта или синхронизацией объекта. Блокировка объекта работает только тогда, когда программа работает с синхронизированным методом синхронизации или кодовым блоком. Объект имеет только один замок. Таким образом, если один поток получает блокировку, ни один другой поток не может получить блокировку до тех пор, пока первый поток не освободит (или не вернет) блокировку. Это также означает, что никакой другой поток не может ввести синхронизированный метод или блок кода для объекта, пока блокировка не будет снята. Снятие блокировки означает, что поток, удерживающий блокировку, вышел из синхронизированного метода или блока кода.

Есть несколько ключевых моментов, касающихся блокировки и синхронизации:

1) синхронизировать можно только методы, а не переменные и классы;

2), блокировка только одна на объект, когда речь идет о синхронизации, должно быть понятно, о какой синхронизации? То есть на каком объекте синхронизируется?

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

4) Если два потока хотят выполнить синхронизированный метод в классе, и два потока используют один и тот же экземпляр для вызова метода, то только один поток может одновременно выполнять метод, а другой должен ждать, пока не будет снята блокировка. выпущенный. То есть: если поток получает блокировку объекта, никакой другой поток не может ввести какой-либо синхронизированный метод в классе (объекта).

5) Если поток имеет синхронизированные и несинхронизированные методы, несинхронизированный метод может быть свободно доступен для нескольких потоков без ограничений блокировками.

6) Когда поток спит, любые блокировки, которые он удерживает, не будут сняты.

7) Поток может получить несколько блокировок. Например, вызов метода синхронизации другого объекта в методе синхронизации одного объекта получает блокировку синхронизации двух объектов.

8) Синхронизация повреждает параллелизм, и область синхронизации должна быть максимально уменьшена. Синхронизация может синхронизировать не только весь метод, но и часть блока кода в методе.

9) При использовании блока синхронизированного кода следует указать, на каком объекте выполнять синхронизацию, т. е. какую блокировку объекта необходимо получить. Например:

public int fix(int y) {
        synchronized (this) {
            x = x - y;
        }
        return x;
    }

Конечно, синхронизированные методы тоже можно переписать как несинхронизированные, но функции точно такие же, например:

 public synchronized int getX() {
        return x++;
    }

а также

 public int getX() {
        synchronized (this) {
            return x;
        }
    }

Эффект точно такой же.

синхронизация статических методов

Для синхронизации статических методов требуется блокировка всего объекта класса, которым является этот класс (XXX.class). Например:

public static synchronized int setName(String name){
      Xxx.name = name;
}

Эквивалентно

public static int setName(String name){
      synchronized(Xxx.class){
            Xxx.name = name;
      }
}

Сводка синхронизации потоков

1. Целью синхронизации потоков является защита от уничтожения ресурсов при доступе к ресурсу нескольких потоков.

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

3. Для статических методов синхронизации блокировка предназначена для этого класса, а объект блокировки — это объект класса класса. Блокировки для статических и нестатических методов не мешают друг другу. Поток получает блокировку, и при доступе к синхронизированному методу другого объекта в синхронизированном методе он получает блокировки обоих объектов.

4. Для синхронизации важно быть в курсе того, какой объект синхронизировать.

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

6. Когда несколько потоков ожидают блокировки объекта, блокируется поток, не получивший блокировку.

7. Взаимная блокировка возникает из-за того, что потоки ожидают блокировку друг друга, и вероятность ее возникновения на практике очень мала. Действительно позволяют написать программу взаимоблокировки, не обязательно проста в использовании, да, да. Однако, как только программа заблокируется, программа умрет.

Глубокое погружение в мьютекс и синхронизацию

Реализация взаимного исключения (блокировки): synchronized(lockObj); Гарантировано, что только один поток получает lockObj одновременно.

Синхронная реализация: wait()/notify()/notifyAll()

Уведомление:Методы wait(), notify(), notifyAll() принадлежат объектам Object, а не объектам Thread.

  • аннулировать уведомление () Пробуждает один поток, ожидающий на мониторе этого объекта.
  • недействительным уведомить все () Пробуждает все потоки, ожидающие на мониторе этого объекта.
  • пустое ожидание () Заставляет текущий поток ожидать, пока другой поток не вызовет метод notify() или метод notifyAll() этого объекта.

Конечно, у wait() есть еще два перегруженных метода:

  • пустое ожидание (длительный тайм-аут) Заставляет текущий поток ожидать, пока другой поток не вызовет метод notify() или метод notifyAll() этого объекта, или пока не пройдет указанное количество времени.
  • пустое ожидание (длительный тайм-аут, int nanos) Заставляет текущий поток ожидать, пока другой поток не вызовет метод notify() или метод notifyAll() этого объекта, или пока какой-либо другой поток не прервет текущий поток, или пока не пройдет некоторое реальное количество времени.

notify() пробуждает один поток из набора ожидания, а notifyall() пробуждает все потоки.

Синхронизация — это интерактивная операция между двумя потоками (один поток отправляет сообщение, а другой отвечает).Ключевые моменты, которые следует помнить об ожидании/уведомлении: Методы wait(), notify(), notifyAll() должны вызываться из синхронной среды. Поток не может вызывать методы ожидания или уведомления для объекта, если он не владеет блокировкой этого объекта. wait(), notify(), notifyAll() — все это методы экземпляра Object. Поскольку у каждого объекта есть блокировка, у каждого объекта может быть список потоков, ожидающих сигнала (уведомления) от него. Поток получает этот список ожидания, выполняя метод wait() для объекта. С этого момента он не выполняет никаких других инструкций, пока не будет вызван метод notify() объекта. Если несколько потоков ожидают обработки одного и того же объекта, для продолжения выполнения будет выбран только один поток (порядок которого не гарантируется). Если нет ожидающих потоков, никаких специальных действий не предпринимается. Давайте посмотрим на пример, чтобы понять:

/** 
* 计算输出其他线程锁计算的数据 
*/ 
public class ThreadA { 
    public static void main(String[] args) { 
        ThreadB b = new ThreadB(); 
        //启动计算线程 
        b.start(); 
        //线程A拥有b对象上的锁。线程为了调用wait()或notify()方法,该线程必须是那个对象锁的拥有者 
        synchronized (b) { 
            try { 
                System.out.println("等待对象b完成计算。。。"); 
                //当前线程A等待 
                b.wait(); 
            } catch (InterruptedException e) { 
                e.printStackTrace(); 
            } 
            System.out.println("b对象计算的总和是:" + b.total); 
        } 
    } 
}
/** 
* 计算1+2+3 ... +100的和 
*/ 
public class ThreadB extends Thread { 
    int total; 

    public void run() { 
        synchronized (this) { 
            for (int i = 0; i < 101; i++) { 
                total += i; 
            } 
            //(完成计算了)唤醒在此对象监视器上等待的单个线程,在本例中线程A被唤醒 
            notify(); 
        } 
    } 
}

результат: Подождите, пока объект b закончит вычисления. . . Сумма, рассчитанная объектом b: 5050 Процесс завершен с кодом выхода 0

Обратите внимание на: Когда метод wait() вызывается для объекта, поток, выполняющий код, немедленно снимает свою блокировку с объекта. Однако, когда вызывается notify(), это не означает, что поток снимет свою блокировку. Если поток успешно завершает код синхронизации, поток не снимает блокировку до тех пор, пока она не будет снята. Поэтому простой вызов notify() не означает, что блокировка становится доступной.

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

Как понять синхронизацию: установка ожидания

Набор ожидания критической секции

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

Ps: Если вызывается notify(); то случайным образом вынимается (это внутренний алгоритм, не надо разбираться) ожидающий ресурс готовится к входу в Критический раздел; если вызывается notifyAll(); вынимаются все и готов войти в критическую секцию.

6. Резюме и перспективы

这里写图片描述
这里写图片描述
Совет по расширению: как расширить свои знания о параллельной обработке Java

1. Режим памяти Java: JMM описывает, как потоки Java взаимодействуют через память, понимают, что происходит — до, синхронизировано, изменчиво и окончательно.

2. Locks % Condition: Высокоуровневая реализация механизма блокировки Java и условий ожидания java.util, concurrent.locks

3. Безопасность потоков: атомарность и видимость, java.util.concurrent.atomic synchronized (блокировка метода блокировки) и volatile (определение общедоступных ресурсов) DeadLocks (взаимная блокировка) — понимание того, что такое взаимоблокировка и условия ее возникновения

4. Модели взаимодействия, обычно используемые в многопоточном программировании.

Модель производитель-потребитель (модель производитель-потребитель)

· Модель блокировки чтения-записи (модель блокировки чтения-записи)

· Будущая модель

· Модель рабочего потока

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

5. Инструменты параллельного программирования в Java5: в пакете java.util.concurrent

Например: пул потоков ExecutorService, Callable&Future, BlockingQueue.

6, рекомендуемые книги: CoreJava, JavaConcurrency In Practice.

  • Источник: http://www.cnblogs.com/Qian123/p/5670304.html.

В статье есть что-то неуместное, пожалуйста, поправьте меня, вы также можете обратить внимание на мой паблик WeChat:好好学java, доступ к высококачественным учебным ресурсам.