티스토리 뷰

728x90

운영체제에서는 실행 중인 하나의 애플리케이션을 프로세스(process) 라고 부른다.

Mac - 활성상태 보기 화면

프로세스는 프로그램을 수행하는 데 필요한 데이터와 메모리 등의 자원 그리고 쓰레드로 구성되어 있으며 프로세스의 자원을 이용해서 실제로 작업을 수행하는 것이 바로 쓰레드이다.

 

Runnable 인터페이스와 Thread 클래스

쓰레드를 생성하는 것은 크게 두 가지 방법이 있다.

하나는 Runnable 인터페이스를 사용하는 것이고, 다른 하나는 Thread 클래스를 사용하는 것이다.

Thread 클래스는 Runnable 인터페이스를 구현한 클래스이므로, 어떤 것을 적용하느냐의 차이만 있다.

 

Runnable 인터페이스는 스레드에서 실행되는 코드를 포함하는 의미인 run() 이라는 메소드를 정의하고 있다.

다음은 Runnable 인터페이스를 구현한 RunnableSample 클래스의 예제

public class RunnableSample implements Runnable {

    @Override
    public void run() {
        System.out.println("This is RunnableSample`s run() method");
    }
}

다음은 Thread 클래스를 확장한 예제이다.

public class ThreadSample extends Thread {

    @Override
    public void run() {
        System.out.println("This is ThreadSample`s run() method.");
    }
}

RunnableSample 클래스와 ThreadSample 클래스는 모두 쓰레드로 실행할 수 있다는 공통점이 있지만 이 두개의 쓰레드 클래스를 실행하는 방식은 다르다.

public class RunThreads {
    public static void main(String[] args) {
        RunThreads runThreads = new RunThreads();
        runThreads.runBasic();
    }

    public void runBasic() {
        RunnableSample runnableSample = new RunnableSample();
        ThreadSample threadSample = new ThreadSample();

        new Thread(runnableSample).start();
        threadSample.start();

        System.out.println("RunThread end");
    }
}

//결과
This is RunnableSample`s run() method
This is ThreadSample`s run() method.
RunThread end

- 쓰레드가 시작하면 수행되는 메소드는 run() 메소드이다.

- 쓰레드를 시작하는 메소드는 start() 이다.

 

이 예제에서 살펴볼건 두가지 객체 모두 쓰레드를 시작할 때 메소드는 start()라는 메소드를 사용한다는 것이다.

쓰레드가 시작하면 수해오디는 메소드가 run() 메소드라는건 Runnable 인터페이스를 구현하거나 Thread 클래스를 상속받아서 사용할 때는 run() 이라는 메소드를 시작점으로 작성해야 한다는 이야기다.

 

그런데 우리는 쓰레드를 실행할 때 run()이라는 메소드를 사용하지 않고 start() 라는 메소드를 사용했다.

우리가 run()을 시작하지 않아도 start() 메소드만으로 실행이 된 이유는 자바에서 run() 메소드를 수행하도록 되어 있기 때문이다.

 

Runnable 인터페이스를 구현한 클래스를 쓰레드로 바로 시작할 수 는 없고, Thread 클래스의 생성자에 해당 객체를 추가하여 시작해 주어야만 한다. 그 다음에 start()메소드를 호출하면 쓰레드가 시작된다.

Thread 클래스를 상속받은 ThreadSample 클래스는 바로 start() 메소드를 호출해서 사용할 수 있다.

 

쓰레드 클래스가 다른 클래스를 확장할 필요가 있을 경우에는 Runnable 인터페이스를 구현하면 되고, 그렇지 않은 경우에는 Thread 클래스를 사용하는 것이 편하다. 

Thread와 Runnable 인터페이스 다이어그램

그런데 쓰레드를 실행하면 항상 결과가 순차적으로 나오는 것은 아니다.

쓰레드라는 것을 start()  메소드를 통해서 시작했다는 것은, 프로세스가 아닌 하나의 쓰레드를 JVM에 추가하여 실행한다는 것이다.

RunnableSample runnableSample = new RunnableSample();
ThreadSample threadSample = new ThreadSample();

new Thread(runnableSample).start();
threadSample.start();

쓰레드를 start()로 시작한 순간 시작한 start() 메소드가 끝날 때까지 기다리지 않고 바로 다음 줄의 start()메소드를 실행한다.

쓰레드를 구현할 때 start() 메소드를 호출하면 쓰레드 클래스에 있는 run() 메소드의 내용이 끝나든, 끝나지 않던 간에 쓰레드를 시작한 메소드에서는 그 다음 줄에 있는 코드를 실행한다.

public void runBasic2() {
    RunnableSample[] runnableSamples = new RunnableSample[5];
    ThreadSample[] threadSamples = new ThreadSample[5];
    for (int i = 0; i < 5; i++) {
        runnableSamples[i] = new RunnableSample();
        threadSamples[i] = new ThreadSample();

        new Thread(runnableSamples[i]).start();
        threadSamples[i].start();
    }
    System.out.println("끝!");
}

