НАЗАД

JAVA. Часть 2.

Синхронизация потоков.

Синхронизация потоков – это обеспечения потока исключительным доступом к определенным ресурсам процесса.
Синхронизацию потоков обеспечивает монитор. Монитор - это некое программное средство, обеспечивающие контроль за доступу к ресурсу. У монитора может быть один владелец в определенную единицу времени. Иными словами, если какой-либо поток обозначил, что ресурс используется им, то монитор не позволит другому потоку получить доступ к этому ресурсу.
Существует два способа объявить синхронизацию ресурса в коде:
1 способ:
Создание блока синхронизации.
В этом случае можно синхронизировать доступ к конкретному объекту.
Синтаксис:
synchronized(obj1){… …}.
obj1 – объект, доступ к которому синхронизирован.
Пример:

package ja_thread_009;
import static ja_thread_009.Thread_A1.setNumberOperation;
class SimpleData{
    private int account=10000;
    private static int list_operation=100;
    public int getAccount() {
        return account;
    }
    public static int getList_operation() {
        return list_operation;
    }
    public void setAccount(int account) {
        this.account = account;
    }
    public static void setList_operation(int list_operation) {
        SimpleData.list_operation = list_operation;
    }
}
class Thread_A1 implements Runnable{
    private int n=0;
    private SimpleData sd;
    public Thread_A1(SimpleData sd){
        this.sd=sd;
        new Thread(this).start();
    }
    public void setNumberAccount(){
        synchronized(sd){
            try {
                Thread.sleep(10);
            } catch (InterruptedException ex) {};
            int ma=sd.getAccount();
            sd.setAccount(++ma);
        }
    }
    public static void setNumberOperation(){
        synchronized(SimpleData.class){
            try {
                Thread.sleep(10);
                } catch (InterruptedException ex) {}
            int ms=SimpleData.getList_operation();
            SimpleData.setList_operation(++ms);
            }
        }
    @Override
    public void run() {
        for(int i=0; i<10;i++){
            setNumberAccount();
            System.out.println(sd.getAccount());
        }
        for(int i=0; i<10;i++){
                setNumberOperation();
                System.out.println(SimpleData.getList_operation());
        }
    }
}
class Processing{
    private int bank_account=100000;
    private int value_bank_account=0;
}
public class JA_Thread_009 {
    public static void main(String[] args) throws InterruptedException {
        SimpleData SimD= new SimpleData();
        Thread_A1 a1=new Thread_A1(SimD);
        Thread_A1 a2=new Thread_A1(SimD);
        Thread.sleep(2000);
        System.out.println(SimD.getAccount());
        System.out.println(SimpleData.getList_operation());
    }
}

В данном примере, указанном выше, доступ ограничен к классу с данными с помощью синхронизации. Т.е. все потоки, которые будут созданы классом Thread_A1 будут иметь разграниченный доступ к данным. Синхронизация обеспечивается в двух блоках в методах setNumberAccount(), setNumberOperation().
2 способ:
Создание синхронизированного метода в классе. Этот способ обычно применяется в случае, если методу необходимо синхронизировать несколько полей данных.
При вызове синхронизированного метода соответствующий объект, в котором он определен, блокируется для использования другими синхронизированными методами.
Синтаксис:

public class Example{
    public synchronized void metod1(arg-list){
    }
}
Пример:

package ja_tread_010;
class Processing {
    private int value_bank_account=0;
    public int getValue_bank_account() {
        return value_bank_account;
    }
    public void setValue_bank_account(int value_bank_account) {
        this.value_bank_account = value_bank_account;
    }
    public synchronized void increment_vba(int value){
        setValue_bank_account(getValue_bank_account()+value);
    }
    public synchronized void decrement_vba(int value){
        setValue_bank_account(getValue_bank_account()-value);
    }
}
public class JA_Thread_010 {
    public static void main(String[] args) throws InterruptedException {
        Processing p1=new Processing();
        Thread A2=new Thread(new Runnable(){
            @Override
            public void run() {
                try {
                    for(int i=0;i<10;i++){
                        Thread.sleep(10);
                        System.out.println(p1.getValue_bank_account());
                        p1.increment_vba(45);
                        System.out.println(p1.getValue_bank_account());
                    }
                } catch (InterruptedException ex) {}
            }
        });
        Thread A3=new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    for(int i=0;i<10;i++){
                        Thread.sleep(10);
                        System.out.println(p1.getValue_bank_account());
                        p1.decrement_vba(15);
                        System.out.println(p1.getValue_bank_account());
                    }
                } catch (InterruptedException ex) {}
            }
        });
        A2.start();
        A3.start();
        Thread.sleep(2000);
        System.out.println("Main stream is finished");
    }
}

