Руководство по Java Core. Многопоточность. Снихронизация.

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

Например, несколько потоком пытаются внести изменения в один  и тот же текстовый файл. В результате этого данные могут быть повреждены, так как один поток пытается записать данные “поверх” данных второго потока.

Для предотвращения таких ситуаций применяется синхронизация.

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

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

Пример без применения синхронизации:

Класс Counter


public class Counter {
    public void displayCounter(){
        try {
            for(int i = 1; i<=5; i++){
                System.out.println("Counter: " + i);
            }
        }catch (Exception e){
            System.out.println("Thread is interrupted.");
        }
    }
}

Класс SimpleThread


public class SimpleThread extends Thread{
    private Thread thread;
    private String threadName;
    Counter counter;

    public SimpleThread(String threadName, Counter counter) {
        this.threadName = threadName;
        this.counter = counter;
    }

    public void run(){
        System.out.println("Thread " + threadName + " is running...");
        counter.displayCounter();
        System.out.println("Leaving " + threadName + " thread...");
    }

    public void start(){
        System.out.println("Thread " + threadName + " is started.");
        if(thread == null){
            thread = new Thread(this,threadName);
            thread.start();
        }
    }
}

Класс SimpleThreadDemo


public class SimpleThreadDemo {
    public static void main(String[] args) {
        Counter counter = new Counter();

        SimpleThread threadOne = new SimpleThread("Thread One", counter);
        SimpleThread threadTwo = new SimpleThread("Thread Two", counter);

        threadOne.start();
        threadTwo.start();

        try {
            threadOne.join();
            threadTwo.join();
        }catch (InterruptedException e){
            System.out.println("Threads interrupted.");
            e.printStackTrace();
        }
    }
}

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


/*Some System Messages*/

Thread Thread One is started.
Thread Thread Two is started.
Thread Thread Two is running...
Thread Thread One is running...
Counter: 1
Counter: 1
Counter: 2
Counter: 3
Counter: 4
Counter: 2
Counter: 3
Counter: 5
Counter: 4
Leaving Thread Two thread...
Counter: 5
Leaving Thread One thread...

Пример с применением синхронизации:

Класс Counter


public class Counter {
    public void displayCounter(){
        try {
            for(int i = 1; i<=5; i++){
                System.out.println("Counter: " + i);
            }
        }catch (Exception e){
            System.out.println("Thread is interrupted.");
        }
    }
}

Класс SynchThread


public class SynchThread extends Thread {
    private Thread thread;
    private String threadName;
    final Counter counter;

    public SynchThread(String threadName, Counter counter) {
        this.threadName = threadName;
        this.counter = counter;
    }

    public void run() {
        System.out.println("Thread " + threadName + " is running...");
        synchronized (counter) {
            counter.displayCounter();
        }
        System.out.println("Leaving " + threadName + " thread...");
    }

    public void start() {
        System.out.println("Thread " + threadName + " successfully started.");
        if (thread == null) {
            thread = new Thread(this, threadName);
            thread.start();
        }
    }
}

Класс SynchThreadDemo


public class SynchThreadDemo {
    public static void main(String[] args) {
        Counter counter = new Counter();

        SynchThread threadOne = new SynchThread("Synchronized Thread One", counter);
        SynchThread threadTwo = new SynchThread("Synchronized Thread Two", counter);

        threadOne.start();
        threadTwo.start();

        try {
            threadOne.join();
            threadTwo.join();
        }catch (InterruptedException e){
            System.out.println("Threads interrupted.");
            e.printStackTrace();
        }
    }
}

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


/*Some System Messages*/

Thread Synchronized Thread One successfully started.
Thread Synchronized Thread Two successfully started.
Thread Synchronized Thread One is running...
Counter: 1
Counter: 2
Counter: 3
Counter: 4
Counter: 5
Leaving Synchronized Thread One thread...
Thread Synchronized Thread Two is running...
Counter: 1
Counter: 2
Counter: 3
Counter: 4
Counter: 5
Leaving Synchronized Thread Two thread...

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