#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()
onthis
(который сам по себе,Thread-0
).
Thread-1
выполняется:
- он проверяет
lock
- обнаруживает, что это
true
- и вызывает
wait()
onthis
(который сам по себе,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:
Извините, но это дает отрицательное значение, потому что оно не дает никаких гарантий и только создает иллюзию безопасности. С этим связано несколько проблем:
- Переменная является энергонезависимой, поскольку такие изменения из одного потока могут быть невидимы для другого.
- Ваша блокировка не является атомарной. Следовательно, что может произойти, так это то, что оба потока могут передавать цикл while, а затем оба присваивают логическому значению значение true.
notifyAll()
пробуждает все потоки, ожидающие экземпляра объекта. И поскольку каждый поток ожидает самого себя в качестве монитора, они не просыпаются.
В Java есть множество хороших механизмов блокировки — попробуйте те, что есть в пакете concurrency (хотя в вашем случае достаточно простого синхронизированного блока). Если вы настаиваете на использовании логического значения для блокировки, вам необходимо:
- Создайте статический финал
AtomicBoolean
в качестве вашего сигнализатора (non final подойдет, если вы не измените его и гарантируете видимость) - Используйте
compareAndSet
в качестве условия цикла / блокировки, чтобы доступ был атомарным. 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. Эх, разве Джейми уже не предоставил почти идентичный фрагмент кода вместе с некоторыми пояснениями (а также кратко упомянул в моем ответе)? Кроме того, вам не нужно уведомление о блокировке.