아주 간단한 Java 코드로 이상한 문제가 있습니다.간단한 Java 코드에서 로깅 문으로 교착 상태가 발생합니까?
나는 프로그램의 라이센스를 제어하고 동적으로 제어 할 수있는 새로운 클래스를 요청할 수 있습니다 (예 : 새로운 라이센스는 주문형으로 발행 될 수 있음). LicenseManager
은 물론 스레드 잠금이 필요하므로 큰 잠금 장치를 사용합니다. 그러나 사과를 제어하는 데 사용하는 잠금 장치가 하나 더 있습니다.이 교착 상태에 어떻게 든 관련이 있다고는하지만 실제로 어떻게 볼 수는 없습니다. 이 프로그램은 또한 엔터프라이즈 로깅 기능을 지원하는 로깅 처리기를 설치합니다.
그냥 코드 LicenseManager
을 보면 그 중 하나의 문제는 볼 수 없습니다.
import java.util.logging.*;
public class Main {
public static final LicenseManager licenseManager = new LicenseManager();
static class LicenseManager {
private static final Logger logger =
Logger.getLogger(LicenseManager.class.getName());
class InsufficientApplesException extends RuntimeException {};
class InsufficientPiesException extends RuntimeException {};
private int apples = 10;
private int pies = 10;
private boolean enterpriseLoggingEnabled = true;
// Apples have high contention; so they get their own lock.
private final Object appleLock = new Object();
void useApple() {
checkExpired();
synchronized (appleLock) {
logger.info("Using apple. Apples left: " + apples);
if (apples == 0) throw new InsufficientApplesException();
apples--;
}
}
/* Examples. There are lots of other operations like this
* on LicenseManager. We don't have time to prove that
* they are commutative, so we just use the main object lock
* around them all. The exception is when we find one with high
* contention, for example apples. */
synchronized void usePear() {checkExpired(); /*...*/}
synchronized void checkExpired() {}
synchronized void usePie() {
checkExpired();
logger.info("Using pie. Pies left: " + pies);
if (pies == 0) throw new InsufficientPiesException();
boolean reallyCanUsePie = true; // do expensive pie computation
if (reallyCanUsePie) {
useApple(); /* using a pie requires an apple.
* TODO: stop putting apples in the pumpkin pie */
pies--;
}
}
synchronized boolean isEnterpriseLoggingEnabled() {
return enterpriseLoggingEnabled;
}
}
public static void main(String[] args) {
// Install enterprise log handler on the root logger
Logger.getLogger("").addHandler(new Handler() {
@Override
public void publish(LogRecord lr) {
if (licenseManager.isEnterpriseLoggingEnabled())
System.out.println("ENTERPRISE ALERT! ["
+ lr.getLevel() + "] " + lr.getMessage());
}
@Override public void flush() {}
@Override public void close() throws SecurityException {}
});
// Simulate fat user
new Thread() {
@Override
public void run() {
while (true) {
licenseManager.usePie();
}
}
}.start();
// Simulate fat albeit healthy user
while (true) {
licenseManager.useApple();
}
}
}
그리고 내가 그것을 실행하면
이$ java Main
Apr 25, 2013 3:23:19 PM Main$LicenseManager useApple
INFO: Using apple. Apples left: 10
Apr 25, 2013 3:23:19 PM Main$LicenseManager usePie
INFO: Using pie. Pies left: 10
ENTERPRISE ALERT! [INFO] Using pie. Pies left: 10
당신은 모두 원형 먹는 사람을 기대는 파이의 부족으로 사망, 대신 두 스레드 교착 상태.
흥미롭게도 로킹 라인을 useApple
(logger.info("Using apple. Apples left: " + apples);
)으로 제거하면 교착 상태가 발생하지 않습니다 (모든 파이가 사용되기 전에 모든 사과가 사라 졌기 때문에 "파이 사용"으로 스팸 된 로그를 보지 못합니다).) :
$ java Main
Exception in thread "main" Main$LicenseManager$InsufficientApplesException
at Main$LicenseManager.useApple(Main.java:24)
at Main.main(Main.java:79)
Apr 25, 2013 3:23:42 PM Main$LicenseManager usePie
INFO: Using pie. Pies left: 10
ENTERPRISE ALERT! [INFO] Using pie. Pies left: 10
Exception in thread "Thread-1" Main$LicenseManager$InsufficientApplesException
at Main$LicenseManager.useApple(Main.java:24)
at Main$LicenseManager.usePie(Main.java:43)
at Main$2.run(Main.java:72)
왜? 로깅을 제거하지 않고 어떻게 해결할 수 있습니까? 메인 스레드는 (하나의 사과를 먹는)를 appleLock
을 소유하고 synchronized isEnterpriseLoggingEnabled()
방법과 자식 스레드 (파이를 먹는 일)이 licenseManager
객체를 소유하고 usePie()
의 내부 useApple()
호출에 액세스하려고하기 때문에
정말 교착 상태에 빠졌습니까? 스택 트레이스에서 교착 상태를 본 적이 있습니까? – Gray
'while (true)'에 약간의'Thread.sleep()'을 삽입 해 보았습니까? – Fildor
FYI : 로거가 프로그램의 동기화 패턴을 변경하는 커버 아래에 동기화 된 'PrintStream'을 가지고있을 수 있습니다. – Gray