인쇄문이 없으면 루프가 다른 스레드에 의해 변경된 값을 볼 수 없습니다.
코드에는 다른 스레드에서 어떤 상태가 변경되기를 기다리는 루프가 있습니다.다른 스레드는 동작하지만 루프는 변경된 값을 인식하지 않습니다.그것은 영원히 기다린다.하지만, 제가 이걸 넣었을 때System.out.println
갑자기 작동한다구요! 왜요?
다음은 내 코드의 예입니다.
class MyHouse {
boolean pizzaArrived = false;
void eatPizza() {
while (pizzaArrived == false) {
//System.out.println("waiting");
}
System.out.println("That was delicious!");
}
void deliverPizza() {
pizzaArrived = true;
}
}
while 루프가 실행 중일 때deliverPizza()
다른 스레드에서 설정하기 위해pizzaArrived
변수.하지만 루프는 내가 코멘트를 해제했을 때만 작동합니다.System.out.println("waiting");
진술.무슨 일이야?
JVM은 다른 스레드가 다음 스레드를 변경하지 않는다고 가정할 수 있습니다.pizzaArrived
변수입니다.다른 말로 하면, 이 기어는pizzaArrived == false
루프 외부 테스트, 최적화:
while (pizzaArrived == false) {}
다음과 같이 입력합니다.
if (pizzaArrived == false) while (true) {}
무한 루프입니다.
한 스레드의 변경 내용을 다른 스레드에 표시하려면 항상 스레드 간에 동기화를 추가해야 합니다.이를 위한 가장 간단한 방법은 공유 변수를volatile
:
volatile boolean pizzaArrived = false;
변수 만들기volatile
는 서로 다른 스레드가 서로 변경된 영향을 볼 수 있음을 보증합니다.이로 인해 JVM이 다음 값을 캐싱할 수 없게 됩니다.pizzaArrived
또는 루프 외부에 테스트를 호이스트합니다.대신 매번 실제 변수 값을 읽어야 합니다.
(좀 더 형식적으로 말하면,volatile
는 변수에 대한 액세스 간에 operations-before 관계를 만듭니다.이것은 피자를 배달하기 전에 스레드가 했던 다른 모든 작업이 피자를 받는 스레드에도 보여진다는 것을 의미합니다, 비록 다른 변화들이 피자에 영향을 주지 않을지라도.volatile
변수)
동기화된 메서드는 주로 상호 제외(동시에 발생하는 두 가지 일을 방지)를 구현하기 위해 사용되지만 다음과 같은 모든 부작용이 있습니다.volatile
변수를 읽고 쓸 때 변수를 사용하는 것도 다른 스레드에 변경 내용을 표시할 수 있는 다른 방법입니다.
class MyHouse {
boolean pizzaArrived = false;
void eatPizza() {
while (getPizzaArrived() == false) {}
System.out.println("That was delicious!");
}
synchronized boolean getPizzaArrived() {
return pizzaArrived;
}
synchronized void deliverPizza() {
pizzaArrived = true;
}
}
인쇄문의 효과
System.out
는 입니다.PrintStream
물건.의 방법PrintStream
는 다음과 같이 동기화됩니다.
public void println(String x) {
synchronized (this) {
print(x);
newLine();
}
}
동기화에 의해,pizzaArrived
루프가 진행되는 동안 캐시됩니다.엄밀히 말하면, 변수에 대한 변경을 확실히 표시하기 위해서, 양쪽의 스레드는 같은 오브젝트로 동기 할 필요가 있습니다.(예를 들면, 콜)println
설정 후pizzaArrived
그리고 읽기 전에 다시 전화한다.pizzaArrived
맞다고 생각합니다.)특정 개체에서 하나의 스레드만 동기화되는 경우 JVM은 이를 무시할 수 있습니다.실제로 JVM은 다른 스레드가 호출하지 않는다는 것을 증명할 만큼 충분히 스마트하지 않습니다.println
설정 후pizzaArrived
아마 그럴 거라고 추측하고 있어요따라서 루프 중에 변수를 캐시할 수 없습니다.System.out.println
그렇기 때문에 올바른 수정은 아니지만 인쇄문이 있는 경우 이와 같은 루프가 작동합니다.
사용.System.out
루프가 작동하지 않는 이유를 디버깅하려고 할 때 가장 자주 발견되는 것이 이 효과를 일으키는 유일한 방법은 아닙니다.
더 큰 문제는
while (pizzaArrived == false) {}
busy-wait 루프입니다.큰일 났다!대기하는 동안 CPU를 독점하여 다른 응용 프로그램의 속도가 느려지고 시스템의 전력 사용량, 온도 및 팬 속도가 증가합니다.루프 스레드가 대기하는 동안 sleeve 상태가 되어 CPU를 독점하지 않는 것이 이상적입니다.
이를 위한 몇 가지 방법은 다음과 같습니다.
wait/notify 사용
낮은 수준의 솔루션은 다음과 같은 wait/notify 방법을 사용하는 것입니다.
class MyHouse {
boolean pizzaArrived = false;
void eatPizza() {
synchronized (this) {
while (!pizzaArrived) {
try {
this.wait();
} catch (InterruptedException e) {}
}
}
System.out.println("That was delicious!");
}
void deliverPizza() {
synchronized (this) {
pizzaArrived = true;
this.notifyAll();
}
}
}
이 버전의 코드에서는 루프 스레드가 를 호출하여 스레드를 sleep 상태로 만듭니다.sleep 중에는 CPU 사이클을 사용하지 않습니다.두 번째 스레드는 변수를 설정한 후 해당 개체에서 대기하고 있던 모든 스레드를 웨이크업하도록 호출합니다.이것은 피자 배달원이 초인종을 누르도록 하는 것과 같아서, 당신은 문 앞에 어색하게 서 있는 대신 앉아서 쉴 수 있다.
오브젝트에서 wait/notify를 호출할 때는 해당 오브젝트의 동기화 잠금을 유지해야 합니다.이것이 위의 코드의 동작입니다.두 스레드가 같은 오브젝트를 사용하는 한 원하는 오브젝트를 사용할 수 있습니다.this
(의 인스턴스MyHouse
일반적으로 2개의 스레드는 같은 오브젝트의 동기화된 블록(동기 목적의 일부)을 동시에 입력할 수 없습니다.단, 스레드가 내부에 있을 때 동기 잠금을 일시적으로 해제하기 때문에 여기서 동작합니다.wait()
방법.
블로킹 큐
A는 생산자/소비자 큐를 구현하기 위해 사용됩니다."소비자"는 큐의 맨 앞부분에서 아이템을 가져가고, "프로듀서"는 뒤에 아이템을 밀어 넣습니다.예:
class MyHouse {
final BlockingQueue<Object> queue = new LinkedBlockingQueue<>();
void eatFood() throws InterruptedException {
// take next item from the queue (sleeps while waiting)
Object food = queue.take();
// and do something with it
System.out.println("Eating: " + food);
}
void deliverPizza() throws InterruptedException {
// in producer threads, we push items on to the queue.
// if there is space in the queue we can return immediately;
// the consumer thread(s) will get to it later
queue.put("A delicious pizza");
}
}
주의:put
그리고.take
방법BlockingQueue
던질 수 있다InterruptedException
s. 이것은 처리해야 하는 체크 예외입니다.위의 코드에서는, 간단하게 하기 위해서, 예외가 리트루닝 됩니다.메서드 내의 예외를 검출하여 put 또는 take 콜을 재시도하여 성공 여부를 확인할 수 있습니다.그 추악한 점 하나 빼고BlockingQueue
매우 사용하기 쉽습니다.
여기에서는 다른 동기화가 필요하지 않습니다.BlockingQueue
는 항목을 큐에 넣기 전에 스레드가 수행한 모든 작업을 스레드에서 볼 수 있도록 합니다.
실행자
Executor
는 기성품이나 다름없다.BlockingQueue
작업을 실행합니다.예:
// A "SingleThreadExecutor" has one work thread and an unlimited queue
ExecutorService executor = Executors.newSingleThreadExecutor();
Runnable eatPizza = () -> { System.out.println("Eating a delicious pizza"); };
Runnable cleanUp = () -> { System.out.println("Cleaning up the house"); };
// we submit tasks which will be executed on the work thread
executor.execute(eatPizza);
executor.execute(cleanUp);
// we continue immediately without needing to wait for the tasks to finish
상세한 것에 대하여는, 및 의 메뉴얼을 참조해 주세요.
이벤트 처리
사용자가 UI에서 무언가를 클릭할 때까지 기다리는 동안 루프가 발생하는 것은 잘못된 것입니다.대신 UI 툴킷의 이벤트 처리 기능을 사용하십시오.Swing의 예:
JLabel label = new JLabel();
JButton button = new JButton("Click me");
button.addActionListener((ActionEvent e) -> {
// This event listener is run when the button is clicked.
// We don't need to loop while waiting.
label.setText("Button was clicked");
});
이벤트 핸들러는 이벤트디스패치 스레드에서 실행되므로 이벤트핸들러에서 장시간 작업을 하면 작업이 완료될 때까지 UI와의 다른 상호작용이 차단됩니다.저속 동작은 새로운 스레드에서 시작하거나 위의 방법 중 하나를 사용하여 대기 스레드로 디스패치할 수 있습니다(대기/통지,BlockingQueue
, 또는Executor
를 사용할 수도 있습니다.이러한 설계로 백그라운드워커 스레드가 자동으로 제공됩니다.
JLabel label = new JLabel();
JButton button = new JButton("Calculate answer");
// Add a click listener for the button
button.addActionListener((ActionEvent e) -> {
// Defines MyWorker as a SwingWorker whose result type is String:
class MyWorker extends SwingWorker<String,Void> {
@Override
public String doInBackground() throws Exception {
// This method is called on a background thread.
// You can do long work here without blocking the UI.
// This is just an example:
Thread.sleep(5000);
return "Answer is 42";
}
@Override
protected void done() {
// This method is called on the Swing thread once the work is done
String result;
try {
result = get();
} catch (Exception e) {
throw new RuntimeException(e);
}
label.setText(result); // will display "Answer is 42"
}
}
// Start the worker
new MyWorker().execute();
});
타이머
정기적인 액션을 수행하려면 를 사용합니다.이 방법은 자신의 타이밍 루프를 쓰는 것보다 사용하기 쉽고 시작과 정지가 용이합니다.이 데모에서는 현재 시간을 초당 1회 인쇄합니다.
Timer timer = new Timer();
TimerTask task = new TimerTask() {
@Override
public void run() {
System.out.println(System.currentTimeMillis());
}
};
timer.scheduleAtFixedRate(task, 0, 1000);
각각java.util.Timer
스케줄된 실행을 위해 사용되는 자체 백그라운드 스레드가 있습니다.TimerTask
s. 당연히 스레드는 작업 간에 sleep 상태가 되기 때문에 CPU를 독점하지 않습니다.
Swing 코드에는 유사한 가 있지만 Swing 스레드에서 리스너를 실행하므로 수동으로 스레드를 전환하지 않고도 Swing 컴포넌트와 안전하게 대화할 수 있습니다.
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Timer timer = new Timer(1000, (ActionEvent e) -> {
frame.setTitle(String.valueOf(System.currentTimeMillis()));
});
timer.setRepeats(true);
timer.start();
frame.setVisible(true);
Other ways
If you are writing multithreaded code, it is worth exploring the classes in these packages to see what is available:
And also see the Concurrency section of the Java tutorials. Multithreading is complicated, but there is lots of help available!
ReferenceURL : https://stackoverflow.com/questions/25425130/loop-doesnt-see-value-changed-by-other-thread-without-a-print-statement
'programing' 카테고리의 다른 글
리소스 예약 패턴의 잠금 및 분리 (0) | 2023.01.28 |
---|---|
mysqldb python 인터페이스를 설치할 때 mysql_config를 찾을 수 없습니다. (0) | 2023.01.28 |
SyntaxError: 모듈 외부에서 Import 문을 사용할 수 없습니다. (0) | 2023.01.28 |
문자열의 모든 줄 바꿈을 어떻게 치환합니까?문자열의 모든 줄 바꿈을 어떻게 치환합니까?요소?요소? (0) | 2023.01.28 |
언제 java.lang을 잡을지.에러? (0) | 2023.01.28 |