Страницы

Поиск по вопросам

Показаны сообщения с ярлыком synchronized. Показать все сообщения
Показаны сообщения с ярлыком synchronized. Показать все сообщения

понедельник, 24 февраля 2020 г.

Не понятна логика возникновения DeadLock

#java #synchronized


public void transferMoney(Account fromAccount, Account toAccount, Amount amount)
throws InsufficientFundsException {
    synchronized (fromAccount) {
        synchronized (toAccount) {
            if (fromAccount.getBalance().compareTo(amount) < 0)
                throw new InsufficientFundsException();
            else {
                fromAccount.debit(amount);
                toAccount.credit(amount);
            }
        }
    }
}


Описание: Если со счета A на счет B перевести x денег, а со счета B на счет A – y,
то при неудачном стечении обстоятельств, транзакция 1 займет монитор счета A, транзакция
2 займет монитор счета B. Результат – взаимная блокировка.

Я никак не могу понять: Как же второй трэд доберётся до второго synchronized, если
первый synchronized уже был заблокирован первым трэдом?
Пересмотрел уже кучу лекций и перечитал про synchronized.
    


Ответы

Ответ 1



Он не будет заблокирован. synchronized воспользуется intrinsic lock того объекта, который будет указан в самом synchronized, поэтому два потока могут войти во внешний synchronized при том условии, что они используют два разных объекта: де-факто, synchronized(lockObject) - это гарантия использования объекта только одним потоком, но не гарантия исполнения кода максимум одним потоком в любой момент времени. Для достижения дедлока в этом случае достаточно, чтобы ни один из потоков не смог войти во внутренний synchronized - и это условие выполняется, если используются те же два аккаунта, но в противоположных потоках: Thread 1 | Thread 2 -------------------|------------------- обычное состояние | обычное состояние взял лок объекта А | взял лок объекта Б ждет лок объекта Б | ждет лок объекта А В этом случае прогресс невозможен, потому что для освобождения ресурса требуется прогресс любого из потоков, а это возможно только в случае освобождения ресурса.

среда, 22 января 2020 г.

Можно ли вызвать НЕ synchronized метод “заблокированного” объекта?

#java #многопоточность #synchronized


Есть 2 потока, один из них начал выполнение synchronized метода, внутри которого
применяется Thread.sleep(5000). Может ли другой поток использовать другие НЕСИНХРОНИЗИРОВАННЫЕ
методы этого "заблокированного" объекта?
Как я понимаю, "блокируется" не сам объект, а лишь synchronized участи кода (либо
методы).
    


Ответы

Ответ 1



Да, можно совершать данное действие. Почему бы просто не попробовать? Вы правильно понимаете.

пятница, 10 января 2020 г.

Блокируется ли объект в synchronized блоке созданный в конструкторе класса?

#java #synchronized


Может быть глупый вопрос но всё же спрошу.

Имеется synchronized блок, в котором блокируется объект. В конструкторе этого самого
объекта создается другой объект.

Вопрос блокируется ли он так же?

пример:

public class SomeClass{

private SecondClass sClass;

public SomeClass(){
      sClass = new SecondClass();    
}

.
. // some action
.
try{
   synchronized(this){
      this.wait(sometime);
   }
 }

    


Ответы

Ответ 1



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

четверг, 9 января 2020 г.

Многопоточность и синхронизация методов в Java

#java #synchronized


Не могу понять, почему данный код не работает корректно.

import java.util.concurrent.atomic.AtomicInteger;

public class Test1 extends Thread {
    public static AtomicInteger counter = new AtomicInteger(0);
    synchronized void doWork1() {

        counter.incrementAndGet();
        counter.incrementAndGet();

        System.out.println("Thread+++" + " - " + Test1.counter);
        try {
            Thread.currentThread().sleep(10);
        } catch (InterruptedException e) {

        }

    }

    synchronized void doWork2() {
        counter.decrementAndGet();
        counter.decrementAndGet();
        System.out.println("Thread---" + " - " + Test1.counter);
        try {
            Thread.currentThread().sleep(10);
        } catch (InterruptedException e) {

        }
    }

    public void run() {
        for (int i = 0; i < 100; i++) {
            doWork1();
            doWork2();
        }
    }

