Что такое многопоточность Java?

Java

Этап 5 Многопоточность

Предисловие:

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

(1) Обзор потоков и процессов

(1) Процесс

  • обработать: Процесс — это независимая единица системы для выделения и вызова ресурсов. Каждый процесс имеет собственное пространство памяти и системные ресурсы.
  • Многопоточность: несколько задач могут выполняться в один и тот же период времени, что улучшает использование ЦП.

(2) Резьба

  • нить: исполнительная единица процесса, путь выполнения

  • один поток: Приложение имеет только один путь выполнения.

  • Многопоточность: Приложение имеет несколько путей выполнения.

  • Что означает многопроцессорность?- Улучшить использование процессора

  • Что означает многопоточность?- Улучшить использование приложения

(3) Дополнение

Параллелизм и параллелизм

  • параллельноЯвляется физически одновременным, относится к одновременному запуску нескольких программ в определенный момент времени.
  • параллелизмЭто логическое одновременное событие, которое относится к одновременному запуску нескольких программ в течение определенного периода времени.

Является ли принцип работы Java-программы и запуск JVM многопоточным?

  • Как работает Java-программа:
    • Запустите JVM с помощью команды java, и запуск JVM эквивалентен запуску процесса
    • Затем процесс создает основной поток для вызова основного метода.
  • Является ли запуск виртуальной машины JVM однопоточным или многопоточным?
    • Поток сборки мусора также должен быть запущен первым, иначе легко произойдет переполнение памяти.
    • Текущий поток сборки мусора плюс предыдущий основной поток, запущено как минимум два потока, поэтому запуск jvm фактически является многопоточным
    • Запуск JVM запускает как минимум поток сборки мусора и основной поток, поэтому он многопоточный

(2) Реализация многопоточного кода

Требования: Мы хотим реализовать многопоточную программу.

Как этого добиться?

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

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

Java этоНевозможно напрямую вызвать системные функциида, так что мыНевозможно напрямую реализовать многопоточностьпрограмма.

Но что?Java может вызывать программы, написанные на C/C++, для реализации многопоточных программ.

C/C++ вызывает системные функции для создания процессов, а затем Java вызывает такие вещи,

Затем предоставьте нам несколько классов для использования. Мы можем реализовать многопоточные программы.

Глядя на API, мы знаем, что есть2 видаспособ реализации многопоточных программ.

Способ 1: наследовать класс Thread

шаг:

  • Пользовательский MyThread (имя пользовательского класса) наследует класс Thread.

  • Запустить () в классе Mythread

  • создать объект

  • начать нить

public class MyThread extends Thread{
    public MyThread() {
    }
    
    @Override
    public void run() {
        for (int i = 0; i < 100; i++){
            System.out.println(getName() + ":" + i);
        }
    }
}
public class MyThreadTest {
    public static void main(String[] args) {
        //创建线程对象
        MyThread my = new MyThread();
        //启动线程,run()相当于普通方法的调用,单线程效果
        //my.run();
        //首先启动了线程,然后再由jvm调用该线程的run()方法,多线程效果
        my.start();

        //两个线程演示,多线程效果需要创建多个对象而不是一个对象多次调用start()方法
        MyThread my1 = new MyThread();
        MyThread my2 = new MyThread();

        my1.start();
        my2.start();
    }
}

//运行结果
Thread-1:0
Thread-1:1
Thread-1:2
Thread-0:0
Thread-1:3
Thread-0:1
Thread-0:2
......
Thread-0:95
Thread-0:96
Thread-0:97
Thread-0:98
Thread-0:99

Способ 2: реализовать интерфейс Runnable (рекомендуется)

шаг:

  • Пользовательский класс MyuRunnable реализует интерфейс Runnable.
  • Переопределить метод run()
  • Создайте объект класса MyRunable
  • Класс Thread для создания объекта и объект C в качестве шага прохождения параметра конфигурации
public class MyRunnable implements Runnable {
    public MyRunnable() {
    }
    
    @Override
    public void run() {
        for (int i = 0; i < 100; i++){
            //由于实现接口的方式不能直接使用Thread类的方法了,但是可以间接的使用
            System.out.println(Thread.currentThread().getName() + ":" + i);
        }
    }
}
public class MyRunnableTest {
    public static void main(String[] args) {
        //创建MyRunnable类的对象
        MyRunnable my = new MyRunnable();

        //创建Thread类的对象,并把C步骤的对象作为构造参数传递
//        Thread t1 = new Thread(my);
//        Thread t2 = new Thread(my);
        //下面具体讲解如何设置线程对象名称
//        t1.setName("User1");
//        t1.setName("User2");

        Thread t1 = new Thread(my,"User1");
        Thread t2 = new Thread(my,"User2");

        t1.start()
        t2.start();
    }
}