//결과
This is RunnableSample`s run() method
This is ThreadSample`s run() method.
This is RunnableSample`s run() method
This is ThreadSample`s run() method.
This is RunnableSample`s run() method
This is ThreadSample`s run() method.
This is RunnableSample`s run() method
This is ThreadSample`s run() method.
끝!
This is ThreadSample`s run() method.
This is RunnableSample`s run() method

결과를 보면 알겠지만 끝!을 출력하는 마지막 출력문이 가장 마지막에 수행되지 않는 것을 볼 수 있다.

새로 생성한 클래스는 run() 메소드가 종료되면 끝이난다.  만약 run() 메소드가 끝나지 않으면 애플리케이션은 끝나지 않는다..

Thread 상태

스레드 객체를 생성하고, start() 메소드를 호출하면 스레드가 바로 실행되는 것처럼 보이지만 사실은 실행 대기 상태가 된다.

실행 대기 상태에 있는 스레드 중에서 스레드 스케줄링으로 선택된 스레드가 CPU를 점유하고 run() 메소드를 실행한다.

실행 대기 상태와 실행 상태를 번갈아가면서 run() 메소드를 실행하고  run() 메소드가 종료되면 더 이상 실행할 코드가 없기 때문에 종료 상태가 된다. 실행 상태에서 실행 대기 상태로 가지 않고 일시 정지 상태로 가기도 하는데, 일시 정지 상태는 스레드가 실행할 수 없는 상태이다.

출처 - 자바의정석 스레드의 상태

자바에서 쓰레드의 현재 상태를 가져오려면 Thread.getState() 메소드를 사용하여 쓰레드의 현재상태를 가져올 수 있다.

Java는 쓰레드의 상태에 대한 ENUM 상수를 정의하는 java.lang.Thread.State 클래스를 제공한다.

public enum State {
    /**
     * 스레드 객체가 생성, 아직 start() 메소드가 호출되지 않은 상태이다.
     */
    NEW,

    /**
     * 실행 상태로 언제 든지 갈 수 있는 상태이다.
     */
    RUNNABLE,

    /**
     * 사용하고자 하는 객체의 락이 풀릴 때까지 기다리는 상태이다.
     */
    BLOCKED,

    /**
     * 다른 스레드가 통지할 때까지 기다리는 상태이다.
     */
    WAITING,

    /**
     * 주어진 시간동안 기다리는 상태이다.
     */
    TIMED_WAITING,

    /**
     * 실행을 마친 상태이다.
     */
    TERMINATED;
}

다음은 스레드의 상태를 출력하는 스레드 코드이다.

import static java.lang.Thread.*;

public class StatePrintThread extends Thread{

    private Thread targetThread;

    public StatePrintThread(Thread targetThread) {
    	//상태를 조사할 스레드
        this.targetThread = targetThread;
    }