    public static void main(String[] args) {
        Test1 test1 = new Test1();
        test1.start();
        Test1 test2 = new Test1();
        test2.start();
    }
}

    


Ответы

Ответ 1



При указании ключевого слова synchronized для методов в качестве монитора, который захватывается потоками, используется соответствующий экземпляр класса. Так как у вас два разных экземпляра класса Test1 - потоки в принципе работают независимо друг от друга. Для того, чтобы решить эту проблему, необходимо использовать общий монитор для обоих потоков, например, отдельный объект: private final static Object lock = new Object(); void doWork1() { synchronized(lock) { ... } } void doWork2() { synchronized(lock) { ... } } Таким образом, потоки уже будут ждать освобождения монитора перед выполнением кода внутри блока synchronized.

Ответ 2



Как уже было отмечено выше, синхронизация в случае нестатических методов происходит по объекту, вызывающему этот метод. В данном случае есть два разных экземпляра класса Test1, локи захватываются на разные объекты. Решить проблему можно несколькими путями: либо захватить лок на весь класс: void doWork1() { synchronized(Test1.class) { ... } } либо захватить лок на один и тот же объект( в данном случае можно на counter) void doWork1() { synchronized(counter) { ... } } Я бы предпочел второй вариант.

Ответ 3



incrementAndGet - да, атомарна, но два таких оператора написанных друг за другом не являются таковыми Вот смотри, например, один поток вошел в doWork1 и выполнил операцию counter.incrementAndGet() и в этот момент управление перешло к другому потоку который начинает выполнять doWork2 и выполняет его до конца. на его выходе получишь нечетное число.

суббота, 4 января 2020 г.

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

#java #многопоточность #synchronized


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

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

Что посоветуете?
    


Ответы

Ответ 1



Поройтесь в java.util.concurrent.synchronizers. Скорее всего вам понадобится CountDownLatch Можно ставить блочить все остальные потоки, пока один поток не дойдет до точки разблокировки. Пошорудить их так, чтобы они поочередно друг друга разблокировали. А вобще, есть хорошая статья в которой эту библиотеку подробно разбирают. Не это, так другое найдете. UPD Во! Я все ее искал то! Вот тут более наглядно и с картинками.

Ответ 2



Самый простой вариант, который пришел в голову. Каждому потоку даем некоторый порядковый номер, заводим глобальную переменную и последовательно ее искрементируем. public class Solution { private static int counter = 0; private static final int THREAD_COUNT = 10; public static void main(final String[] args) throws InterruptedException { for (int i = 0; i < THREAD_COUNT; i++) { Printer printer = new Printer(i, Arrays.toString(Character.toChars(i + 'a'))); Runnable task = () -> { while (!Thread.currentThread().isInterrupted()) print(printer); }; new Thread(task).start(); } while (!Thread.currentThread().isInterrupted()) TimeUnit.SECONDS.sleep(10); } private static void print(Printer printer) { while (!Thread.currentThread().isInterrupted()) { synchronized (Solution.class) { if (counter == printer.id) { counter = (counter + 1) % THREAD_COUNT; System.out.println(printer.word); } } } } private static class Printer { private final int id; private final String word; Printer(int id, String word) { this.id = id; this.word = word; } }

вторник, 31 декабря 2019 г.

Несколько параллельных потоков, будут ли проблемы

#java #многопоточность #synchronized


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

    public class SomeData {
      private boolean correct;
      private boolean computed;

      public SomeData (/*...*/){
           correct = true;
           computed = false;
      }
      public booolean isCorrect(){
       if (!correct){
          computeCorrectess();
         }
       return correct;
      }

      private synchronized void computeCorrectess(){
         computed = true;
         //some long computation of value correct
         //correct = ...
      }
   }

    


Ответы

Ответ 1



Проблема может быть с методом isCorrect Кейс следующий: Пусть есть поток А и поток Б, работающие с данным объектом, в обоих потоках одновременно вызывается isCorrect, далее: if(!correct){ //correct == false для потока А и для потока Б //Оба потока попадают сюда, и оба попадут внутрь computeCorrectness, но по очереди. //Я полагаю что этот метод должен выполниться только в одном из потоков. computeCorrectness(); } Для решения данной проблемы рекомендую ознакомится с паттерном Double checked locking

Ответ 2



Проблема будет с методом isCorrect а если быть совсем точным с синхронизацией метода private synchronized void computeCorrectess() Все потоки дошедшие до данного метода станут в очередь пройдя логическую развилку: if (!correct) { } и как далее понятно первый вошедший поток изменит состояние флага но очередь потоков уже возможно будет сформирована. Проверочный код: public class SomeClass implements Runnable { SomeData someData = new SomeData(); public static void main(String[] args) { new SomeClass().threadsGenerator(); } private void threadsGenerator() { for (int i = 0; i < 10; i++) { new Thread(this).start(); } } @Override public void run() { try { someData.isCorrect(); } catch (InterruptedException e) { e.printStackTrace(); } } public class SomeData { private boolean correct; private boolean computed; public SomeData() { correct = false; computed = false; } public boolean isCorrect() throws InterruptedException { if (!correct) { computeCorrectess(); } return correct; } private synchronized void computeCorrectess() throws InterruptedException { System.out.println(correct); TimeUnit.SECONDS.sleep(1); correct = true; } } } рекомендую к прочтению

суббота, 28 декабря 2019 г.

зачем нужен synchronized()

#java #android #многопоточность #synchronized


Интент-сервис регистрации/логина в приложении.

@Override
protected void onHandleIntent(Intent intent) {
    try {
        // ЗАЧЕМ ?
        synchronized (TAG) {
            // [START register_for_gcm]
            // Initially this call goes out to the network to retrieve the token,
subsequent calls
            // are local.

            // [START get_token]
            InstanceID instanceID = InstanceID.getInstance(this);

            String token = instanceID.getToken(
                    getString(R.string.gcm_defaultSenderId),
                    GoogleCloudMessaging.INSTANCE_ID_SCOPE, null);
            // [END get_token]

            if (intent.getStringExtra("state").equals("registration")) {
                //make registration request with callback
            } else if (intent.getStringExtra("state").equals("login")) {
                //make login request with callback
            }


            subscribeTopics(token);

            // [END register_for_gcm]
        }
    } catch (Exception e) {
    }
}


Вопросы:


Зачем тут используется synchronized() ?
Для чего в целом нужен synchronized() , где его использовать?


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


Ответы

Ответ 1



Простыми словами: если у вас есть переменные, которые изменяются в одном из потоков, и читаются в другом, то их использование нужно синхронизировать. То есть, заключать их использование в блок synchronized. Использование блока synchronized исключает одновременное выполнение этих блоков (синхронизирующихся по одному и тому же объекту) разными потоками. Зачем именно тут используется synchronized — скорее всего, какая-то часть кода внутри работает с разделяемыми переменными.

Ответ 2



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

понедельник, 23 декабря 2019 г.

Зачем в getInstance synchronized?

#android #synchronized


Зачем в этом getInstance synchronized делать ?

Это класс-синглтон для работы с сетью с использованием retrofit.

public static NetworkWorker getInstance(){
    if (networkWorker == null){
        synchronized (NetworkWorker.class) {
            if (networkWorker == null) {
                networkWorker = new NetworkWorker();
            }
        }
    }
    return networkWorker;
}

    


Ответы

Ответ 1



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

Ответ 2



Паттерн называется Double checked locking. Призван в случае ленивой инициализации ликвидировать дорогую синхронизацию (в случае когда getInstance объявляется синхронным), которая нужна только когда несколько потоков обращаются за инстансом в момент его инициализации, при последующих обращениях синхронизация не нужна. У правильной реализации данного паттерна полно проблем: и happens before (когда ссылка на объект доступна вне критической секции до окончания инициализации) и деградация производительности из-за volatile... Если ситуация позволяет, то синглтон правильнее создавать сразу при объявлении. Либо гарантировать запуск потоков строго после инициализации синглтона, при этом DCL использовать ни к чему.

четверг, 19 декабря 2019 г.

Java оператор synchronized

#java #synchronized


Постигаю основы Java по книге Герберта Шилдта Java 8 Полное руководство. Решил воспроизвести
пример с книги:

public class Test {

    public static void main(String args[]) {
        Callme target = new Callme();
        Caller obj1 = new Caller(target, "Welcome");
        Caller obj2 = new Caller(target, "to synchronized");
        Caller obj3 = new Caller(target, "world!");
        try {
            obj1.t.join();
            obj2.t.join();
            obj3.t.join();
        } catch(InterruptedException e) {
            System.out.println("interrupted!");
        }
    }

}

class Callme {
    void call(String msg) {
        System.out.print("[" + msg);
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            System.out.println("Thread is interrupted!");
        }
        System.out.println("]");
    }
}

class Caller implements Runnable{
    String msg;
    Callme target;
    Thread t;
    public Caller(Callme trg, String s) {
        target = trg;
        msg = s;
        t = new Thread(this);
        t.start();
    }
    public void run() {
            synchronized(target) {
                target.call(msg);
            }

    }
}


Судя по учебнику вывод должен быть таким: 

[Welcome]  
[to synchronized]  
[world!]


Вместо этого получаем следующее:

[Welcome]
[world!]
[to synchronized]


Заранее прошу прощения за, возможно, нубский вопрос, но ошибку в упор не вижу.

UPD:

Даже если поставить задержку между созданием объектов следующим образом:

        Callme target = new Callme();
        Caller obj1 = new Caller(target, "Welcome");
        Caller obj2 = new Caller(target, "to synchronized");
        Thread.sleep(700);
        Caller obj3 = new Caller(target, "world!");


то все равно не удается добиться желаемого эффекта. Как по мне так очень странно.
    


Ответы

Ответ 1



Смысл примера в следующем. На каждый возов идет две операции печати. При синхронизации гарантируется, что метод выполнит обе печати, прежде чем другой поток сможет печатать что-либо свое. Без синхронизации будет что-то типа [Welcome[to synchronized[world!] ] ] Порядок же выполнения потоков не детерминирован. Кто быстрее зайдет в критическую секцию, тот и будет печатать, остальные будут ждать окончания.

Ответ 2



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

Ответ 3



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

понедельник, 16 декабря 2019 г.

(Java) Как правильно использовать BufferedReader одновременно несколькими потоками (Thread)

#java #многопоточность #input #synchronized


При изучении потоков (Threads) столкнулся с задачей. Нужно, используя один BufferedReader
и три потока (Thread), считать строки с клавиатуры и сохранить в ArrayList значения
считанных потоком строк. (3 экземпляра класса-наследника Thread и также 3 экземпляра
переменной класса Array ArrayList)

Методом проб и ошибок вывел рабочий код:

public class Solution {
    public static volatile AtomicInteger countReadStrings = new AtomicInteger(0);
    public static volatile BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));

