생각정리/Java

[JAVA] 스레드

생각중임 2023. 8. 17. 16:35

스레드(Thread)

프로세스에서 실제로 작업을 수행하는 주체를 말한다.

모든 프로세스에는 한 개 이상의 스레드가 존재하여 작업을 수행한다.

두 개 이상의 스레드를 가지는 프로세스를 멀티스레드 프로세스라고 한다.

스레드의 실행은 매번 다르게 나올 수 있다. 

스레드의 생성 방법

  • Thread 클래스를 상속
  • Runnable 인터페이스를 구현
  • main 메서드 안에서 람다식을 이용해서 구현
  • 스레드를 통해 작업하고 싶은 내용을 run() 메서드에 작성해 사용한다. (람다식의 경우 바로 사용)
// Thread 클래스를 상속
public class ThreadWithClass extends Thread{
    public void run() {
        System.out.println(Thread.currentThread() + "start");
    }
}

// Runnable 인터페이스를 구현
public class ThreadWithRunnable implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread() + "start");
    }
}
public class Main {
    public static void main(String[] args) {
        System.out.println(Thread.currentThread() + "start"); // 메인 스레드
        ThreadWithClass thread1 = new ThreadWithClass();
        Thread thread2 = new Thread(new ThreadWithRunnable());

        thread1.start();
        thread2.start();

    	// 람다식을 이용한 스레드 구현
        Runnable task = () -> {
            System.out.println(Thread.currentThread() + "start");
        };

        Thread thread3 = new Thread(task, "Thread3");

        thread3.start();

        System.out.println(Thread.currentThread() + "end"); // 메인 스레드

    }

}

스레드의 우선순위

각 스레드는 우선순위(priority)에 관한 자신만의 필드를 가지고 있다.

우선순위의 범위는 1부터 10까지이며, 기본적으로 5를 가지고 있으며, 숫자가 높을수록 우선순위가 높아진다.

우선순위가 높다고 무조건적으로 먼저 작업을 하는 것이 아니라 상대적으로 우선 처리가 된다는 것이다.

public class Main {
    public static void main(String[] args) {
        ThreadWithClass thread1 = new ThreadWithClass();
        Thread thread2 = new Thread(new ThreadWithRunnable());
		
        thread2.setPriority(10); // Thread-1의 우선순위를 10으로 변경
        
        thread1.start();
        thread2.start();

        System.out.println(thread1.getPriority()); // 5
        System.out.println(thread2.getPriority()); // 10
    }
}

스레드의 상태 및 제어

메소드를 사용하기 위해서는 예외처리를 꼭 해주어야 한다.

상태 Enum 설명
객체생성 NEW 스레드 객체 생성, 아직 start() 메서드 호출 전의 상태
실행대기 (RUNNABLE) RUNNABLE 실행 상태로 언제든지 갈 수 있는 상태
일시정지 (NOT RUNNABLE) WAITING 다른 스레드가 통지(notify)할 때까지 기다리는 상태
TIMED_WAITING 주어진 시간 동안 기다리는 상태
BLOCKED 사용하고자 하는 객체의 Lock이 풀릴 때까지 기다리는 상태
종료 (DEAD) TERMINATED 스레드의 작업이 종료된 상태
  • sleep() : 현재 스레드를 지정된 시간 동안 멈추게 한다.
  • interrupt() : 다른 Thread에 예외를 발생시키는 interrupt를 보낸다. (isInterrupted()함수를 이용해 상태값을 알 수 있다.)
    • join(), sleep(), wait() 함수에 의해서 일시정지 상태일 때 interrupt() 함수를 호출하면 다시 실행대기 상태가 됨
  • join() : 정해진 시간 동안 지정한 스레드가 작업하는 것을 기다린다. (지정하지 않으면 끝날 때까지 기다린다.)
  • yield() : 남은 시간을 다음 스레드에게 양보하고 스레드 자신은 실행대기 상태가 된다.
  • wait() : 실행 중이던 스레드는 일시정지 상태로 돌린다.
  • notify() : 해당 스레드를 일시정지에서 실행대기 상태로 돌린다.