В вышеуказанном примере используется синхронизация методов. В обычном классе созданы синхронные методы обработки данных класса. Это позволяет обеспечить доступ к данным
Ожидание оповещения или синхронизация по событиям.
Для организации взаимодействия потоков, таким образом, чтобы один поток управлял работой другого или других потоков, необходимо воспользоваться методами wait(), notify(), notofyAll(), которые определены в Object.
Метод void wait() – переводит поток в состояние ожидания, до тех пор, пока для этого потока не будет вызван метод notify() или метод notifyAll(). В качестве параметра данного метода может указываться время в миллисекундах.
Метод void notify() – оповещение, которое приводит к возобновлению работы потока (любого одного потока).
Метод void notifyAll() – оповещение, которое приводит к возобновлению работы всех потоков.
Для того, чтобы не получить Exception, методы wait(), notify() и notifyAll() должны находиться внутри блока synchronized, либо внутри метода synchronized.
Пример работы с оповещениями приведен ниже.

package ja_thread_011;
class Stream_Test01 extends Thread{
    private Object share;
    public Stream_Test01(Object obj) {
        share=obj;
    }
    public void waitStream(){
        synchronized(share){
            try {
                share.wait();
            } catch (InterruptedException ex) {}
        }
    }
    public void goStream(){
        synchronized (share){
            share.notifyAll();
        }
    }
    @Override
    public void run(){
        System.out.println("The Stream " + Thread.currentThread().getName()+" is worked");
        for(int i=0;i<20;i++){
            try{
                if (i==10){
                    System.out.println("!!wait() is enabled!!");
                    waitStream();
            }
            Thread.sleep(10);
            }catch(InterruptedException ex){}
            System.out.println("Value of counter is " + Thread.currentThread().getName()+ " "+i);
        }
    }
}
class Stream_Test02 extends Thread{
    private Object share;
    public Stream_Test02(Object obj) {
        share=obj;
    }
    public void waitStream(){
        synchronized(share){
            try {
                share.wait();
                    Thread.sleep(10);
            } catch (InterruptedException ex) {}
        }
    }
    public void goStream(){
        synchronized (share){
            share.notifyAll();
        }
    }
    @Override
    public void run(){
        System.out.println("The Stream " + Thread.currentThread().getName()+" is worked");
        for(int i=0;i<20;i++){
            if (i==15){
                System.out.println("!!notifyAll() is enable!!");
                goStream();
            }
            try{
                Thread.sleep(100);
            }catch(InterruptedException ex){}
            System.out.println("Value of counter is " + Thread.currentThread().getName()+ " "+i);
        }
    }
}
public class JA_Thread_011 {
    public static void main(String[] args) throws InterruptedException {
        Object obj=new Object();
        Stream_Test01 ST1;
        Stream_Test02 ST2;
        ST1=new Stream_Test01(obj);
        ST2=new Stream_Test02(obj);
        ST1.start();
        ST2.start();
        ST1.setName("First Stream");
        ST2.setName("Second Stream");
        System.out.println(ST1.getName());
        System.out.println(ST2.getName());
    }
}

Суть работы вышестоящей программы в следующем, создаются два класса, цель которых выполнить перебор числа в цикле. Один из классов имеет код, цель которого при выполнении перебора вызвать метод wait() и остановить свою работу. Другой класс имеет код, цель которого наоборот запустить все остановленные потоки вызвав метод, содержащий notifyAll(). Оба класса наследуют класс Thread и переопределят метод Run().
Каждый класс содержит метод остановки потока waitStream() и запуска потока goStream(). Блоки синхронизации находятся как раз в этих методах. В качестве объекта «маркера доступа» используется объект obj класса Object. В конструкторе каждого класса, данный объект присваивается объекту share класса Object.
В методе main(), основного класса программы, сначала создается объект obj, который по сути будет является «маркером доступа». Затем запускаем потоки. Первый поток выполняется до момента срабатывания условия, при котором выполняется метод wait(), и поток останавливается выдав сообщение !!wait() is enabled!!. Параллельно выполняется второй поток с несколько большей задержкой. В определенное время срабатывать условие и выполняется метод notifyAll(), поток выдает сообщение "!!notifyAll() is enable!!". Затем запускается первый поток и выполняется до полного завершения.



Николай Ткаченко, 2015 г.