뭐라도 끄적이는 BLOG

Java Thread 본문

Java/Java 기본

Java Thread

Drawhale 2023. 7. 2. 05:56

Process와 Thread

Thread에대한 공부를 시작하기전에 가장 먼저 알아야될 것은 Process와 Thread가 각각 무엇인지 알아야된다.

Process란

간단히 말하면 실행중인 프로그램을 말한다.

  • 운영체제에 의해 메모리 공간을 할당받아 실행 중인 프로그램
  • 프로세스는 프로그램에 사용되는 데이터와 메모리등의 자원을 운영체제에서 할당받게되며 작업을 쓰레드로 구성
  • 프로세스간 자원을 공유하기 위해서는 IPC를 활용해야 한다.

Thread란

프로세스에서 실제 작업을 수행하게되는 작업의 단위

  • 모든 프로세스에는 하나 이상의 쓰레드가 존재하여 작업을 수행
  • 두 개 이상의 쓰레드를 가지는 프로세스를 멀티 쓰레드 라고 한다.
  • 쓰레드는 프로세스의 자원을 공유할 수 있다.

Thread 클래스와 Runnable 인터페이스

Thread를 생성하는 방법으로 두 가지 방법이 있다.

  1. Runnable 인터페이스 구현
  2. Thread 클래스 상속

두가지 모두 java.lang패키지에 포함되어 있으며 Thread 클래스는 Runnable 인터페이스를 구현한 클래스이다.

Thread 클래스가 다른 클래스를 상속할 필요가 있다면 Runnable 인터페이스를 구현하고 그렇지 않으면 Thread 클래스를 사용할 수 있다.

Thread의 순서?

Thread를 여러개 생성하여 사용하면 보통 어떤 Thread가 먼저 끝날지 알 수 없다.

public class ThreadTest {
    public static void main(String[] args) {
        Runnable task = () ->{
            int n1 = 10;
            int n2 = 20;
            String name = Thread.currentThread().getName();
            System.out.println(name + " : " +(n1 + n2));
        };
        Thread t = new Thread(task);
        Thread t2 = new Thread(task);
        t.start();
        t2.start();
        System.out.println("End "+ Thread.currentThread().getName());
    }
}

Output

End main
Thread-0 : 30
Thread-1 : 30
End main
Thread-1 : 30
Thread-0 : 30

위와 같이 실행때 마다 결과가 달라지는 것을 볼 수 있다.

쓰레드의 상태

  • NEW: 아직 실행되지 않은 새로생성된 Thread
  • RUNNABLE: 실행 중이거나 실행할 준비가 되었지만 resource 할당을 대기중인 상태
  • BLOCKED: Thread가 중지된 상태 monitor lock이 풀리기를 기다리는 상태
  • WAITING: 쓰레드 특정 작업을 수행할때 까지 시간 제한없이 대기중인상태
  • TIMED_WAITING: 특정 시간만큼 쓰레드가 대기중인 상태
  • TERMINATED: 쓰레드가 종료된 상태

NEW

Thread 인스턴스를 생성하고 아무것도 하지 않은 상태이면 getState() 메소드로 NEW라는 상태 값을 받을 수 있다.

public class ThreadTest {
    public static void main(String[] args) {
        Runnable task = () ->{
            int n1 = 10;
            int n2 = 20;
            String name = Thread.currentThread().getName();
            System.out.println(name + " : " +(n1 + n2));
        };
        Thread t = new Thread(task);
        System.out.println(t.getState());
    }
}

Output

NEW

RUNNABLE

생성후 start() 메소드를 호출하면 NEW에서 RUNNABLE상태가 된다.

public class ThreadTest {
    public static void main(String[] args) {
        Runnable task = () ->{
            int n1 = 10;
            int n2 = 20;
            String name = Thread.currentThread().getName();
            System.out.println(name + " : " +(n1 + n2));
        };
        Thread t = new Thread(task);
        t.start();
        System.out.println(t.getState());
    }
}

Output

RUNNABLE

BLOCKED

public class BlockedThread {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new DemoThreadB());
        Thread t2 = new Thread(new DemoThreadB());

        t1.start();
        t2.start();

        Thread.sleep(1000);

        System.out.println(t2.getState());
        System.exit(0);
    }
}
class DemoThreadB implements Runnable {
    @Override
    public void run() {
        commonResource();
    }

    public static synchronized void commonResource() {
        while(true) {
        }
    }
}

Output

BLOCKED

WAITING

package study.thread;

public class WaitingThread implements Runnable {
    public static Thread t1;

    public static void main(String[] args) {
        t1 = new Thread(new WaitingThread());
        t1.start();
    }

    public void run() {
        Thread t2 = new Thread(new DemoThreadWS());
        t2.start();

        try {
            t2.join();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            System.out.println("Thread interrupted");
        }
    }
}

class DemoThreadWS implements Runnable {
    public void run() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            System.out.println("Thread interrupted");
        }

        System.out.println(WaitingThread.t1.getState());
    }
}

Output