Преимущества реализации интерфейса

Можно избежать ограничений из-за одиночного наследования Java

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

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

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

(Интерфейс может реализовывать несколько реализаций, но наследование расширений может быть только одиночным наследованием), а его родительский класс не хочет наследовать Thread, потому что ему не нужно реализовывать многопоточность.

(3) Получить и установить объект потока

//获取线程的名称
public final String getName()

//设置线程的名称
public final void setName(String name)

Установите имя потока (если имя не задано, по умолчанию используется Thread-? (число))

Способ 1: без построения параметров + setXxx (рекомендуется)

//创建MyRunnable类的对象
MyRunnable my = new MyRunnable();

//创建Thread类的对象,并把C步骤的对象作为构造参数传递
Thread t1 = new Thread(my);
Thread t2 = new Thread(my);
t1.setName("User1");
t1.setName("User2");
		
//与上面代码等价
Thread t1 = new Thread(my,"User1");
Thread t2 = new Thread(my,"User2");

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

//MyThread类中

public MyThread(String name){
    super(name);//直接调用父类的就好
}

//MyThreadTest类中
MyThread my = new MyThread("admin");

получить имя потока

Примечание. Переопределите способ получения имени потока в методе запуска.

//Thread
getName()

//Runnable
//由于实现接口的方式不能直接使用Thread类的方法了,但是可以间接的使用
Thread.currentThread().getName()

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

//这种情况Thread类提供了一个方法:
//public static Thread currentThread():

//返回当前正在执行的线程对象,返回值是Thread,而Thread恰巧可以调用getName()方法
System.out.println(Thread.currentThread().getName());

(4) Планирование потоков, получение и установка приоритета потоков.

Если наш компьютер имеет только один ЦП, то ЦП может выполнить только одну инструкцию в определенное время, а поток может выполнить инструкцию только после получения кванта времени ЦП, то есть права на использование. Так как же Java вызывает потоки?

Существует две модели планирования потоков:

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

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

Java использует упреждающую модель планирования

//演示如何设置和获取线程优先级

//返回线程对象的优先级
public final int getPriority()

//更改线程的优先级
public final void setPriority(int newPriority)

Приоритет потоков по умолчанию равен 5.

Диапазон приоритетов потоков: 1-10.

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

(5) Контроль потока

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

//线程休眠
public static void sleep(long millis)

//线程加入(等待该线程终止,主线程结束后,其余线程开始抢占资源)
public final void join()

//线程礼让(暂停当前正在执行的线程对象,并且执行其他线程让多个线程的执行更加和谐,但是不能保证一人一次)
public static void yield()

//后台线程(某线程结束后,其他线程也结束)
public final void setDaemon(boolean on)

//(过时了但还可以用)
public final void stop()

//中断线程
public void interrupt()

(6) Жизненный цикл потока

новый- создать объект потока

готов—— Объект потока запущен, но не получено право выполнения ЦП

бегать—— Получено право выполнения ЦП

  • блокировать- нет полномочий ЦП, вернуться к готовности

умереть- Когда код завершает работу, поток умирает

(7) Случай многопоточной выдачи билетов в кино

public class SellTickets implements Runnable {
    private int tickets = 100;

    @Override
    public void run() {
        while (true){
            if (tickets > 0){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() 
                                   + "正在出售第" + (tickets--) + "张票");
            }
        }
    }
}
public class SellTicketsTest {
    public static void main(String[] args) {
        //创建资源对象
        SellTickets st = new SellTickets();

        //创建线程对象
        Thread t1 = new Thread(st, "窗口1");
        Thread t2 = new Thread(st, "窗口2");
        Thread t3 = new Thread(st, "窗口3");

        //启动线程
        t1.start();
        t2.start();
        t3.start();
    }
}

Добавьте метод sleep в класс SellTicket, чтобы задержать поток и замедлить выполнение.

После добавления задержки возникают две проблемы:

О: Один и тот же билет был продан несколько раз.

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

B: есть отрицательный голос

Вызвано случайностью и задержкой (три потока одновременно втиснуты в цикл, операция вычитания билетов-- может выполняться несколько раз в одном и том же цикле, и возникает ситуация выхода за границы. Например, билеты больше чем 0, но за пределами поля. -1)

Другими словами, поток 2 может выполняться, пока выполняется поток 1, а не поток 2, который не может выполняться, когда выполняется поток 1.

Сначала нам нужно знать, какие проблемы могут вызвать проблемы:

И по этим причинам мы будемКритерии определения наличия в программе проблем с безопасностью потоков

A: Это многопоточная среда?

B: Есть ли какие-либо общие данные

C: Есть ли несколько операторов, которые работают с общими данными?

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

Так как же решить эту проблему?

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

Java дает нам:механизм синхронизации

//同步代码块:

synchronized(对象){
	需要同步的代码;
}