public class ThreadWithClass extends Thread{
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(getName()); // 현재 실행 중인 스레드의 이름을 반환
            try {
                Thread.sleep(1000); // 1초 대기한다.
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

public class Main {
    public static void main(String[] args) {
        System.out.println(Thread.currentThread() + "start"); // main
        ThreadWithClass thread1 = new ThreadWithClass();
        Thread thread2 = new Thread(new ThreadWithRunnable());

        thread1.start(); // thread-0
        thread2.start(); // thread-1
		
        try {
            thread1.join(); // 해당 스레드가 thread1 스레드가 종료할 때까지 기다린다. (main)
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println(Thread.currentThread() + "end"); // 가장 나중에 출력
    }
}

synchronized

멀티 스레드의 경우 여러 스레드가 한 프로세스의 자원을 공유해서 작업할 때 서로에게 영향을 줄 수 있어 이를 방지하기 위해 한 스레드가 진행 중인 작업을 다른 스레드가 작업을 하지 못하도록 막을 때 사용한다.

메서드에 직접 synchronized를 써도 되고 구현부에 사용해도 된다.

public class Main {
    public static void main(String[] args) {
        AppleStore appleStore = new AppleStore();

        Runnable task = () -> {
            while (appleStore.getStoredApple() > 0) {
                appleStore.eatApple();
                System.out.println("남은 사과의 수 = " + appleStore.getStoredApple());
            }
        };
        for (int i = 0; i < 3; i++) {
            new Thread(task).start();
        }
    }
}

class AppleStore {
    private int storedApple = 10;

    public int getStoredApple() {
        return storedApple;
    }

    public void eatApple() {
        synchronized (this) {
            if(storedApple > 0) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                storedApple -= 1;
            }
        }
    }
}

데몬 스레드

우선순위가 낮고 다른 스레드가 모두 종료되면 강제 종료 하는 스레드

public class Main {
    public static void main(String[] args) {
        Runnable demon = () -> {
            for (int i = 0; i < 1000000; i++) {
                System.out.println("demon");
            }
        };

        Thread thread = new Thread(demon);
        thread.setDaemon(true); // true로 설정시 데몬스레드로 실행됨

        thread.start();

        for (int i = 0; i < 100; i++) {
            System.out.println("task");
        }
    }
}

스레드 그룹

서로 관련이 있는 스레드를 하나의 그룹으로 묶어 사용이 가능하다.

스레드 그룹은 다른 스레드 그룹을 포함할 수 있으며, 이렇게 포함된 스레드 그룹은 트리 형태로 연결된다.

public class Main {
    public static void main(String[] args) {
        Runnable task = () -> {
            while (!Thread.currentThread().isInterrupted()) {
                try {
                    Thread.sleep(1000);
                    System.out.println(Thread.currentThread().getName());
                } catch (InterruptedException e) {
                    break;
                }
            }
            System.out.println(Thread.currentThread().getName() + " Interrupted");
        };

        // ThreadGroup 클래스로 객체를 만듭니다.
        ThreadGroup group1 = new ThreadGroup("Group1");

        // Thread 객체 생성시 첫번째 매개변수로 넣어줍니다.
        // Thread(ThreadGroup group, Runnable target, String name)
        Thread thread1 = new Thread(group1, task, "Thread 1");
        Thread thread2 = new Thread(group1, task, "Thread 2");

        // Thread에 ThreadGroup 이 할당된것을 확인할 수 있습니다.
        System.out.println("Group of thread1 : " + thread1.getThreadGroup().getName());
        System.out.println("Group of thread2 : " + thread2.getThreadGroup().getName());

        thread1.start();
        thread2.start();

        try {
            // 현재 스레드를 지정된 시간동안 멈추게 합니다.
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // interrupt()는 일시정지 상태인 스레드를 실행대기 상태로 만듭니다.
        group1.interrupt();

    }
}

무한 작동 스레드 종료시키기

public class TerminateThread extends Thread {
	private boolean flag = false;
    int i;
    
    public TerminateThread(String name) {
    	super(name);
    }
    
    public void () {
    	while(!flag) {
        	try {
            	sleep(100);
            } catch (InterruptedException e) {
            	e.printStackTrace();
            }
        }
    }
    
    public void setFlag(boolean flag) {
    	this.flag = flag;
    }
    
    public static void main(String[] args) throws IOException {
    	TerminateThread threadA = new TerminateThread("A");
        TerminateThread threadB = new TerminateThread("B");
        
        tgreadA.start();
        tgreadB.start();
        
		while(true) {
        	int in = System.in.read();
			if (in == 'A') {
            	threadA.setFlag(true);
            } else if (in == 'B') {
            	threadB.setFlag(true);
            }