Синхронизация методов и блоков в Java

Опубликовано: 15 Февраля, 2022

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

Необходимость синхронизации

Consider the following Example:

// Java program to illustrate need
// of Synchronization
import java.io.*;
  
class Multithread
{
    private int i = 0;
    public void increment()
    {
        i++;
    }
  
    public int getValue()
    {
        return i;
    }
}
  
class GfG
{
    public static void main (String[] args)
    {
        Multithread t = new Multithread();
        t.increment();
        System.out.println(t.getValue());
    }
}

Выход:

 1

В приведенном выше примере выполняются три операции:

  1. Получить значение переменной i.
  2. Увеличьте полученное значение.
  3. И сохраните увеличенное значение i в его местоположении.

Здесь,

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

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

В языке программирования Java есть две идиомы синхронизации:

  • Синхронизация методов
  • Оператор (ы) синхронизации (Блочная синхронизация)

Синхронизация методов

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

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

Example: This shows if more than one threads accessing getLine() method without synchronization.

// Example illustrates multiple threads are executing
// on the same Object at same time without synchronization.
import java.io.*;
  
class Line
{
    // if multiple threads(trains) will try to
    // access this unsynchronized method,
    // they all will get it. So there is chance
    // that Object"s  state will be corrupted.
    public void getLine()
    {
        for (int i = 0; i < 3; i++)
        {
            System.out.println(i);
            try
            {
                Thread.sleep(400);
            }
            catch (Exception e)
            {
                System.out.println(e);
            }
        }
    }
}
  
class Train extends Thread
{
    // reference to Line"s Object.
    Line line;
  
    Train(Line line)
    {
        this.line = line;
    }
  
    @Override
    public void run()
    {
        line.getLine();
    }
}
  
class GFG
{
    public static void main(String[] args)
    {
        // Object of Line class that is shared
        // among the threads.
        Line obj = new Line();
  
        // creating the threads that are
        // sharing the same Object.
        Train train1 = new Train(obj);
        Train train2 = new Train(obj);
  
        // threads start their execution.
        train1.start();
        train2.start();
    }
}

Выход

0
0
1
1
2
2

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

Example: Synchronized access to getLine() method on the same Object

// Example that shows multiple threads
// can execute the same method but in
// synchronized way.
class Line
{
  
    // if multiple threads(trains) trying to access
    // this synchronized method on the same Object
    // but only one thread will be able
    // to execute it at a time.
    synchronized public void getLine()
    {
        for (int i = 0; i < 3; i++)
        {
            System.out.println(i);
            try
            {
                Thread.sleep(400);
            }
            catch (Exception e)
            {
                System.out.println(e);
            }
        }
    }
}
  
class Train extends Thread
{
    // Reference variable of type Line.
    Line line;
  
    Train(Line line)
    {
        this.line = line;
    }
  
    @Override
    public void run()
    {
        line.getLine();
    }
}
  
class GFG
{
    public static void main(String[] args)
    {
        Line obj = new Line();
  
        // we are creating two threads which share
        // same Object.
        Train train1 = new Train(obj);
        Train train2 = new Train(obj);
  
        // both threads start executing .
        train1.start();
        train2.start();
    }
}

Выход:

0
1
2
0
1
2

Блочная синхронизация

If we only need to execute some subsequent lines of code not all lines (instructions) of code within a method, then we should synchronize only block of the code within which required instructions are exists.
For example, lets suppose there is a method that contains 100 lines of code but there are only 10 lines (one after one) of code which contain critical section of code i.e. these lines can modify (change) the Object’s state. So we only need to synchronize these 10 lines of code method to avoid any modification in state of the Object and to ensure that other threads can execute rest of the lines within the same method without any interruption.

import java.io.*;
import java.util.*;
  
public class Geek
{
    String name = "";
    public int count = 0;
  
    public void geekName(String geek, List<String> list)
    {
        // Only one thread is permitted
        // to change geek"s name at a time.
        synchronized(this)
        {
            name = geek;
            count++;  // how many threads change geek"s name.
        }
  
        // All other threads are permitted
        // to add geek name into list.
        list.add(geek);
    }
}
  
class GFG
{
    public static void main (String[] args)
    {
        Geek gk = new Geek();
        List<String> list = new ArrayList<String>();
        gk.geekName("mohit", list);
        System.out.println(gk.name);
  
    }
}

Выход :

мохит

Важные моменты:

  • Когда поток входит в синхронизированный метод или блок, он получает блокировку, и как только он завершает свою задачу и выходит из синхронизированного метода, он снимает блокировку.
  • Когда поток входит в метод или блок синхронизированного экземпляра, он получает блокировку уровня объекта, а когда он входит в синхронизированный статический метод или блок, он получает блокировку уровня класса.
  • Синхронизация Java вызовет исключение нулевого указателя, если объект, используемый в синхронизированном блоке, имеет значение NULL. Например, если в synchronized (instance) экземпляр имеет значение NULL, то он выдаст исключение с нулевым указателем.
  • В Java wait (), notify () и notifyAll () являются важными методами, которые используются при синхронизации.
  • Вы не можете применить ключевое слово java synchronized к переменным.
  • Не выполняйте синхронизацию в незавершенном поле в синхронизированном блоке, потому что ссылка на незавершенное поле может измениться в любое время, и тогда разные потоки могут синхронизироваться на разных объектах, то есть никакой синхронизации вообще.

Преимущества

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

Ограничения

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

Эта статья предоставлена Nitsdheerendra . Если вам нравится GeeksforGeeks, и вы хотели бы внести свой вклад, вы также можете написать статью с помощью provide.geeksforgeeks.org или отправить ее по электронной почте на deposit@geeksforgeeks.org. Посмотрите, как ваша статья появляется на главной странице GeeksforGeeks, и помогите другим гикам.

Пожалуйста, напишите комментарии, если вы обнаружите что-то неправильное, или вы хотите поделиться дополнительной информацией по теме, обсужденной выше.

Вниманию читателя! Не переставай учиться сейчас. Ознакомьтесь со всеми важными концепциями Java Foundation и коллекций с помощью курса "Основы Java и Java Collections" по доступной для студентов цене и будьте готовы к работе в отрасли. Чтобы завершить подготовку от изучения языка к DS Algo и многому другому, см. Полный курс подготовки к собеседованию .