могу ли я использовать статическую логическую переменную в качестве блокировки для синхронизированного потока?

#java #multithreading #synchronized

#java #многопоточность #синхронизированный

Вопрос:

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

 public class Main {

    public static void main(String[] args){

        //MyObject lock = new MyObject();
        Thread1 t1 = new Thread1(100,'#');
        Thread1 t2 = new Thread1(100,'*');

        t1.start();
        t2.start();
    }
}
  

 public class Thread1 extends Thread {

    public static boolean lock;
    int myNum;
    char myChar;

    public Thread1(int num, char c){
        myNum = num;
        myChar = c;
        lock = false;
    }

    public synchronized void run(){
        System.out.println(getName()   " is runing");
        while (Thread1.lock == true){
            System.out.println(getName()   " is waiting");
            try{wait();}
            catch(InterruptedException e){}
        }
        Thread1.lock = true;
        for(int i = 0; i<myNum; i  ){
            if(i%10==0)
                System.out.println("");
            System.out.print(myChar);
        }
        Thread1.lock = false;
        notifyAll();
    }
}
  

вероятно, я делаю это неправильно, потому что только один поток печатает «mychar», а другой поток просто переходит в wait() и не просыпается, когда я выполняю notifyAll().
Я подумал, что это может быть хорошим способом использовать статическую логическую переменную для всего класса, а не изменять ее каждый раз и вызывать notifyAll(), чтобы проверить этот флаг в других объектах…

пример вывода:

 Thread-0 is runing

Thread-1 is runing
Thread-1 is waiting
##########
##########
##########
##########
##########
##########
##########
##########
##########
##########
  

Комментарии:

1. Можно также использовать цикл вместо потоков, если вы хотите запускать потоки последовательно.

2. Классическим примером изучения многопоточности является реализация потоков производителя / потребителя. Производители производят материал и уведомляют потребителей; тем временем потребители ждут после потребления материала.

Ответ №1:

Почему это не работает

notify() и wait() работать с «монитором» объекта, к которому они вызываются. В вашем случае это this конкретный экземпляр Thread1 , который выполняется.

Итак, когда Thread-0 выполняется:

  • он проверяет lock
  • находит ее false
  • он запускает основной код
  • он вызывает notifyAll() on this (который сам по себе, Thread-0 ).

Thread-1 выполняется:

  • он проверяет lock
  • обнаруживает, что это true
  • и вызывает wait() on this (который сам по себе, Thread-1 )

Поскольку Thread-0 вызовы notifyAll() на this (который сам по себе) и Thread-1 вызовы wait() на this (который является самим), Thread-1 ожидает на другом мониторе, чем Thread-0 уведомление, поэтому он никогда не выпускается.

Решение

Если вы планируете просто запустить этот код последовательно, используйте это:

 public class Thread1 extends Thread {

    private static final Object lock = new Object();

    public void run(){
        // non-sequential code
        System.out.println(getName()   " is running");
        synchronized (lock) {
            // this code will be run sequentially by one thread at a time
        }
        // non-sequential code
    }
}
  

Комментарии:

1. почему «публичная статическая окончательная блокировка объекта» должна быть окончательной?

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

3. @YuvalLevy кроме того, он должен быть закрытым, потому что он не используется вне Thread1 класса. (Отредактировано)

Ответ №2:

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

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

В Java есть множество хороших механизмов блокировки — попробуйте те, что есть в пакете concurrency (хотя в вашем случае достаточно простого синхронизированного блока). Если вы настаиваете на использовании логического значения для блокировки, вам необходимо:

  1. Создайте статический финал AtomicBoolean в качестве вашего сигнализатора (non final подойдет, если вы не измените его и гарантируете видимость)
  2. Используйте compareAndSet в качестве условия цикла / блокировки, чтобы доступ был атомарным.
  3. Wait() на AtomicBoolean , чтобы потоки совместно использовали монитор, на котором они ожидают (и notifyAll() на нем также).

Комментарии:

1. не могли бы вы подробнее объяснить проблему номер 3? Если поток ожидает самого себя в качестве монитора, почему он не проснулся?

2. Потоки ожидают экземпляры объекта. В этом случае у вас есть 2 экземпляра: t1 и t2 . Поток 1 ожидает t1 . Поток 2 работает, и по завершении уведомляет все, что ожидает объект, против которого вызывается уведомление, т. Е. t2 . Однако ничего не ожидает t2 ( t1 и t2 являются разными объектами), следовательно, ничего не запускается.

Ответ №3:

Вы можете использовать более простой способ синхронизации потоков.

 class Thread1 extends Thread {

    private static final Object lock = new Object();
    int myNum;
    char myChar;

    public Thread1(int num, char c){
        myNum = num;
        myChar = c;
    }

    public void run(){
        System.out.println(getName()   " is runing");
        synchronized(lock) {
            for(int i = 0; i<myNum; i  ){
                if(i%10==0)
                    System.out.println("");
                System.out.print(myChar);
            }
        }
    }
}
  

Комментарии:

1. Эх, разве Джейми уже не предоставил почти идентичный фрагмент кода вместе с некоторыми пояснениями (а также кратко упомянул в моем ответе)? Кроме того, вам не нужно уведомление о блокировке.