Преимущества синхронизации

Появление синхронизации решает проблему безопасности многопоточности

Недостатки синхронизации

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

Обзор:

A: Кто является объектом блокировки блока синхронизированного кода?

любой объект

B: Формат метода синхронизации и проблема блокировки объекта?

Добавьте ключевое слово синхронизации в метод

Кто является объектом блокировки метода синхронизации?

this

C: Статический метод и проблема с блокировкой объекта?

Кто является объектом блокировки статического метода?

Объект файла байт-кода класса.

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

public class SellTickets implements Runnable {
    private int tickets = 100;

    //创建锁对象
    //把这个关键的锁对象定义到run()方法(独立于线程之外),造成同一把锁
    private Object obj = new Object();

    @Override
    public void run() {
        while (true) {
            synchronized (obj) {
                if (tickets > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() 
                                       + "正在出售第" + (tickets--) + "张票");
                }
            }
        }
    }
}

(8) Обзор и использование блокировки

Чтобы более четко указать, как блокировать и снимать блокировки, после JDK5 предоставляется новый объект блокировки Lock.

(Вы можете видеть более четко, где блокировка добавляется и где блокировка снимается,)

void lock() 加锁

void unlock() 释放锁
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class SellTickets2 implements Runnable {

    private int tickets = 100;

    private Lock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            try {
                lock.lock();
                ;
                if (tickets > 0) {
                    try {
                        Thread.sleep(150);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票");
                }
            } finally {
                lock.unlock();
            }
        }
    }
}

(9) Проблема взаимоблокировки (простое понимание)

Недостатки синхронизации

низкая эффективность

Если вы синхронизируете вложенные там, это склонно к взаимоблокировке

тупиковая проблема

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

(10) Ожидание механизма пробуждения

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

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

Объясним это простыми словами

Java использует упреждающую модель планирования

  • О: Если потребитель сначала захватит право выполнения ЦП, он будет потреблять данные, но текущие данные являются значением по умолчанию.Если это бессмысленно, он должен дождаться, пока данные станут значимыми, прежде чем потреблять их. Это похоже на покупателя, который пришел в магазин рано, но еще не сделал его, и может только ждать, пока его сделают раньше, прежде чем потреблять.
  • B: Если первый производитель возьмет на себя выполнение ЦП, он вернется к производственным данным, но когда он генерирует данные, но при этом сохраняет исполнительные полномочия, он может продолжать генерировать данные, это неразумно, вы должны подождите, пока потребители будут потреблять данные, а затем производство. Это, в свою очередь, похоже на то, что магазин не может делать бесконечные завтраки, продавать некоторые, делать, чтобы избежать потерь.

Разбирать идеи:

  • A: Производитель - сначала посмотрите, есть ли данные, подождите, если есть, произведите, если нет, уведомите потребителей о необходимости использования данных после производства.
  • B: Потребитель - сначала посмотрите, есть ли данные, потребляйте, если есть, подождите, если нет, уведомите производителя для создания данных.

объяснять:Пробуждение — сделайте потоки в пуле потоков доступными для выполнения

Класс Object предоставляет три метода:

//等待
wait()
//唤醒单个线程
notify()
//唤醒所有线程
notifyAll()

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

Почему эти методы не определены в классе Thread?

Вызовы этих методов должны вызываться через объект блокировки, а объект блокировки, который мы только что использовали, является произвольным объектом блокировки.

Поэтому эти методы должны быть определены в классе Object.

Напишем простой кодРеализовать механизм ожидания пробуждения

public class Student {
    String name;
    int age;
    boolean flag;// 默认情况是没有数据(false),如果是true,说明有数据

    public Student() {
    }
}
public class SetThread implements Runnable {
    private Student s;
    private int x = 0;

    public SetThread(Student s) {
        this.s = s;
    }