    public static void main(String[] args) throws IOException {
        //read count of strings
        int count = Integer.parseInt(reader.readLine());

        //init threads
        ReaderThread consolReader1 = new ReaderThread();
        ReaderThread consolReader2 = new ReaderThread();
        ReaderThread consolReader3 = new ReaderThread();

        consolReader1.start();
        consolReader2.start();
        consolReader3.start();

        while (count > countReadStrings.get()) {
        }

        consolReader1.interrupt();
        consolReader2.interrupt();
        consolReader3.interrupt();
        System.out.println("#1:" + consolReader1);
        System.out.println("#2:" + consolReader2);
        System.out.println("#3:" + consolReader3);

        reader.close();
    }

    public static class ReaderThread extends Thread {
        private List result = new ArrayList();

        public void run() {
            while ( !Thread.currentThread().isInterrupted()){
                    try {
                        if (reader.ready()){ // <- корректно отрабатывает
                            result.add(reader.readLine());
                            Solution.countReadStrings.incrementAndGet();
                        }
                    }catch (IOException e){
                        System.out.println("*************error");
                }
            }
        }

        @Override
        public String toString() {
            return result.toString();
        }
    }
}


Но ранее пробовал такой метод run() класса ReaderThread

            public void run() {
            //add your code here - добавьте код тут
            while ( !Thread.currentThread().isInterrupted()){
                    try {
                        synchronized (reader){ // <- НЕкорректно отрабатывает
                            result.add(reader.readLine());
                            Solution.countReadStrings.incrementAndGet();
                        }
                    }catch (IOException e){
                        System.out.println("*************error");
                }
            }
        }


и несмотря на synchronized почему-то некоторые потоки проходили до result.add(reader.readLine());
и, соответственно, при их прерывании с помощью  interrupt() отрабатывали не так, как
предполагалось (для полной остановки программы требовалось ввести еще 1 строку и возникали
2 IOException (видимо от оставшихся двух потоков))


  Т.е. у меня вопрос: Почему реализация с synchronized (reader)
  отрабатывает некорректно, а с if (reader.ready()) - корректно ... P.S.
  в задаче требуется реализовать метод run() класса ReaderThread,
  остальное по условию задачи трогать нельзя ...


