Синхронизирован с многопоточностью Java

Java

авторСБ Валар
Если вам нужно перепечатать, пожалуйста, сохраните исходную ссылку

связанное предложение:

Синхронизирован с многопоточностью Java

Многопоточность Java нестабильна

содержание:

  • Что синхронизируется?
  • Связь между синхронизацией и атомарностью, видимостью и упорядоченностью
  • Несколько вариантов использования синхронизированного
  • Разница между синхронизацией и блокировкой

1. Что синхронизируется

Синхронизированный китайский означает: синхронизированный, синхронизированный. является ключевым словом в Java.

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

2. Связь между синхронизацией и атомарностью, видимостью и упорядоченностью

Сначала кратко ознакомьтесь со следующими 3 понятиями,

  • Атомный:原子(atom)本意指化学反应不可再分的基本微粒。Атомарность в программировании означает, что операцию больше нельзя разделить на несколько шагов. Операция или несколько операций выполняются либо все, и процесс выполнения не прерывается никаким фактором, либо ни одна из них не выполняется.
  • Видимость: когда несколько потоков обращаются к одной и той же переменной, один поток изменяет значение переменной, а другие потоки могут сразу увидеть измененное значение.
  • Упорядоченный: то есть порядок выполнения программы выполняется в порядке кода.

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

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

Например, если следующий код не переупорядочен, порядок его выполнения будет 1->2->3->4. Но в реальном исполнении это может быть 1->2->4->3 или 2->1->3->4 или другое. Но это гарантирует, что 1 предшествует 3, а 2 предшествует 4. Все конечные результатыa=10; b=20.

int a = 0;//语句1
int b = 1;//语句2
a = 10; //语句3
b = 20; //语句4

Но если это многопоточный случай, в другом потоке есть следующая программа. Когда указанный выше порядок выполнения изменяется на 1->2->4->3, когда поток 1 выполняется до шага 3b=20Когда переключитесь на выполнение потока 2, он выведетa此时已经是10了, а значение a все еще равно 0 в это время.

if(b == 20){
  System.out.print("a此时已经是10了");
}

2.1 Какая связь между синхронизацией и атомарностью?

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

В Java синхронизация соответствует двум инструкциям байт-кода.monitorenterа такжеmonitorexit. пройти черезmonitorenterа такжеmonitorexitИнструкция, которая может гарантировать, что код, измененный с помощью synchronized, может быть доступен только одному потоку за раз и не может быть доступен другим потокам, пока блокировка не будет снята.

2.2 Как синхронизация обеспечивает видимость?

Согласно механизму JMM (Java Memory Model, модель памяти Java) память в основном делится на два типа: основная память и рабочая память.При работе потока переменная копируется из основной памяти в рабочую память.

JMM предусматривает два условия для синхронизации:

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

2.3 Может ли синхронизированный гарантировать заказ?

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

Как понять это?

  • Вот концепция в первую очередь.as-if-serial语义, а это значит, что как бы ни переупорядочивались (компилятор и процессор для улучшения параллелизма), результат выполнения однопоточной программы изменить нельзя. Компиляторы и процессоры, как бы они ни оптимизировали, должны подчинятьсяas-if-serial语义.
  • as-if-serial语义Гарантируется, что существуют некоторые ограничения на переупорядочивание инструкций в одном потоке, то есть как бы переупорядочивание не производилось, оно не может повлиять на результат однопоточного выполнения. И синхронизация гарантирует, что к этой части программы может получить доступ только один и тот же поток в одно и то же время, поэтому она также гарантированно будет упорядочена.

3. Несколько вариантов использования синхронизированного

Использование синхронизированного можно условно разделить на три типа:

  1. Используется для декорирования общих методов (методов экземпляра), блокировка является текущим объектом экземпляра, а блокировка текущего экземпляра должна быть получена перед вводом кода синхронизации.
  2. Используется для изменения статических методов, блокировка является объектом класса текущего класса, и блокировка объекта текущего класса должна быть получена до ввода кода синхронизации.
  3. Действуя на кодовый блок, блокировкой является объект в круглых скобках, а данный объект заблокирован, и блокировка данного объекта должна быть получена до входа в синхронную кодовую базу.

Его влияние на метод записывается следующим образом:synchronizedпросто поставь返回类型Только вперед.

Ниже приведены некоторые конкретные примеры, чтобы увидеть, как использоватьsynchronizedРезультат после:

3.1 синхронизированные действия над методами экземпляра