    @Override
    public void run() {
        while (true) {
            //스레드 상태 얻기
            State state = targetThread.getState();
            System.out.println("타겟 스레드 상태 = " + state);

            if (state == State.NEW) {
                targetThread.start();
            }

            if (state == State.TERMINATED) {
                break;
            }
            try {
                //0.5초간 일시정지
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

타겟 스레드 클래스

10억번 반복으로 RUNNABLE 상태를 유지하고 sleep() 메소드를 호출해서 1.5초간 TIME_WAITING 상태를 유지한다.

그리고 다음 라인에서 다시 10억번 반복으로  RUNNABLE 상태를 유지한다.

public class TargetThread extends Thread {

    public void run() {
        for (long i = 0; i < 1_000_000_000; i++) { }

        try {
            //1.5초간 일시 정지
            Thread.sleep(1500);
        } catch (Exception e) {

        }

        for (long i = 0; i < 1_000_000_000; i++) { }
    }
}

실행코드

public class ThreadStateExample {
    public static void main(String[] args) {
        StatePrintThread statePrintThread = new StatePrintThread(new TargetThread());
        statePrintThread.start();
    }
}

//결과
타겟 스레드 상태 = NEW
타겟 스레드 상태 = TIMED_WAITING
타겟 스레드 상태 = TIMED_WAITING
타겟 스레드 상태 = TIMED_WAITING
타겟 스레드 상태 = RUNNABLE
타겟 스레드 상태 = TERMINATED

Process finished with exit code 0

 Thread 우선순위

Thread 클래스의 주요 메소드에서는 크게 보면 쓰레드의 속성을 확인하고, 지정하기 위한 메소드와 쓰레드의 상태를 통제하기 위한 메소드로 나눌 수 있다.

쓰레드의 속성을 지정할 수 있는 메소드 중에 우선 순위(Priority)가 있다.

쓰레드 우선순위를 말 그대로, 대기하고 있는 상황에서 더 먼저 수행할 수 있는 순위를 말한다.

/**
 * The minimum priority that a thread can have.
 */
public static final int MIN_PRIORITY = 1;

/**
 * The default priority that is assigned to a thread.
 */
public static final int NORM_PRIORITY = 5;

/**
 * The maximum priority that a thread can have.
 */
public static final int MAX_PRIORITY = 10;

쓰레드 클래스안에 이렇게 상수로 구분되어 있다.

 

MAX_PRIORITY = 가장 높은 우선순위이며, 그 값은 10이다.

NORM_PRIORITY = 일반 쓰레드의 우선순위이며, 그 값은 5다.

MIN_PRIORITY = 가장 낮은 우선순위이며, 그 값은 1이다.

 

쓰레드가 가질 수 있는 우선순위의 범위는 1~10이며 숫자가 높을수록 우선순위가 높다.

class ThreadDemo extends Thread {
    public void run() {
        System.out.println("Inside run method");
    }

    public static void main(String[] args) {
        ThreadDemo t1 = new ThreadDemo();
        ThreadDemo t2 = new ThreadDemo();
        ThreadDemo t3 = new ThreadDemo();

        // Default 5
        System.out.println("t1 thread priority : " + t1.getPriority());

        // Default 5
        System.out.println("t2 thread priority : " + t2.getPriority());

        // Default 5
        System.out.println("t3 thread priority : " + t3.getPriority());

        t1.setPriority(Thread.MIN_PRIORITY);
        t2.setPriority(Thread.NORM_PRIORITY);
        t3.setPriority(Thread.MAX_PRIORITY);

        // t3.setPriority(21); will throw
        // IllegalArgumentException

        // 1
        System.out.println("t1 thread priority : " + t1.getPriority());

        // 5
        System.out.println("t2 thread priority : " + t2.getPriority());

        // 10
        System.out.println("t3 thread priority : " + t3.getPriority());

        // Main thread

        // Displays the name of
        // currently executing Thread
        System.out.println("Currently Executing Thread : " + Thread.currentThread().getName());

        System.out.println("Main thread priority : " + Thread.currentThread().getPriority());

        // Main thread priority is set to 10
        Thread.currentThread().setPriority(10);
        System.out.println("Main thread priority : " + Thread.currentThread().getPriority());
    }
}


// 결과
t1 thread priority : 5
t2 thread priority : 5
t3 thread priority : 5
t1 thread priority : 1
t2 thread priority : 5
t3 thread priority : 10
Currently Executing Thread : main
Main thread priority : 5
Main thread priority : 10

Main 쓰레드

Java의 Main 스레드는 프로그램이 시작될 때 실행을 시작하는 스레드입니다. 모든 자식 스레드는 Main 스레드에서 생성된다. 또한 다양한 종료 동작을 수행하므로 실행을 완료하는 마지막 스레드이다.

public class Demo {
   public static void main(String args[]) {
      Thread t = Thread.currentThread();
      System.out.println("Main thread: " + t);
      t.setName("current");
      System.out.println("Current thread: " + t);
      try {
         for (int i = 1; i <= 5; i++) {
            System.out.println(i);
            Thread.sleep(10);
         }
      } catch (InterruptedException e) {
         System.out.println("Main thread is interrupted");
      }
      System.out.println("Exiting the Main thread");
   }
}

//결과
Main thread: Thread[main,5,main]
Current thread: Thread[current,5,main]
1
2
3
4
5
Exiting the Main thread

동기화(Synchronized)

 

데드락(deadlock)

Alphonse와 Gaston은 친구이며 예의를 지키는 위대한 신자입니다. 엄격한 예의 규칙은 친구에게 절을 할 때 친구가 절을 할 기회가 있을 때까지 절을 해야 한다는 것입니다. 불행히도, 이 규칙은 두 친구가 동시에 서로에게 절을 할 수 있는 가능성을 설명하지 않습니다.이 예제 애플리케이션은 Deadlock이러한 가능성을 모델링합니다.

public class Deadlock {
    static class Friend {
        private final String name;

        public Friend(String name) {
            this.name = name;
        }

        public String getName() {
            return this.name;
        }

        public synchronized void bow(Friend bower) {
            System.out.format("%s: %s" + "  has bowed to me!%n", this.name, bower.getName());
            bower.bowBack(this);
        }

        public synchronized void bowBack(Friend bower) {
            System.out.format("%s: %s" + " has bowed back to me!%n", this.name, bower.getName());
        }
    }

    public static void main(String[] args) {
        final Friend alphonse = new Friend("Alphonse");
        final Friend gaston = new Friend("Gaston");
        
        new Thread(() -> alphonse.bow(gaston)).start();
        new Thread(() -> gaston.bow(alphonse)).start();
    }
}

//
Alphonse: Gaston  has bowed to me!
Gaston: Alphonse  has bowed to me!

끝나지 않는다..

각 스레드는 다른 스레드가 종료되기를 기다리고 있기 때문에 어느 블록도 끝나지 않습니다.

 

참고

www.geeksforgeeks.org/main-thread-java/

www.geeksforgeeks.org/java-thread-priority-multithreading/

www.geeksforgeeks.org/lifecycle-and-states-of-a-thread-in-java/

자바의 정석 3e

자바의 신

이것이 자바다

docs.oracle.com/javase/tutorial/

728x90
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
«   2024/05   »
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
글 보관함