자바 Thread_6
락킹
멀티스레드 애플리케이션을 제작할 때 Fine-Grained(세밀한 락킹), Coarse-Grained(성긴 락킹) 을 선택할 수 있음.
Coarse-Grained
- 단일 락킹만 신경쓰면 된다
- 공유 리소스에 액세스할 떄마다 해당 락킹만 사용하면 됨
- 메서드에 synchronized 키워드로 해결 가능함
=> 전체 리소스를 잠그는것으로 간단하지만 성능이 중요하다면 최적의 방법은 아님 !
- Fine-Grained
- 모든 리소스에 개별 락킹을 생성하는 것과 같음
병렬성은 키우고 경쟁을 낮춤
=> 데드락에 걸릴 문제가 생김
데드락
- 모두가 움직이려고 하는데, 다른 사람이 움직인다고 기다리느라 나도 못움직이는 현상 (교착현상) => 뻗는 현상
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
// 스레드A가 B를 락을 잡고있는와중에
// 스레드B가 A를 락을 잡고있어서 서로 접근을 못하고 대기를 해서 데드락이 걸린 상황
public static void main(String[] args) throws InterruptedException {
Intersection intersection = new Intersection();
Thread trainA = new Thread(new TrainA(intersection));
Thread trainB = new Thread(new TrainB(intersection));
trainA.start();
trainB.start();
}
public static class TrainB implements Runnable {
private Intersection intersection;
private Random random = new Random();
public TrainB(Intersection intersection) {
this.intersection = intersection;
}
@Override
public void run() {
while (true) {
long sleepTime = random.nextInt(5);
try {
Thread.sleep(sleepTime);
} catch (Exception e){
}
intersection.takeRoadB();
}
}
}
public static class TrainA implements Runnable {
private Intersection intersection;
private Random random = new Random();
public TrainA(Intersection intersection) {
this.intersection = intersection;
}
@Override
public void run() {
while (true) {
long sleepTime = random.nextInt(5);
try {
Thread.sleep(sleepTime);
} catch (Exception e){
}
intersection.takeRoadA();
}
}
}
public static class Intersection {
private Object roadA = new Object();
private Object roadB = new Object();
public void takeRoadA() {
synchronized (roadA) {
System.out.println("RoadA" + Thread.currentThread().getName());
synchronized (roadB) {
System.out.println("");
try {
Thread.sleep(1);
} catch (Exception e) {
}
}
}
}
public void takeRoadB() {
synchronized (roadB) {
System.out.println("Road B" + Thread.currentThread().getName());
synchronized (roadA) {
try {
Thread.sleep(1);
} catch (Exception e){
}
}
}
}
}
해결방안
- 상호 배제 - 한 번에 한 스레드만 단독으로 리소스에 액세스함
- 점유와 대기 - 최소 하나의 스레드가 리소스를 점유하며 다른 리소스에 대기
- 비선점 할당 - 스레드가 사용 완료할 때까지 리소스를 사용할 수 없는 것, 다른 스레드의 리소스를 뺏을 수 없고 다 쓸때까지 기다려야함
- 순환대기 - 데드락에 빠진 스레드에서 발견할 수 있는데 서로 다른 스레드가 대기함
=> 위 네가지 조건이 갖춰지면 데드락이 매우 잘 걸린다 그래서 네가지 조건중 하나라도 충족하지않게 만들어주는것으로 해결할 수 있다
- 마지막 조건인 순환 대기를 예방하는 것으로 동일한 순서로 공유 리소스를 잠그고 모든 코드에 해당 순서를 유지하기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
public static void main(String[] args) throws InterruptedException {
Intersection intersection = new Intersection();
Thread trainA = new Thread(new TrainA(intersection));
Thread trainB = new Thread(new TrainB(intersection));
trainA.start();
trainB.start();
}
public static class TrainB implements Runnable {
private Intersection intersection;
private Random random = new Random();
public TrainB(Intersection intersection) {
this.intersection = intersection;
}
@Override
public void run() {
while (true) {
long sleepTime = random.nextInt(5);
try {
Thread.sleep(sleepTime);
} catch (Exception e){
}
intersection.takeRoadB();
}
}
}
public static class TrainA implements Runnable {
private Intersection intersection;
private Random random = new Random();
public TrainA(Intersection intersection) {
this.intersection = intersection;
}
@Override
public void run() {
while (true) {
long sleepTime = random.nextInt(5);
try {
Thread.sleep(sleepTime);
} catch (Exception e){
}
intersection.takeRoadA();
}
}
}
public static class Intersection {
private Object roadA = new Object();
private Object roadB = new Object();
public void takeRoadA() {
synchronized (roadA) {
System.out.println("RoadA" + Thread.currentThread().getName());
synchronized (roadB) {
System.out.println("");
try {
Thread.sleep(1);
} catch (Exception e) {
}
}
}
}
public void takeRoadB() {
synchronized (roadA) {
System.out.println("Road A" + Thread.currentThread().getName());
synchronized (roadA) {
try {
Thread.sleep(1);
} catch (Exception e){
}
}
}
}
}
- 락킹 순서를 통일하는것도 데드락을 예방할 수 있는 방법
- 프로그램이 단순하고 작을수록 훨씬 용이하고 적합할 수 있음
- 복잡한 프로그램은 다양한 곳에 락킹이 걸려있고 순서를 유지하는게 어려운 단점이 존재
대체방법
- 데드락을 감시하는 장치를 만들수있음
- 매우 낮은 레벨에서 주기적으로 특정 레지스터 상태를 체크해서 응답하지 않는 스레드가 있다면 데드락이라 판단하고 재가동함
- 락킹을 처리하기전에 다른 스레드에 의해 락킹이 처리되었는지 확인해서 스레드가 중단되는걸 막은 try lock이라는 방법이 존재
- synchronized는 중단된 스레드를 인터럽트할 수 없고, trylock도 사용할 수 없음 => 순서를 지키는게 불가능할때 적용
출처 - https://kmooc.udemy.com/course/java-multi-threading/ (Java 멀티스레딩, 병행성 및 성능 최적화 - 전문가 되기 )
This post is licensed under CC BY 4.0 by the author.