//пример входных данных и результата

10  //сколько строк считать
1  //первая строка для считывания потоками
2
3
4
5
6
7
8
9
10       //ввод заканчивается здесь
//вывод начинается отсюда
#1:[2, 4, 5, 8, 10]  // то что удалось считать 1-ому потоку
#2:[6] // то что удалось считать 2-ому потоку
#3:[1, 3, 7, 9] // то что удалось считать 3-ему потоку

    


Ответы

Ответ 1



На самом деле сейчас у вас в коде есть проблемы. В 9 случаях из 10 ваш код сработает корректно, но смотрите, что будет в последнем: while ( !Thread.currentThread().isInterrupted()){ try { if (reader.ready()){ // 1 result.add(reader.readLine()); //2 Solution.countReadStrings.incrementAndGet(); } }catch (IOException e){ System.out.println("*************error"); } Метод reader.ready() проверяет, можно ли прочесть что-то из буффера ввода, и если там что-то есть, запускается блокирующий метод reader.readLie(), т.е. он блокирует выполнение потока, пока не увидит в буфере символ окончания ввода (\n, \r). Теперь смотрите, у вас три потока используют один BufferedReader, это означает, что если будет долгая задержка от пользователя (такая, что все три потока успеют отработать и пройдут //1), то все три потока зависнут на блокирующем методе //2. Как только пользователь нажмет энтер, первый проснувшийся поток сможет прочитать строку и сохранит её себе. Заметьте, что оставшиеся два потока так и будут висеть на //2. Это неправильно с той точки зрения, что свою проверку они уже прошли, но строку так и не прочитали. Поэтому возможна нередкая ситуация, когда вы вводите 10 строку из 10, вам распечатывается результат, но программа не завершается, а ждет еще ввода. Это всё потому, что есть висящие потоки на строке //2. После этого главный поток вызывает reader.close(); и висящие потоки ловят IOException, после чего выполнение программы завершается. Теперь посмотрим на ваш изначальный вариант: while ( !Thread.currentThread().isInterrupted()){ try { synchronized (reader){ // 1 result.add(reader.readLine()); // 2 Solution.countReadStrings.incrementAndGet(); } }catch (IOException e){ System.out.println("*************error"); } } Этот вариант использует синхронизацию, таким образом никто не может войти внутрь блока кода //1, если его уже занял другой поток. И на первый взгляд это правильно, только почему-то это не работает. И вот почему. Потоки, которые не могут войти в синхронизированный блок, ожидают освобождения монитора. Теперь представьте ситуацию, вы ввели 9 строк и хотите ввести последнюю. У нас три потока: один стоит на блокирующем методе //2, два остальных стоят перед синхронизированным блоком //1. Вы вводите 10 строку, поток выходит из блока и освобождает его другим. Главный поток выходит из цикла while и начинает интерраптить ваши потоки. Теперь просыпается любой блокированный поток, у него выставлен статус interrupted, но он стоит на //1 и НЕ проверяет свой статус. Таким образом, проснувшись, он заходит в свободный уже блок кода и повисает на блокирующем методе //2. Главный поток закрывает буффер, вы что-то вводите, получаете эксепшн, поток закрывается и просыпается последний поток, который точно так же стоит на строчке //1 и НЕ проверяя свой статус, заходит в освободившийся блок кода. Там повторяется то же самое. Этим объясняется то, что вам нужно еще два раза ввести данные, чтобы программа завершилась. Решение, которое мне здесь видится, это скомбинировать два этих способа: synchronized (reader){ if (reader.ready()) { result.add(reader.readLine()); Test.countReadStrings.incrementAndGet(); } } Таким образом, проснувшиеся потоки зайдя в синхронизированный блок кода не будут зависать на вводе, поскольку условие if (reader.ready()) должно будет возвращать false.

пятница, 13 декабря 2019 г.

Многопоточность в java, почему порядок вывода результата разнится?

#java #многопоточность #synchronized


Допустим есть такой код. Его результат:[Синхронизация]
          [в Java]
          [ полезная]   . Если объект Caller запускать без отдельного потока (т.е
без "extends Thread" и без метода "start()"), то результат будет в другом порядке-
[Синхронизация]  [полезная] [в Java]   Почему так происходит? Прошу дать развернутый ответ.

class CallMe{
    void call(String msg){
        System.out.print("[" + msg );
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("]");
    }
}

class Caller extends Thread{
    String msg;
    CallMe target;

    Caller(CallMe target, String msg){
        this.target = target;
        this.msg = msg;
        start();
    }

    public void run(){
        synchronized (target) {
            target.call(msg);
        }
    }
}

public class Main {
    public static void main(String[] args) {
        CallMe callMe = new CallMe();
        new Caller(callMe, "Синхронизация");
        new Caller(callMe, "в Java");
        new Caller(callMe, " полезная");
    }
}

    


Ответы

Ответ 1



Поставьте задержку на 200 миллисекунд в методе run(), а в методе main() добавьте цикл операций на 100 - получите еще больше вариантов. Потому что невозможно предсказать какой поток войдет в блок synchronized первым, даже если вы их запускаете последовательно. Для синхронизации потоков используются классы Semaphore CountDownLatch CyclicBarrier Lock у них различные цели и методы. Это зависит от конкретной задачи. Semaphore - как правило служит для ограничения количества потоков при работе с ресурсами. Доступ ограничивается с помощью счетчика, если его значение больше нуля, то доступ потоку разрешается, а значение счетчика уменьшается. Если счетчик равен нулю, то текущий поток блокируется, пока другой поток не освободит ресурс. Для получения доступа используется метод acquire(), для освобождения – release(). public class SemaphoreDemo { public static void main(String[] args) { Semaphore smp = new Semaphore(2); for (int i = 0; i < 5; i++) { final int w = i; new Thread(() -> { try { System.out.println("Поток" + w + " перед семафором"); smp.acquire(); System.out.println("Поток" + w + " получил доступ к ресурсу"); Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } finally { System.out.println("Поток" + w + " освободил ресурс"); smp.release(); } }).start(); } } } Результат работы: Поток 0 перед семафором Поток 0 получил доступ к ресурсу Поток 2 перед семафором Поток 2 получил доступ к ресурсу Поток 1 перед семафором Поток 4 перед семафором Поток 3 перед семафором Поток 2 освободил ресурс Поток 1 получил доступ к ресурсу Поток 0 освободил ресурс Поток 4 получил доступ к ресурсу Поток 4 освободил ресурс Поток 1 освободил ресурс Поток 3 получил доступ к ресурсу Поток 3 освободил ресурс Одновременно семафор могут захватить(с помощью метода acquire()) только два потока, остальные потоки становятся в очередь, пока один из потоков не освободит семафор методом release(). CountDownLatch - Позволяет потоку ожидать до тех пор, пока не завершится определенное количество операций, выполняющихся в других потоках, в режим ожидания поток заходит с помощью метода await(). Количество требуемых операций задается при создании объекта, после чего уменьшается при вызове метода countDown(). Как только счетчик доходит до 0, ожидающий поток разблокируется. public class SimpleCDL { public static void main(String[] args) { // задаем кол-во потоков final int THREADS_COUNT = 6; // задаем значение счетчика final CountDownLatch cdl = new CountDownLatch(THREADS_COUNT); System.out.println("Начинаем"); for (int i = 0; i < THREADS_COUNT; i++) { final int w = i; new Thread(() -> { try { // считаем что выполнение задачи занимает ~1 сек Thread.sleep(500 + (int)(500 * Math.random())); // как только задача выполнена, уменьшаем счетчик cdl.countDown(); System.out.println("Поток #" + w + " - готов"); } catch (InterruptedException e) { e.printStackTrace(); } }).start(); } try { // ждем пока счетчик не сбросится в ноль, пока это не // произойдет, будем стоять на этой строке cdl.await(); } catch (InterruptedException e) { e.printStackTrace(); } // как только все потоки выполнили свои задачи - пишем сообщение System.out.println("Работа завершена"); } } Результат работы: Начинаем Поток #1 - готов Поток #0 - готов Поток #3 - готов Поток #2 - готов Поток #4 - готов Поток #5 - готов Работа завершена Основной поток создает 6 потоков и ждет пока каждый из этих потоков закончит приготовление к работе. CyclicBarrier - используется для синхронизации заданного количества потоков в одной точке. При вызове метода await() поток блокируется. Как только заданное количество потоков заблокировалось, с них одновременно снимается блокировка. public class BarrierExample { public static void main(String[] args) { CyclicBarrier cb = new CyclicBarrier(3); for (int i = 0; i < 3; i++) { final int w = i; new Thread(() -> { try { System.out.println("Поток " + w + " готовится"); Thread.sleep(100 + (int) (3000 * Math.random())); System.out.println("Поток " + w + " готов"); cb.await(); System.out.println("Поток " + w + " запустился"); } catch (Exception e) { e.printStackTrace(); } }).start(); } } } Результат работы: Поток 0 готовится Поток 1 готовится Поток 2 готовится Поток 2 готов Поток 0 готов Поток 1 готов Поток 1 запустился Поток 2 запустился Поток 0 запустился Несмотря на то, что какие-то потоки закончили подготовку раньше, какие-то позже, стартовали они в одно и то же время, так как блокировка снимается одновременно. Lock - Интерфейс. Представляет собой продвинутый механизм синхронизации потоков, который предоставляет большую гибкость чем блоки синхронизации. Поскольку Lock это интерфейс, для работы с ним необходимо создать объект одной из его реализаций. Lock lock = new ReentrantLock(); lock.lock(); lock.unlock(); В начале создается объект типа Lock, после чего у этого объекта вызывается метод lock() и он захватывается. Попытка другого потока вызвать у этого же объекта метод lock() приведет к блокировке этого потока, пока поток удерживающий объект типа Lock не освободит его с помощью метода unlock(). После вызова метода unlock() объект типа Lock освобождается, и другие потоки могут его захватить Основные отличия между Lock и синхронизированными блоками: Синхронизированные блоки не гарантируют сохранность порядка обращения потоков к критической секции; Выйти из синхронизированного блока по времени ожидания(timeout) не получится; Синхронизированные блоки должны полностью содержаться в одном методе, в то время как Lock может быть захвачен в одном методе, а освобожден в другом.

среда, 12 июня 2019 г.

Вопрос про доступность полей и методов объекта synchronized блока

Сдавал финальный экзамен на intuit.ru. Курс по java, на следующий вопрос про synchronized-блок получил, что ответ неверный. Никак не могу понять почему. Может кто подскажет, спасибо.

В самом курсе черным по белому написано, что и к полям, и к методам объекта, на который вешается lock, можно без проблем обращаться другим потокам. (Ну, видимо, кроме synchronized методов, ибо они пытаются повесить lock на объект, из которого вызываются, а он уже залочен по условиям задачи. Но сути это не меняет, к полям тоже можно обращаться, т.е. вариант 4 не подходит...)
Может что с 2003 года поменялось... (курс старый)


Ответ

Тут три варианта: либо вопрос поставлен некорректно, либо ответы сформулированы не совсем ясно (особенно третий, который можно трактовать в сторону правильного), либо в тесте ошибка и правильного варианта ответа нет.
Если один поток начал исполнение synchronized-блока, указав ссылку на некий объект, то другой поток сможет обратиться к полю этого объекта и так же сможет обратиться к методу этого объекта (если метод не синхронизированный).
Синхронизация по объекту накладывает ограничение на другие блоки синхронизации по этому же объекту и на вызов синхронизированных методов. На доступ к полям и не синхронизированным методам synchronized-блок не влияет.
И пример:
public class Foo { public int mValue = 5;
public String bar() { return "bar"; } }

public class Main { private static Foo sFoo;
public static void main(String[] args) { sFoo = new Foo();
new Thread(() -> f()).start();
new Thread(() -> { System.out.println("Second thread: start"); System.out.println("Member: " + sFoo.mValue); System.out.println("Method: " + sFoo.bar()); System.out.println("Second thread: end"); }).start();
}
private static void f() { synchronized (sFoo) { try { System.out.println("First thread: start"); Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("First thread: end"); } } }
Вывод на консоль:
First thread: start Second thread: start Member: 5 Method: bar Second thread: end First thread: end

четверг, 2 мая 2019 г.

Не понятна логика возникновения DeadLock

public void transferMoney(Account fromAccount, Account toAccount, Amount amount) throws InsufficientFundsException { synchronized (fromAccount) { synchronized (toAccount) { if (fromAccount.getBalance().compareTo(amount) < 0) throw new InsufficientFundsException(); else { fromAccount.debit(amount); toAccount.credit(amount); } } } }
Описание: Если со счета A на счет B перевести x денег, а со счета B на счет A – y, то при неудачном стечении обстоятельств, транзакция 1 займет монитор счета A, транзакция 2 займет монитор счета B. Результат – взаимная блокировка.
Я никак не могу понять: Как же второй трэд доберётся до второго synchronized, если первый synchronized уже был заблокирован первым трэдом? Пересмотрел уже кучу лекций и перечитал про synchronized.


Ответ

Он не будет заблокирован. synchronized воспользуется intrinsic lock того объекта, который будет указан в самом synchronized, поэтому два потока могут войти во внешний synchronized при том условии, что они используют два разных объекта: де-факто, synchronized(lockObject) - это гарантия использования объекта только одним потоком, но не гарантия исполнения кода максимум одним потоком в любой момент времени. Для достижения дедлока в этом случае достаточно, чтобы ни один из потоков не смог войти во внутренний synchronized - и это условие выполняется, если используются те же два аккаунта, но в противоположных потоках:
Thread 1 | Thread 2 -------------------|------------------- обычное состояние | обычное состояние взял лок объекта А | взял лок объекта Б ждет лок объекта Б | ждет лок объекта А
В этом случае прогресс невозможен, потому что для освобождения ресурса требуется прогресс любого из потоков, а это возможно только в случае освобождения ресурса.

вторник, 12 марта 2019 г.

Можно ли вызвать НЕ synchronized метод “заблокированного” объекта?

Есть 2 потока, один из них начал выполнение synchronized метода, внутри которого применяется Thread.sleep(5000). Может ли другой поток использовать другие НЕСИНХРОНИЗИРОВАННЫЕ методы этого "заблокированного" объекта? Как я понимаю, "блокируется" не сам объект, а лишь synchronized участи кода (либо методы).


Ответ

Да, можно совершать данное действие. Почему бы просто не попробовать? Вы правильно понимаете.

понедельник, 17 декабря 2018 г.

Многопоточность в java, почему порядок вывода результата разнится?

Допустим есть такой код. Его результат:[Синхронизация] [в Java] [ полезная] . Если объект Caller запускать без отдельного потока (т.е без "extends Thread" и без метода "start()"), то результат будет в другом порядке- [Синхронизация] [полезная] [в Java] Почему так происходит? Прошу дать развернутый ответ.
class CallMe{ void call(String msg){ System.out.print("[" + msg ); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("]"); } }
class Caller extends Thread{ String msg; CallMe target;
Caller(CallMe target, String msg){ this.target = target; this.msg = msg; start(); }
public void run(){ synchronized (target) { target.call(msg); } } }
public class Main { public static void main(String[] args) { CallMe callMe = new CallMe(); new Caller(callMe, "Синхронизация"); new Caller(callMe, "в Java"); new Caller(callMe, " полезная"); } }


Ответ

Поставьте задержку на 200 миллисекунд в методе run(), а в методе main() добавьте цикл операций на 100 - получите еще больше вариантов.
Потому что невозможно предсказать какой поток войдет в блок synchronized первым, даже если вы их запускаете последовательно.
Для синхронизации потоков используются классы
Semaphore CountDownLatch CyclicBarrier Lock
у них различные цели и методы. Это зависит от конкретной задачи.

Semaphore - как правило служит для ограничения количества потоков при работе с ресурсами. Доступ ограничивается с помощью счетчика, если его значение больше нуля, то доступ потоку разрешается, а значение счетчика уменьшается. Если счетчик равен нулю, то текущий поток блокируется, пока другой поток не освободит ресурс. Для получения доступа используется метод acquire(), для освобождения – release()
public class SemaphoreDemo { public static void main(String[] args) { Semaphore smp = new Semaphore(2); for (int i = 0; i < 5; i++) { final int w = i; new Thread(() -> { try { System.out.println("Поток" + w + " перед семафором"); smp.acquire(); System.out.println("Поток" + w + " получил доступ к ресурсу"); Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } finally { System.out.println("Поток" + w + " освободил ресурс"); smp.release(); } }).start(); } } }
Результат работы:
Поток 0 перед семафором Поток 0 получил доступ к ресурсу Поток 2 перед семафором Поток 2 получил доступ к ресурсу Поток 1 перед семафором Поток 4 перед семафором Поток 3 перед семафором Поток 2 освободил ресурс Поток 1 получил доступ к ресурсу Поток 0 освободил ресурс Поток 4 получил доступ к ресурсу Поток 4 освободил ресурс Поток 1 освободил ресурс Поток 3 получил доступ к ресурсу Поток 3 освободил ресурс
Одновременно семафор могут захватить(с помощью метода acquire()) только два потока, остальные потоки становятся в очередь, пока один из потоков не освободит семафор методом release()

CountDownLatch - Позволяет потоку ожидать до тех пор, пока не завершится определенное количество операций, выполняющихся в других потоках, в режим ожидания поток заходит с помощью метода await(). Количество требуемых операций задается при создании объекта, после чего уменьшается при вызове метода countDown(). Как только счетчик доходит до 0, ожидающий поток разблокируется.
public class SimpleCDL { public static void main(String[] args) { // задаем кол-во потоков final int THREADS_COUNT = 6; // задаем значение счетчика final CountDownLatch cdl = new CountDownLatch(THREADS_COUNT); System.out.println("Начинаем"); for (int i = 0; i < THREADS_COUNT; i++) { final int w = i; new Thread(() -> { try { // считаем что выполнение задачи занимает ~1 сек Thread.sleep(500 + (int)(500 * Math.random())); // как только задача выполнена, уменьшаем счетчик cdl.countDown(); System.out.println("Поток #" + w + " - готов"); } catch (InterruptedException e) { e.printStackTrace(); } }).start(); } try { // ждем пока счетчик не сбросится в ноль, пока это не // произойдет, будем стоять на этой строке cdl.await(); } catch (InterruptedException e) { e.printStackTrace(); } // как только все потоки выполнили свои задачи - пишем сообщение System.out.println("Работа завершена"); } }
Результат работы:
Начинаем Поток #1 - готов Поток #0 - готов Поток #3 - готов Поток #2 - готов Поток #4 - готов Поток #5 - готов Работа завершена
Основной поток создает 6 потоков и ждет пока каждый из этих потоков закончит приготовление к работе.

CyclicBarrier - используется для синхронизации заданного количества потоков в одной точке. При вызове метода await() поток блокируется. Как только заданное количество потоков заблокировалось, с них одновременно снимается блокировка.
public class BarrierExample { public static void main(String[] args) { CyclicBarrier cb = new CyclicBarrier(3); for (int i = 0; i < 3; i++) { final int w = i; new Thread(() -> { try { System.out.println("Поток " + w + " готовится"); Thread.sleep(100 + (int) (3000 * Math.random())); System.out.println("Поток " + w + " готов"); cb.await(); System.out.println("Поток " + w + " запустился"); } catch (Exception e) { e.printStackTrace(); } }).start(); } } }
Результат работы:
Поток 0 готовится Поток 1 готовится Поток 2 готовится Поток 2 готов Поток 0 готов Поток 1 готов Поток 1 запустился Поток 2 запустился Поток 0 запустился Результат работы: Поток 0 готовится Поток 1 готовится Поток 2 готовится Поток 2 готов Поток 0 готов Поток 1 готов Поток 1 запустился Поток 2 запустился Поток 0 запустился
Несмотря на то, что какие-то потоки закончили подготовку раньше, какие-то позже, стартовали они в одно и то же время, так как блокировка снимается одновременно. Несмотря на то, что какие-то потоки закончили подготовку раньше, какие-то позже, стартовали они в одно и то же время, так как блокировка снимается одновременно.

Lock - Интерфейс. Представляет собой продвинутый механизм синхронизации потоков, который предоставляет большую гибкость чем блоки синхронизации. Поскольку Lock это интерфейс, для работы с ним необходимо создать объект одной из его реализаций.
Lock lock = new ReentrantLock(); lock.lock(); lock.unlock();
В начале создается объект типа Lock, после чего у этого объекта вызывается метод lock() и он захватывается. Попытка другого потока вызвать у этого же объекта метод lock() приведет к блокировке этого потока, пока поток удерживающий объект lock не освободит его с помощью метода unlock(). После вызова метода unlock() объект типа Lock освобождается и другие потоки могут его захватить Основные отличия между Lock и синхронизированными блоками:
Синхронизированные блоки не гарантируют сохранность порядка обращения потоков к критической секции; Выйти из синхронизированного блока по времени ожидания(timeout) не получится; Синхронизированные блоки должны полностью содержаться в одном методе, в то время как Lock может быть захвачен в одном метода, а освобожден в другом.

вторник, 20 ноября 2018 г.

Зачем в getInstance synchronized?

Зачем в этом getInstance synchronized делать ?
Это класс-синглтон для работы с сетью с использованием retrofit.
public static NetworkWorker getInstance(){ if (networkWorker == null){ synchronized (NetworkWorker.class) { if (networkWorker == null) { networkWorker = new NetworkWorker(); } } } return networkWorker; }


Ответ

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

понедельник, 29 октября 2018 г.

(Java) Как правильно использовать BufferedReader одновременно несколькими потоками (Thread)

При изучении потоков (Threads) столкнулся с задачей. Нужно, используя один BufferedReader и три потока (Thread), считать строки с клавиатуры и сохранить в ArrayList значения считанных потоком строк. (3 экземпляра класса-наследника Thread и также 3 экземпляра переменной класса Array ArrayList)
Методом проб и ошибок вывел рабочий код:
public class Solution { public static volatile AtomicInteger countReadStrings = new AtomicInteger(0); public static volatile BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
public static void main(String[] args) throws IOException { //read count of strings int count = Integer.parseInt(reader.readLine());
//init threads ReaderThread consolReader1 = new ReaderThread(); ReaderThread consolReader2 = new ReaderThread(); ReaderThread consolReader3 = new ReaderThread();
consolReader1.start(); consolReader2.start(); consolReader3.start();
while (count > countReadStrings.get()) { }
consolReader1.interrupt(); consolReader2.interrupt(); consolReader3.interrupt(); System.out.println("#1:" + consolReader1); System.out.println("#2:" + consolReader2); System.out.println("#3:" + consolReader3);
reader.close(); }
public static class ReaderThread extends Thread { private List result = new ArrayList();
public void run() { while ( !Thread.currentThread().isInterrupted()){ try { if (reader.ready()){ // <- корректно отрабатывает result.add(reader.readLine()); Solution.countReadStrings.incrementAndGet(); } }catch (IOException e){ System.out.println("*************error"); } } }
@Override public String toString() { return result.toString(); } } }
Но ранее пробовал такой метод run() класса ReaderThread
public void run() { //add your code here - добавьте код тут while ( !Thread.currentThread().isInterrupted()){ try { synchronized (reader){ // <- НЕкорректно отрабатывает result.add(reader.readLine()); Solution.countReadStrings.incrementAndGet(); } }catch (IOException e){ System.out.println("*************error"); } } }
и несмотря на synchronized почему-то некоторые потоки проходили до result.add(reader.readLine()); и, соответственно, при их прерывании с помощью interrupt() отрабатывали не так, как предполагалось (для полной остановки программы требовалось ввести еще 1 строку и возникали 2 IOException (видимо от оставшихся двух потоков))
Т.е. у меня вопрос: Почему реализация с synchronized (reader) отрабатывает некорректно, а с if (reader.ready()) - корректно ... P.S. в задаче требуется реализовать метод run() класса ReaderThread, остальное по условию задачи трогать нельзя ...
//пример входных данных и результата
10 //сколько строк считать 1 //первая строка для считывания потоками 2 3 4 5 6 7 8 9 10 //ввод заканчивается здесь //вывод начинается отсюда #1:[2, 4, 5, 8, 10] // то что удалось считать 1-ому потоку #2:[6] // то что удалось считать 2-ому потоку #3:[1, 3, 7, 9] // то что удалось считать 3-ему потоку


Ответ

На самом деле сейчас у вас в коде есть проблемы. В 9 случаях из 10 ваш код сработает корректно, но смотрите, что будет в последнем:
while ( !Thread.currentThread().isInterrupted()){ try { if (reader.ready()){ // 1 result.add(reader.readLine()); //2 Solution.countReadStrings.incrementAndGet(); } }catch (IOException e){ System.out.println("*************error"); }
Метод reader.ready() проверяет, можно ли прочесть что-то из буффера ввода, и если там что-то есть, запускается блокирующий метод reader.readLie(), т.е. он блокирует выполнение потока, пока не увидит в буфере символ окончания ввода (
,
). Теперь смотрите, у вас три потока используют один BufferedReader, это означает, что если будет долгая задержка от пользователя (такая, что все три потока успеют отработать и пройдут //1), то все три потока зависнут на блокирующем методе //2. Как только пользователь нажмет энтер, первый проснувшийся поток сможет прочитать строку и сохранит её себе. Заметьте, что оставшиеся два потока так и будут висеть на //2. Это неправильно с той точки зрения, что свою проверку они уже прошли, но строку так и не прочитали. Поэтому возможна нередкая ситуация, когда вы вводите 10 строку из 10, вам распечатывается результат, но программа не завершается, а ждет еще ввода. Это всё потому, что есть висящие потоки на строке //2. После этого главный поток вызывает reader.close(); и висящие потоки ловят IOException, после чего выполнение программы завершается.
Теперь посмотрим на ваш изначальный вариант:
while ( !Thread.currentThread().isInterrupted()){ try { synchronized (reader){ // 1 result.add(reader.readLine()); // 2 Solution.countReadStrings.incrementAndGet(); } }catch (IOException e){ System.out.println("*************error"); } }
Этот вариант использует синхронизацию, таким образом никто не может войти внутрь блока кода //1, если его уже занял другой поток. И на первый взгляд это правильно, только почему-то это не работает. И вот почему. Потоки, которые не могут войти в синхронизированный блок, ожидают освобождения монитора. Теперь представьте ситуацию, вы ввели 9 строк и хотите ввести последнюю. У нас три потока: один стоит на блокирующем методе //2, два остальных стоят перед синхронизированным блоком //1. Вы вводите 10 строку, поток выходит из блока и освобождает его другим. Главный поток выходит из цикла while и начинает интерраптить ваши потоки. Теперь просыпается любой блокированный поток, у него выставлен статус interrupted, но он стоит на //1 и НЕ проверяет свой статус. Таким образом, проснувшись, он заходит в свободный уже блок кода и повисает на блокирующем методе //2. Главный поток закрывает буффер, вы что-то вводите, получаете эксепшн, поток закрывается и просыпается последний поток, который точно так же стоит на строчке //1 и НЕ проверяя свой статус, заходит в освободившийся блок кода. Там повторяется то же самое. Этим объясняется то, что вам нужно еще два раза ввести данные, чтобы программа завершилась. Решение, которое мне здесь видится, это скомбинировать два этих способа:
synchronized (reader){ if (reader.ready()) { result.add(reader.readLine()); Test.countReadStrings.incrementAndGet(); } }
Таким образом, проснувшиеся потоки зайдя в синхронизированный блок кода не будут зависать на вводе, поскольку условие if (reader.ready()) должно будет возвращать false