Skip to content

Commit f5bbd92

Browse files
committed
post: [9주차_노경민] 8. Thread & Lock
1 parent 13e4f90 commit f5bbd92

File tree

1 file changed

+238
-0
lines changed

1 file changed

+238
-0
lines changed
Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
---
2+
title: 🦊 8. Thread & Lock
3+
author: gengminy
4+
date: 2023-12-17 12:00:00 +09:00
5+
categories: [코딩 인터뷰 대학, 8. Thread & Lock]
6+
tags: [코딩 인터뷰 대학, 운영체제, 9주차, 노경민]
7+
render_with_liquid: false
8+
math: true
9+
---
10+
11+
# 자바의 스레드
12+
13+
자바의 모든 스레드는 `java.lang.Thread` 클래스 객체에 의해 생성되고 제어된다.
14+
15+
자바에서 스레드를 구현하는 방법은 두 가지가 있다.
16+
17+
- `java.lang.Runnable` 인터페이스를 구현하기
18+
- `java.lang.Thread` 클래스를 상속받기
19+
20+
## Runnable 인터페이스를 구현하는 방법
21+
22+
```java
23+
public interface Runnable {
24+
void run();
25+
}
26+
```
27+
28+
- Runnable 인터페이스를 구현하는 클래스를 만든다
29+
- Thread 타입의 객체 생성자에 Runnable 객체를 인자로 넘긴다
30+
- Thread 객체의 start() 메서드를 호출한다
31+
32+
```java
33+
public static void main(String[] args) {
34+
RunnableThreadExample instance = new RunnableThreadExample();
35+
Thread thread = new Thread(instance);
36+
thread.start();
37+
38+
while (instance.count != 5) {
39+
try {
40+
Thread.sleep(25);
41+
} catch (InterruptedException exc) {
42+
exc.printStackTrace();
43+
}
44+
}
45+
}
46+
```
47+
48+
## Thread 클래스 상속
49+
50+
```java
51+
public class ThreadExample extends Thread {
52+
int count = 0;
53+
54+
public void run() {
55+
...
56+
}
57+
}
58+
59+
public class ExampleB {
60+
public static void main(String args[]) {
61+
ThreadExample instance = new ThreadExample();
62+
instance.start();
63+
64+
while (instance.count != 5) {
65+
try {
66+
Thread.sleep(250);
67+
} catch (InterruptedException exc) {
68+
exc.printStackTrace();
69+
}
70+
}
71+
}
72+
}
73+
```
74+
75+
인터페이스를 구현하는 대신 Thread 클래스를 상속받아 인스턴스화하고 start() 를 직접 호출하게 된다.
76+
77+
### Thread 상속 vs. Runnable 인터페이스 구현
78+
79+
스레드를 생성할 때 Runnable 인터페이스를 구현하는 것이 더 선호된다.
80+
81+
- 자바는 다중 상속을 지원하지 않기 때문에, Thread 를 상속하게 되면 하위 클래스는 다른 클래스를 상속할 수 없다. 반만 Runnable 인터페이스를 구현하는 클래스는 다른 클래스를 상속받을 수 있다.
82+
- Thread 클래스의 모든 것을 상속받는 것이 너무 부담스러운 경우에 Runnable 을 구현하는 편이 낫다.
83+
84+
## 동기화와 락
85+
86+
어떤 프로세스 안에서 생성된 스레드들은 같은 메모리 공간을 공유한다.
87+
88+
두 스레드가 같은 자원을 동시에 변경하는 경우에는 문제가 된다.
89+
90+
자바는 공유 자원에 대한 접근을 제어하기 위한 동기화 방법을 제공한다.
91+
92+
### 동기화된 메서드
93+
94+
`synchronized` 키워드를 사용할 때 공유 자원에 대한 접근을 제어한다.
95+
96+
메서드 또는 특정 코드 블록에 적용할 수 있다.
97+
98+
```java
99+
public class MyObject {
100+
public synchronized void foo(String name) {
101+
...
102+
}
103+
}
104+
```
105+
106+
두 개의 MyClass 인스턴스가 foo 를 동시에 호출할 수 있을까?
107+
108+
같은 MyObject 인스턴스를 가리키고 있다면 동시 호출이 불가능하다.
109+
110+
다른 인스턴스를 가지고 있다면 가능하다.
111+
112+
```java
113+
MyObject obj1 = new MyObject();
114+
MyObject obj1 = new MyObject();
115+
MyClass thread1 = new MyClass(obj1, "1");
116+
MyClass thread1 = new MyClass(obj2, "2");
117+
thread1.start();
118+
thread2.start();
119+
// 동시 호출 OK
120+
121+
MyObject obj = new MyObject();
122+
MyClass thread1 = new MyClass(obj, "1");
123+
MyClass thread1 = new MyClass(obj, "2");
124+
thread1.start();
125+
thread2.start();
126+
// 동시 호출 X, 다른 하나는 기다리고 있어야 한다.
127+
```
128+
129+
정적 메서드는 클래스 락에 의해 동기화된다.
130+
131+
같은 클래스에 있는 동기화된 정적 메서드는 두 스레드에서 동시에 실행될 수 없다.
132+
133+
하나는 foo 를 호출하고, 다른 하나는 bar을 호출한다고 해도 말이다.
134+
135+
```java
136+
public class MyClass extends Thread {
137+
...
138+
public void run() {
139+
if (name.equlas("1")) MyObject.foo(name);
140+
else if (name.equals("2")) MyObject.bar(name);
141+
}
142+
}
143+
144+
public class MyObejct {
145+
public static synchronized void foo(String name) { ... }
146+
public static synchronized void bar(String name) { ... }
147+
}
148+
```
149+
150+
```java
151+
// 실행결과
152+
Thread 1.foo(): starting
153+
154+
Thread 1.bar(): ending
155+
156+
Thread 2.foo(): starting
157+
158+
Thread 2.bar(): ending
159+
```
160+
161+
## 동기화된 블록
162+
163+
특정한 코드 블록을 동기화할 수 있다. 이는 메서드를 동기화하는 것과 아주 비슷하게 동작한다.
164+
165+
```java
166+
public class MyClass extends Thread {
167+
...
168+
public void run() {
169+
myObj.foo(name);
170+
}
171+
}
172+
173+
public class MyObject {
174+
...
175+
public void foo(String name) {
176+
synchronized(this) {
177+
...
178+
}
179+
}
180+
}
181+
182+
```
183+
184+
MyObject 인스턴스 하나당 하나의 스레드만이 synchronized 블록 안의 코드를 실행할 수 있다.
185+
186+
#
187+
188+
좀 더 세밀하게 동기화를 제어하고 싶다면 락(Lock)을 사용한다.
189+
190+
락(또는 Monitor)을 공유 자원에 붙이면 해당 자원에 대한 접근을 동기화할 수 있다.
191+
192+
스레드가 해당 자원에 접근하려면 그 자원에 붙어있는 락을 획득해야 한다.
193+
194+
어떤 자원이 프로그램 내의 이곳저곳에서 사용되지만 한 번에 한 스레드만 사용하도록 만들고자 할 때 주로 락을 이용한다.
195+
196+
```java
197+
public class LockedATM {
198+
private Lock lock;
199+
private int balance = 100;
200+
201+
public LockedATM() {
202+
lock = new ReentrantLock();
203+
}
204+
205+
public int withdraw(int value) {
206+
lock.lock();
207+
...
208+
lock.unlock();
209+
return value;
210+
}
211+
212+
public int deposit(int value) {
213+
lock.lock();
214+
...
215+
lock.unlock();
216+
return value;
217+
}
218+
}
219+
```
220+
221+
락을 사용하면 공유된 자원이 예기치 않게 변경되는 일을 막을 수 있다.
222+
223+
## 교착상태와 교착상태 방지
224+
225+
교착상태(deadlock)란, 스레드1은 스레드2가 들고 있는 객체의 락이 풀리기를 기다리고 있고 스레드2는 스레드1이 들고있는 객체의 락이 풀리기를 기다리는 상황이다.
226+
227+
모든 스레드가 락이 풀리기를 기다리고 있기 때문에 무한 대기 상태에 빠진다.
228+
229+
교착상태가 발생하려면 4가지 조건을 모두 충족되어야 한다.
230+
231+
- 상호배제(mutual exclusion): 한 번에 한 프로세스만 공유 자원을 사용할 수 있다.
232+
- 들고 기다리기(hold and wait): 공유 자원에 대한 접근 권한을 가진 프로세스가 접근 권한을 양보하지 않은 상태에서 다른 자원에 대한 접근 권한을 요구할 수 있다.
233+
- 선취 불가능(preemption): 한 프로세스가 다른 프로세스의 자원 접근 권한을 강제로 취소할 수 없다.
234+
- 대기 상태의 사이클(circular wait): 두 개 이상의 프로세스가 자원 접근을 기다리는데, 그 관계에 사이클이 존재한다.
235+
236+
교착상태를 방지하기 위해 이 조건 가운데 하나를 제거하면 된다.
237+
238+
대부분의 교착상태 방지 알고리즘은 4번 조건을 막는데 초점이 맞춰져있다.

0 commit comments

Comments
 (0)