public class TestBean {
    //TestBean中有两个实例方法,method1和method2
    public synchronized void method1(){
        System.out.println("method1 start");
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("method1 end");
    }

    public synchronized void method2(){
        System.out.println("method2 start");
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("method2 end");
    }
}
public class MainTest {

    public static void main(String[] args){
        TestBean testBean = new TestBean();
        //第一个线程,执行method1()
        new Thread(new Runnable() {
            @Override
            public void run() {
                testBean.method1();
            }
        }).start();
        /立刻开启第二个线程,执行method2()
        new Thread(new Runnable() {
            @Override
            public void run() {
                testBean.method2();
            }
        }).start();
    }
}

Консольный результат:

method1 start
(...3秒后输出)
method1 end
method2 start
(...3秒后输出)
method2 end

Видно, что замок действует наtestBeanобъект, другие потоки для доступаsynchronizedДругие методы модификации требуют ожидания, пока поток 1 сначала освободит блокировку.

  • что еслиmethod2()изsynchronizedМодификатор удален. Конечно, он будет выполняться немедленно, не дожидаясь снятия блокировки.method2(). Консоль выводит следующие результаты:
method1 start
method2 start
(...3秒后输出)
method1 end
method2 end
  • Или класс TestBean.java остается прежним, и оба методаsynchronizedУкрашенный, но с двумя разными объектами экземпляра.
public class MainTest {

    public static void main(String[] args){
        TestBean testBean1 = new TestBean();
        TestBean testBean2 = new TestBean();
        
        //第一个线程,执行method1()
        new Thread(new Runnable() {
            @Override
            public void run() {
                testBean1.method1();
            }
        }).start();
        /立刻开启第二个线程,执行method2()
        new Thread(new Runnable() {
            @Override
            public void run() {
                testBean2.method2();
            }
        }).start();
    }
}

В настоящее время, поскольку два потока воздействуют на разные объекты и получают разные блокировки, они не влияют друг на друга Результаты консоли следующие:

method1 start
method2 start
(...3秒后输出)
method1 end
method2 end

3.2 синхронизированный модифицированный статический метод.

Измените TestBean.java выше на:

public class TestBean {
    //TestBean中有一个静态方法,method
    synchronized public static void method(String threadName){
        System.out.println("method start by " + threadName);
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("method end by " + threadName);
    }
}

Main.java выглядит следующим образом:

public class MainTest {

    public static void main(String[] args){
        TestBean testBean1 = new TestBean();
        TestBean testBean2 = new TestBean();

        new Thread(new Runnable() {
            @Override
            public void run() {
                testBean1.method("thread1");
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                testBean2.method("thread2");
            }
        }).start();
    }
}

Консольный результат:

method start by thread1
(...3秒后输出)
method end by thread1
method start by thread2
(...3秒后输出)
method end by thread2

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

3.3 синхронизированный модифицированный кодовый блок.

Зачем воздействовать на кодовые блоки?

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

public class TestBean {

    private final static Object objectLock = new Object();

    void method1(){
        System.out.println("not synchronized method1");
        synchronized (objectLock){
            System.out.println("synchronized method1 start");
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("synchronized method1 end  ");
        }

    }

    void method2(){
        System.out.println("not synchronized method2" );
        synchronized (objectLock){
            System.out.println("synchronized method2 start");
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("synchronized method2 end");
        }
    }
}
public class MainTest {

    public static void main(String[] args){
        TestBean testBean1 = new TestBean();
        TestBean testBean2 = new TestBean();

        new Thread(new Runnable() {
            @Override
            public void run() {
                testBean1.method1();
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                testBean2.method2();
            }
        }).start();
    }
}

Вывод консоли:

not synchronized method1
synchronized method1 start
not synchronized method2
(...3秒后输出)
synchronized method1 end  
synchronized method2 start
(...3秒后输出)
synchronized method2 end

Вы можете видеть, что нет взаимного исключения, когда код не обернут синхронизированным,System.out.println("not synchronized method2" );Нет необходимости ждать завершения выполнения method1, но блок кода, обернутый с помощью synchronized и использующий тот же объект, что и блокировка, будет взаимоисключающим.

