프로그램 개발/미분류

[코딩 인터뷰]지식 기반 문제 - 스레드와 락

(ㅇㅅㅎ) 2023. 1. 13. 14:21
728x90
반응형

 

 

[ 자바의 스레드 ]

 자바의 모든 스레드는 java.lang.Thread 클래스 객체에 의해 생성되고 제어됩니다. 자바에서 스레드를 구현하는 방법은 Runnable 인터페이스를 구현하는 법Thread 클래스를 상속받는 법으로 2가지가 있습니다.

⭐ 주 스레드(main thread) : 독립적인 응용 프로그램이 실행될 때, main() 메서드를 실행하기 위한 하나의 사용자 스레드

 

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

✔️ 구조

public interface Runnable{
    void run();
}

✔️ 과정

  • Runnable 인터페이스를 구현하는 클래스를 만듭니다.
    → 이 클래스의 객체는 Runnable의 객체가 됩니다.
  • Thread 타입의 객체를 만들 때, Thread의 생성자에 Runnable 객체를 인자로 넘깁니다.
    → 이 Thread 객체는 이제 run() 메서드를 구현하는 Runnable객체를 소유하게 됩니다.
  • 이전 단계에서 생성한 Thread 객체의 start() 메서드를 호출합니다.

 

👀 Thread 클래스 상속

✔️ 구조

public class ThreadExample extends Thread{
    public void run(){
        Thread.sleep(500);
        System.out.println("Thread!");
    }
}

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

✔️ 과정

  • Thread 클래스를 상속받아서 run() 메서드를 오버라이드하여 구성합니다.
  • 인스턴스 자체에서 start()를 직접 호출합니다.

 

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

 자바는 다중 상속을 지원하지 않기 때문에 Thread 클래스를 상속하게 되면 하위 클래스는 다른 클래스를 상속할 수 없습니다. 그리고 Thread 클래스의 모든 것을 상속받는 것이 너무 부담되기 때문에 Runnable 인터페이스를 구현하는 것을 선호합니다.

 

 

[ Synchronized와 Lock ]

 프로세스 안에서 생성된 스레드들은 같은 메모리 공간을 공유합니다. 스레드들이 같은 자원을 동시에 변경하는 경우 문제가 생기므로 자바는 공유 자원에 대한 접근을 제어하기 위한 동기화(synchronization) 방법을 제공합니다.

 

👀 동기화된 메서드

 ✔️ synchronized 키워드

 공유 자원에 대한 접근을 제어 및 여러 스레드가 같은 객체를 동시에 실행하는 것도 방지합니다. 메서드 및 특정한 코드 블록에 적용 가능합니다.

public class Test extends Thread{
    private String name;
    private TestObject testobj;
    
    public TestObject(TestObject obj, String n){
        name = n;
        testobj = obj;
    }
    public void run(){
        testobj.test(name);
    }
}
public class TestObject{
    public synchronized void test(String name){ ... }
}

/* 가능 */
TestObject obj1 = new TestObject();
TestObject obj2 = new TestObject();
Test thread1 = new Test(obj1, "1");
Test thread2 = new Test(obj2, "2");
thread1.start();
thread2.start();

/* 불가능 */
TestObject obj = new TestObject();
Test thread1 = new Test(obj, "1");
Test thread2 = new Test(obj, "2");
thread1.start();
thread2.start();

⭐ 정적 메서드는 클래스 락(class lock)에 의해 동기화됩니다. 같은 클래스에 있는 동기화된 정적 메서드는 두 스레드에서 동시에 실행될 수 없습니다.

public class Test extends Thread{
    ...
    public void run(){
        if (name.equals("1")) testobj.test(name);
        else testobj.test1(name);
    }
}
public class TestObject{
    public static synchronized void test(String name){ ... }
    public static synchronized void test1(String name){ ... }
}

/* 동시에는 불가능 test 끝나고 test1 시작함*/
TestObject obj = new TestObject();
Test thread1 = new Test(obj, "1");
Test thread2 = new Test(obj, "2");
thread1.start();
thread2.start();

 

👀 동기화된 블록

 메서드를 동기화하는 것과 아주 비슷하게 동작합니다.

public class Test extends Thread{
    ...
    public void run(){
        testobj.test(name);
    }
}
public class TestObject{
    public void test(String name){
        synchronized(this){ ... }
    }
}

TestObject obj = new TestObject();
Test thread1 = new Test(obj, "1");
thread1.start();

 

👀 Lock

 좀 더 세밀하게 동기화를 제어하고 싶을 때 사용합니다. 공유 자원에 붙이면 해당 자원에 대한 접근을 동기화할 수 있습니다. 스레드가 해당 자원에 접근하려면 락을 얻어야 하므로 해당 자원은 한 번에 한 스레드만 사용할 수 있습니다.

예제) ATM 입금 및 출금

public class ATM{
    private Lock lock;
    ...
    public class ATM(){
        lock = new ReentrantLock();
    }
    
    public int withdraw(int value){
        lock.lock();
        ...
        lock.unlock();
        ...
    }
    
    public int deposit(int value){
        lock.lock();
        ...
        lock.unlock();
        ...
    }
}

 

 

[ 교착상태와 교착상태 방지 ]

👀 교착상태

: 첫 번째 스레드는 두 번째 스레드가 들고 있는 객체의 락이 풀리기를 기다리고 있고, 두 번째 스레드 역시 첫 번째 스레드가 들고 있는 객체의 락이 풀리기를 기다리는 상황입니다.

 

👀 교착상태에 빠지는 4가지 조건

  1. 상호 배제 : 한 번에 한 프로세스만 공유 자원을 사용합니다. 공유 자원에 대한 접근 권한이 제한됩니다. 자원의 양이 제한되어 있어도 교착상태는 발생할 수 있습니다.
  2. 들고 기다리기 : 공유 자원에 대한 접근 권한을 양보하지 않은 상태에서 다른 자원에 대한 접근 권한을 요구합니다.
  3. 선취 불가능 : 한 프로세스가 다른 프로세스의 자원 접근 권한을 강제로 취소 불가능합니다.
  4. 대기 상태의 사이클 : 두 개 이상의 프로세스가 자원 접근을 기다리는데 그 관계에 사이클이 존재합니다.

 

👀 교착상태 방지

 위의 조건들 가운데 하나를 제거하면 됩니다.

반응형