Skip to content

Commit

Permalink
post: [9주차_노경민] 8. Thread & Lock
Browse files Browse the repository at this point in the history
  • Loading branch information
gengminy committed Dec 17, 2023
1 parent 13e4f90 commit f5bbd92
Showing 1 changed file with 238 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
---
title: 🦊 8. Thread & Lock
author: gengminy
date: 2023-12-17 12:00:00 +09:00
categories: [코딩 인터뷰 대학, 8. Thread & Lock]
tags: [코딩 인터뷰 대학, 운영체제, 9주차, 노경민]
render_with_liquid: false
math: true
---

# 자바의 스레드

자바의 모든 스레드는 `java.lang.Thread` 클래스 객체에 의해 생성되고 제어된다.

자바에서 스레드를 구현하는 방법은 두 가지가 있다.

- `java.lang.Runnable` 인터페이스를 구현하기
- `java.lang.Thread` 클래스를 상속받기

## Runnable 인터페이스를 구현하는 방법

```java
public interface Runnable {
void run();
}
```

- Runnable 인터페이스를 구현하는 클래스를 만든다
- Thread 타입의 객체 생성자에 Runnable 객체를 인자로 넘긴다
- Thread 객체의 start() 메서드를 호출한다

```java
public static void main(String[] args) {
RunnableThreadExample instance = new RunnableThreadExample();
Thread thread = new Thread(instance);
thread.start();

while (instance.count != 5) {
try {
Thread.sleep(25);
} catch (InterruptedException exc) {
exc.printStackTrace();
}
}
}
```

## Thread 클래스 상속

```java
public class ThreadExample extends Thread {
int count = 0;

public void run() {
...
}
}

public class ExampleB {
public static void main(String args[]) {
ThreadExample instance = new ThreadExample();
instance.start();

while (instance.count != 5) {
try {
Thread.sleep(250);
} catch (InterruptedException exc) {
exc.printStackTrace();
}
}
}
}
```

인터페이스를 구현하는 대신 Thread 클래스를 상속받아 인스턴스화하고 start() 를 직접 호출하게 된다.

### Thread 상속 vs. Runnable 인터페이스 구현

스레드를 생성할 때 Runnable 인터페이스를 구현하는 것이 더 선호된다.

- 자바는 다중 상속을 지원하지 않기 때문에, Thread 를 상속하게 되면 하위 클래스는 다른 클래스를 상속할 수 없다. 반만 Runnable 인터페이스를 구현하는 클래스는 다른 클래스를 상속받을 수 있다.
- Thread 클래스의 모든 것을 상속받는 것이 너무 부담스러운 경우에 Runnable 을 구현하는 편이 낫다.

## 동기화와 락

어떤 프로세스 안에서 생성된 스레드들은 같은 메모리 공간을 공유한다.

두 스레드가 같은 자원을 동시에 변경하는 경우에는 문제가 된다.

자바는 공유 자원에 대한 접근을 제어하기 위한 동기화 방법을 제공한다.

### 동기화된 메서드

`synchronized` 키워드를 사용할 때 공유 자원에 대한 접근을 제어한다.

메서드 또는 특정 코드 블록에 적용할 수 있다.

```java
public class MyObject {
public synchronized void foo(String name) {
...
}
}
```

두 개의 MyClass 인스턴스가 foo 를 동시에 호출할 수 있을까?

같은 MyObject 인스턴스를 가리키고 있다면 동시 호출이 불가능하다.

다른 인스턴스를 가지고 있다면 가능하다.

```java
MyObject obj1 = new MyObject();
MyObject obj1 = new MyObject();
MyClass thread1 = new MyClass(obj1, "1");
MyClass thread1 = new MyClass(obj2, "2");
thread1.start();
thread2.start();
// 동시 호출 OK

MyObject obj = new MyObject();
MyClass thread1 = new MyClass(obj, "1");
MyClass thread1 = new MyClass(obj, "2");
thread1.start();
thread2.start();
// 동시 호출 X, 다른 하나는 기다리고 있어야 한다.
```

정적 메서드는 클래스 락에 의해 동기화된다.

같은 클래스에 있는 동기화된 정적 메서드는 두 스레드에서 동시에 실행될 수 없다.

하나는 foo 를 호출하고, 다른 하나는 bar을 호출한다고 해도 말이다.

```java
public class MyClass extends Thread {
...
public void run() {
if (name.equlas("1")) MyObject.foo(name);
else if (name.equals("2")) MyObject.bar(name);
}
}

public class MyObejct {
public static synchronized void foo(String name) { ... }
public static synchronized void bar(String name) { ... }
}
```

```java
// 실행결과
Thread 1.foo(): starting

Thread 1.bar(): ending

Thread 2.foo(): starting

Thread 2.bar(): ending
```

## 동기화된 블록

특정한 코드 블록을 동기화할 수 있다. 이는 메서드를 동기화하는 것과 아주 비슷하게 동작한다.

```java
public class MyClass extends Thread {
...
public void run() {
myObj.foo(name);
}
}

public class MyObject {
...
public void foo(String name) {
synchronized(this) {
...
}
}
}

```

MyObject 인스턴스 하나당 하나의 스레드만이 synchronized 블록 안의 코드를 실행할 수 있다.

#

좀 더 세밀하게 동기화를 제어하고 싶다면 락(Lock)을 사용한다.

락(또는 Monitor)을 공유 자원에 붙이면 해당 자원에 대한 접근을 동기화할 수 있다.

스레드가 해당 자원에 접근하려면 그 자원에 붙어있는 락을 획득해야 한다.

어떤 자원이 프로그램 내의 이곳저곳에서 사용되지만 한 번에 한 스레드만 사용하도록 만들고자 할 때 주로 락을 이용한다.

```java
public class LockedATM {
private Lock lock;
private int balance = 100;

public LockedATM() {
lock = new ReentrantLock();
}

public int withdraw(int value) {
lock.lock();
...
lock.unlock();
return value;
}

public int deposit(int value) {
lock.lock();
...
lock.unlock();
return value;
}
}
```

락을 사용하면 공유된 자원이 예기치 않게 변경되는 일을 막을 수 있다.

## 교착상태와 교착상태 방지

교착상태(deadlock)란, 스레드1은 스레드2가 들고 있는 객체의 락이 풀리기를 기다리고 있고 스레드2는 스레드1이 들고있는 객체의 락이 풀리기를 기다리는 상황이다.

모든 스레드가 락이 풀리기를 기다리고 있기 때문에 무한 대기 상태에 빠진다.

교착상태가 발생하려면 4가지 조건을 모두 충족되어야 한다.

- 상호배제(mutual exclusion): 한 번에 한 프로세스만 공유 자원을 사용할 수 있다.
- 들고 기다리기(hold and wait): 공유 자원에 대한 접근 권한을 가진 프로세스가 접근 권한을 양보하지 않은 상태에서 다른 자원에 대한 접근 권한을 요구할 수 있다.
- 선취 불가능(preemption): 한 프로세스가 다른 프로세스의 자원 접근 권한을 강제로 취소할 수 없다.
- 대기 상태의 사이클(circular wait): 두 개 이상의 프로세스가 자원 접근을 기다리는데, 그 관계에 사이클이 존재한다.

교착상태를 방지하기 위해 이 조건 가운데 하나를 제거하면 된다.

대부분의 교착상태 방지 알고리즘은 4번 조건을 막는데 초점이 맞춰져있다.

0 comments on commit f5bbd92

Please sign in to comment.