    @Override
    public void run() {
        while (true){
            synchronized (s) {
                //判断有没有数据
                //如果有数据,就wait
                if (s.flag) {
                    try {
                        s.wait(); //t1等待,释放锁
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                //没有数据,就生产数据
                if (x % 2 == 0) {
                    s.name = "admin";
                    s.age = 20;
                } else {
                    s.name = "User";
                    s.age = 30;
                }
                x++;
                //现在数据就已经存在了,修改标记
                s.flag = true;

                //唤醒线程
                //唤醒t2,唤醒并不表示你立马可以执行,必须还得抢CPU的执行权。
                s.notify();
            }
        }
    }
}
package cn.bwh_05_Notify;

public class GetThread implements Runnable {
    private Student s;

    public GetThread(Student s) {
        this.s = s;
    }

    @Override
    public void run() {
        while (true){
            synchronized (s){
                //如果没有数据,就等待
                if (!s.flag){
                    try {
                        s.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                System.out.println(s.name + "---" + s.age);

                //修改标记
                s.flag = false;
                //唤醒线程t1
                s.notify();
            }
        }
    }
}
package cn.bwh_05_Notify;

public class StudentTest {
    public static void main(String[] args) {
        Student s = new Student();

        //设置和获取的类
        SetThread st = new SetThread(s);
        GetThread gt = new GetThread(s);

        //线程类
        Thread t1 = new Thread(st);
        Thread t2 = new Thread(gt);

        //启动线程
        t1.start();
        t2.start();
    }
}
//运行结果依次交替出现

Оптимизация кода механизма ожидания пробуждения производителя-потребителя

Окончательный код (большие изменения в классе Student, затем классы GetThread и SetThread стали намного чище)

public class Student {
    private String name;
    private int age;
    private boolean flag;

    public synchronized void set(String name, int age) {
        if (this.flag) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        this.name = name;
        this.age = age;

        this.flag = true;
        this.notify();
    }

    public synchronized void get() {
        if (!this.flag) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        System.out.println(this.name + "---" + this.age);

        this.flag = false;
        this.notify();
    }
}
public class SetThread implements Runnable {
    private Student s;
    private int x = 0;

    public SetThread(Student s) {
        this.s = s;
    }

    @Override
    public void run() {
        while (true) {
            if (x % 2 == 0) {
                s.set("admin", 20);
            } else {
                s.set("User", 30);
            }
            x++;
        }
    }
}
public class GetThread implements Runnable{
    private Student s;

    public GetThread(Student s) {
        this.s = s;
    }

    @Override
    public void run() {
        while (true){
            s.get();
        }
    }
}
public class StudentTest {
    public static void main(String[] args) {
        Student s = new Student();
        //设置和获取的类

        SetThread st = new SetThread(s);
        GetThread gt = new GetThread(s);

        Thread t1 = new Thread(st);
        Thread t2 = new Thread(gt);

        t1.start();
        t2.start();
    }
}

Особенности окончательного кода:

  • Сделайте переменные-члены Student закрытыми.
  • Операции установки и получения инкапсулированы в функции, добавлена ​​синхронизация.
  • Вам нужно только вызвать метод в потоке, который установлен или получен

(11) Пул потоков

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

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

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

JDK5新增了一个Executors工厂类来产生线程池,有如下几个方法
//创建一个具有缓存功能的线程池
//缓存:百度浏览过的信息再次访问
public static ExecutorService newCachedThreadPool()

//创建一个可重用的,具有固定线程数的线程池
public static ExecutorService newFixedThreadPool(intnThreads)
  					 
//创建一个只有单线程的线程池,相当于上个方法的参数是1 
public static ExecutorService newSingleThreadExecutor()
  					 
这些方法的返回值是ExecutorService对象,该对象表示一个线程池,可以执行Runnable对象或者Callable对象代表的线程。它提供了如下方法

Future<?> submit(Runnable task)
<T> Future<T> submit(Callable<T> task)
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ExecutorDemo {
    public static void main(String[] args) {
        //创建一个线程池对象,控制要创建几个线程对象
        ExecutorService pool = Executors.newFixedThreadPool(2);

        //可以执行Runnalble对象或者Callable对象代表的线程
        pool.submit(new MyRunnable());
        pool.submit(new MyRunnable());

        //结束线程池
        pool.shutdown();
    }
}

(12) Реализация многопоточных программ в виде анонимных внутренних классов.

Формат анонимного внутреннего класса:

new 类名或者接口名( ) {
              重写方法;
          };

Сущность: это подкласс объекта класса или интерфейса

public class ThreadDemo {
    public static void main(String[] args) {
        new Thread() {
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    System.out.println(Thread.currentThread().getName() + i);
                }
            }
        }.start();
    }
}
public class RunnableDemo {
    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    System.out.println(Thread.currentThread().getName() + i);
                }
            }
        }).start();
    }
}

(13) Таймер

Таймер — это широко используемый инструмент многопоточности, который можно использовать для планирования выполнения нескольких задач по времени в качестве фоновых потоков. В Java функцию определения расписания можно реализовать с помощью классов Timer и TimerTask.

Timer

·public Timer()

public void schedule(TimerTask task, long delay)

public void schedule(TimerTask task,long delay,long period)

TimerTask

abstract void run()

public boolean cancel()

В разработке

Quartz — это фреймворк планирования с открытым исходным кодом, полностью написанный на Java.

конец:

Если есть какие-либо недостатки в содержании или неправильные места, пожалуйста, оставьте мне сообщение, чтобы оставить комментарии! ^_^

Если это может вам помочь, то следуйте за мной! (Серия статей будет обновляться по мере возможности на официальном аккаунте)

Мы здесь незнакомцы, но мы все усердно работаем для своей мечты ❤

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