в заключении:

  • будетsynchronizedДействует на данный экземпляр объекта objectLock каждый раз, когда поток входитsynchronizedОбернутый блок кода потребует, чтобы текущий поток удерживал блокировку объекта экземпляра objectLock.Если другие потоки в настоящее время удерживают блокировку объекта, вновь прибывший поток должен ждать.
  • Конечно, помимо objectLock в качестве объекта мы также можем использоватьthisобъект (представляющий текущий экземпляр) или текущего классаclass对象В качестве блокировки следующий код:
    synchronized (this){
        System.out.println("synchronized method1 start");
        System.out.println("synchronized method1 end");
    }

    synchronized (TestBean.class){
        System.out.println("synchronized method2 start");
        System.out.println("synchronized method2 end");
    }

4. Разница между синхронизацией и блокировкой

LockЭто класс интерфейса на языке Java, и соответствующий класс реализацииReentrantLock, они находятся вjava.util.concurrent.locksПод пакетом учащиеся, знакомые с Java, должны знать, что параллельный пакет используется для решения проблем многопоточности Java.

4.1 Использование блокировки

Несколько распространенных способов блокировки:

lock():获取锁,如果锁被暂用则一直等待

unlock():释放锁

tryLock(): 注意返回类型是boolean,如果获取锁的时候锁被占用就返回false,否则返回true

tryLock(long time, TimeUnit unit):比起tryLock()就是给了一个时间期限,保证等待参数时间
ck
lockInterruptibly():用该锁的获得方式,如果线程在获取锁的阶段进入了等待,那么可以中断此线程,先去做别的事

Использование блокировки похоже на синхронизацию, применяемую к блокам кода:

public class TestBean {
    private Lock lock = new ReentrantLock();
    void method1(String threadName) {
        lock.lock();
        try{
            System.out.println("method1 start " + threadName);
            //耗时操作
            ...
        } finally {
            System.out.println("method1 end " + threadName); 
            lock.unlock();//释放锁
        }
    }

}
public class MainTest {

    public static void main(String[] args){
        TestBean testBean1 = new TestBean();

        new Thread("thread1") {
            @Override
            public void run() {
                testBean1.method1(Thread.currentThread().getName());
            }
        }.start();

        new Thread("thread2"){
            @Override
            public void run() {
                testBean1.method1(Thread.currentThread().getName());
            }
        }.start();
    }
}

Результат выполнения следующий:

method1 start thread1
(...3秒后输出)
method1 end thread1
method1 start thread2
(...3秒后输出)
method1 end thread2

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

  • Немного измените TestBean.java и посмотритеtryLock()Использование метода:
public class TestBean {
    private Lock lock = new ReentrantLock();
    void method1(String threadName) {
         if (lock.tryLock()){
            try{
                System.out.println("method1 start " + threadName);
                //耗时操作
                ...
            } finally {
                lock.unlock();
            }
        } else {
            System.out.println("我是"+threadName+",有人占着锁,我放弃了");
        }
    }

}

Вывод консоли:

method1 start thread1
我是thread2,有人占着锁,我放弃了
(...3秒后输出)
method1 end thread1

4.2 Разница между блокировкой и синхронизацией

категория synchorinzed lock
уровень существования Ключевые слова Java Класс интерфейса, реализованный ReentrantLock
разблокировка замка 1. Поток, который получает блокировку, выполняет код синхронизации и освобождает блокировку 2. Исключение возникает при выполнении потока, и jvm позволяет потоку снять блокировку. Блокировка должна быть снята окончательно, иначе легко вызвать взаимоблокировку потока.
получение блокировки Предположим, что поток A получает блокировку, а поток B ждет. Если поток A заблокируется, поток B будет продолжать ждать В зависимости от ситуации у блокировки есть несколько методов получения блокировки, вы можете попробовать получить блокировкуtryLock(), поток не должен ждать все время
статус блокировки не в состоянии судить Можно судить
представление Это больше подходит, когда конкуренция за ресурсы не очень жесткая; когда синхронизация очень жесткая, производительность синхронизированного падает очень быстро. В случае низкой конкуренции за ресурсы производительность немного хуже, чем при синхронизации, но может оставаться нормальной при жесткой конкуренции за ресурсы.

Два конкретных теста производительности:woo woo woo.cn blog on.com/you say me 2018/afraid/5…

Ссылаться на:blog.CSDN.net/U012403290/…

Эпилог

Я напишу статью с описанием позжеОсновной принцип синхронизации: Используемые концепции будут включать инструкции Monitor, monitorenter и monitorexit, синхронизированный повторный вход, синхронизированный и прерванный.

Я также напишу статью оvolatileСтатья: в основном включает в себя характеристики Volatile, принцип его реализации, переупорядочивание инструкций и барьеры памяти.