WAITING

TIMED_WAITING

public class TimedWaitingThread {
    public static void main(String[] args) throws InterruptedException {
        DemoThread obj1 = new DemoThread();
        Thread t1 = new Thread(obj1);
        t1.start();

        Thread.sleep(1000);
        System.out.println(t1.getState());
    }
}

class DemoThread implements Runnable {
    @Override
    public void run() {
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            System.out.println("Thread interrupted" + e);
        }
    }
}

Output

TIMED_WAITING

TERMINATED

public class TerminatedThread implements Runnable {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new TerminatedThread());
        t1.start();
        
        Thread.sleep(1000);
        System.out.println(t1.getState());
    }

    @Override
    public void run() {
    }
}

Output

TERMINATED

쓰레드의 우선순위

다중 Thread환경에서 Thread 스케줄러는 Thread의 우선순위에 따라 CPU 자원을 할당한다. Java에서 Thread를 생성할 때 항상 우선 순위가 지정된다. Thread에 허용되는 우선순위 값은 1 ~ 10사이 이다. 우선순위에 대한 정적 변수는 3가지가 있다.

  • public static int MIN_PRIORITY: Thread가 가질수 있는 최소 우선순위 입니다. 값은 1입니다.
  • public static int NORM_PRIORITY: Thread가 가질수 있는 기본 우선순위 입니다. 값은 5입니다.
  • public static int MAX_PRIORITY: Thread가 가질수 있는 최대 우선순위 입니다. 값은 10입니다.

getPriority() 로 해당 Thread의 우선순위를 받을 수 있고 setPriority()로 변경할 수도 있다.

Thread우선 순위는 절대적인 값이 아닌 상대적인 값이므로 우선순위가 1인 Thread가 10인 Thread보다 10배 할당받는 것은 아니다. 단지 좀 더 많은 작업 시간을 할당 받을 뿐이다.

Main 쓰레드

process는 하나 이상의 thread를 가진다. 그렇기 때문에 thread를 따로 생성하지 않아도 하나의 thread가 존재하게 되는데 그것이 main thread이다.

public class ThreadTest {
    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getName());
    }
}

현재 Thread.currentThread()를 사용하면 현재 실행되고 있는 Thread이름을 가져올 수 있다. 이 목록을 출력해 보면 main이 출력된다.

output

main

동기화

여러 Thread가 한 개의 리소스를 사용하려고 할 때 사용 하려는 Thread를 제외한 나머지를 접근하지 못하게 막는 아야한다. 이러한 작업을 동기화 작업이라고 하며 동기화 작업이 적용된 것을 Thread-safe하다고 한다.

java에서 동기화 방법은 3가지가 있다.

  • Synchronized
  • Atomic
  • Volatile

Synchronized

java 예약어중 하나로 method에 해당 키워드를 사용한다.

Atomic

Atomicity는 쪼갤수 없는 가장 작은 단위를 뜻하는 단어이다. java의 Atomic Type은 Wrapping클래스의 일종으로, 참조 타입과 원시 타입 두 종류의 변수에 모두 적용 가능하다. 사용시 내부적으로 CAS(Compare-And-Swap)알고리즘을 사용하 lock없이 동기화를 처리할 수 있다.

java.util.concurrent.atomic 패키지에 정의된 클래스

CAS(Compare-And-Swap)

메모리 위치의 내용을 주어진 값과 비교하고 동일한 경우에만 해당 메모리 위치의 내용을 새로 주어진 값으로 수정을 한다. 즉, 현재 주어진 값(= 현재 쓰레드에서의 데이터)과 실제 데이터와 저장된 데이터를 비교해서 두 개가 일치할 때만 값을 업데이트 한다.

compareAndSet()이 해당 역할을 한다. 

Volatile

volatile는 java변수를 main memory에 저장하겠다는 것을 명시하는 것이다. 매번 변수의 값을 read할 때마다 CPU cache에 저장된 값이 아닌 Main Memory에서 읽는 것이다. 또한 변수의 값을 write할 때마다 Main Memory에 쓰게 된다.

volatile 변수를 사용하지 않은 MultiThread는 작업을 수행하는 동안 성능 향상을 위해 Main Memory에서 읽은 변수를 CPU Cache에 저장하게 된다. 이때 만약 Thread가 변수 값을 읽어 올 때 각각의 CPU Cache에 저장된 값이 다르기 때문에 변수 값의 불일치가 발생할 수 있다.

데드락(Deadlock, 교착상태)

Deadlock은 둘 이상의 쓰레드가 lock을 획득하기 위해 대기하는데, 이 lock을 잡고 있는 Thread들도 똑같이 다른 lock을 기다리면서 서로 block 상태에 놓이는 상태를 말한다.

반응형

'Java > Java 기본' 카테고리의 다른 글

Java Annotation  (0) 2023.07.02
Java Enum  (0) 2023.07.02
Java Exception, Error  (0) 2023.07.02
Java Interface  (0) 2023.07.02
Java 패키지  (0) 2023.07.02