🦁아기사자

[Java] 멀티스레드 프로그래밍

코딩하는 하마 2025. 3. 25. 18:14

Thread 클래스와 Runnable 인터페이스

1) Thread

Thread : 프로그램 실행의 가장 작은 단위

이를 사용하는 방법으로는 Thread 클래스를 상속받아 사용하기 , Runnable 인터페이스를 Thread 생성자의 매개변수로 넣어 주는 방법이 있음 

-> sleep 메소드 : 현재 스레드 멈추기 , 자원을 놓아주지는 않고 제어권을 넘겨주므로 데드락이 발생할 수 있음 

-> interrupt 메소드 : 다른 스레드를 깨워서 interruptException 발생시킴 , interrupt가 발생한 스레드는 예외를 catch 하여 다른 작업 가능

-> join 메소드 : 다른 스레드 작업이 끝날 때까지 기다리게 함 , 스레드 순서 제어시 사용 

//Thread 상속 받아 구현 
class exam extends Thread{
    public void run() {
        System.out.println("RUNNING~~");
    }
}
public class Main {
    public static void main(String[] args) {
        Thread ex = new exam();
        ex.start();
    }
}

 

//Runnalbe 인터페이스를 구현하여 인자로 전달
class exam02 implements Runnable {
    public void run() {
        System.out.println("RUNNING");
    }

}
public class Main {
    public static void main(String[] args) {
        Runnable ex = new exam02();
        Thread exthread = new Thread(ex);
        exthread.start();
    }
}

 


2) Runnable

Runnable : 자바에서 다중 스레드를 구현하기 위한 인터페이스

                   Runnable 인터페이스를 구현한 객체는 실행 가능한 코드이며 스레드에 의해 실행 가능함 

-> run 메소드 : 이를 오버라이드하여 실행 가능한 코드를 정의할 수 있음

이를 사용하면 스레드 클래스와 작업 클래스를 분리 가능, 코드 재사용성과 유연성 향상 가능 

@FunctionalInterface 
public interface Runnable{
	public abstract void run();
}

 


 

스레드의 상태

스레드의 상태를 알 수 있도록 하는 메소드 : getState()

 

객체 생성 : NEW  -> 스레드 객체가 생성 , 아직 start() 메소드가 호출되지 않은 상태 

실행 대기 : RUNNABLE -> 실행 상태로 언제든지 갈 수 있는 상태 

일시 정지 : WAITING -> 다른 스레드가 통지할 때까지 기다리는 상태

                   TIMED_WAITING -> 주어진 시간동안 기다리는 상태 

                   BLOCKED -> 사용하고자 하는 객체의 락이 풀릴 때까지 기다리는 상태 

종료 :  TERMINATED -> 실행을 마친 상태


스레드의 우선순위

모든 Thread는 우선순위를 가짐 

우선순위가 높은 스레드일 수록 더 많은 리소스 사용하기 위해 시도 -> 즉 우선순위가 높은 스레드는 실행 기회를 많이 갖음 ( BUT 순위가 높다고 자원을 모두 가져가거나 항상 먼저 실행되는 것은 아님)

우선순위가 낮은 스레드 -> cpu 자원을 적게 얻으려고 시도 

 

기본 우선순위는 5임

public static final int MAX_PRIORITY  가장 높은 순위 , 상수 10
public static final int NORM_PRIORITY 일반적인 순위 , 상수 5
public static final int MIN_PRIORITY 가장 낮은 순위 , 상수 1

 

ex) 우선 순위 스케줄링 실습 코드 

class myThread extends Thread{

    int num;

    public myThread(int num) {
        this.num = num;
    }

    @Override
    public void run() {
        System.out.println(this.num+" thread  start");

        for(int i=0;i<10000;i++){
            for(int j=0;j<10000;j++){
                for(int k=0;k<10000;k++){

                }
            }
        }
        System.out.println(this.num+" thread  end");
    }
}
public class Main {
    public static void main(String[] args) {

        for(int i=1;i<=5;i++){
            myThread thread = new myThread(i);
            if(i == 5){
                thread.setPriority(Thread.MAX_PRIORITY);
            }else{
                thread.setPriority(Thread.MIN_PRIORITY);
            }
            thread.start();
        }
    }
}

 

실행 결과 )

위 실행 결과를 보면 5번 스레드의 우선순위를 MAX_PRIORITY를 두고 나머지 스레드는 MIN_PRIORITY로 설정했다. 

가장 우선 순위가 높은 스레드가 실행 상태를 더 많이 가져 제일 먼저 종료되는 것을 볼 수 있다.

 


Main 스레드

자바 프로그램 시작과 동시에 하나의 스레드가 즉시 동작하게 되는 것을 Main 스레드라고 한다. 

 

[속성]

- 메인 스레드로부터 다른 자식 스레드가 발생

- 다양한 종료 작업을 수행하기 때문에 대체로 마지막으로 실행 종료되는 스레드여야 함


동기화

1) Synchronized란? 

이는 동기화라는 뜻으로 하나의 프로세스가 진행될 때 다른 프로세스가 간섭하지 못하게 해당 프로세스만을 진행하고 수행하도록 하는 것을 말한다. 

 

- 동기화 하지 않은 코드 

class counter {
    private int count =0;

    public void increment() {
        count++;
    }

    public int getCount() {
        return count;
    }
}

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

        Thread t1 = new Thread(()-> {
            for (int i =0;i<10000;i++){
                c.increment();
            }
        });

        Thread t2 = new Thread(() -> {
            for(int i =0;i<10000;i++){
                c.increment();
            }
        });

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

        try{
            t1.join();
            t2.join();
        } catch(InterruptedException e ){
            e.printStackTrace();
        }
        System.out.println("fina count: "+c.getCount());
    }
}

 

위 코드는 동기화를 하지 않은 코드로 실행 시켰을 때 increment() 메소드에서 경쟁 상태가 발생할 수 있다. 

여러 스레드가 동시에 count++를 수행하면 원자성이 깨지고 최종 값이 예상보다 작아질 수 있다. 

 

- 동기화 한 코드 

class counter {
    private int count =0;
    public synchronized void increment(){
        count++;
    }

    public synchronized int getCount() {
        return count;
    }
}
public class Main {
    public static void main(String[] args) {
        counter c = new counter();

        Thread t1 = new Thread(()-> {
            for (int i =0; i< 10000;i++){
                c.increment();
            }
        });

        Thread t2 = new Thread(() -> {
            for (int i =0;i<10000;i++){
                c.increment();
            }
        });

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

        try{
            t1.join();
            t2.join();
        }catch(InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("final count : "+c.getCount());
    }
}

 

위 코드를 실행 시키면 두 개의 스레드가 increment()를 호출 할 떄 한 번에 하나의 스레드만 접근 가능하도록 되어 final count의 값이 항상 20000이 나오는 것을 볼 수 있다.

 


 

데드락

두 개 이상의 스레드가 서로 상대방의 리소스를 기다리면서 무한 대기에 빠지는 상태를 말한다. 

즉 서로가 서로를 기다리면서 실행이 멈춰버리는 상황을 말한다. 

 

[발생 조건]

1. 상호 배제 (mutual exclusion)

하나의 리소스는 한 번에 한 개의 스레드만 사용할 수 있다. 

 

2. 점유 및 대기 

스레드가 이미 하나의 리소스를 점유한 상태에서 추가 리소스를 요청하며 대기한다. 

 

3. 비선점 

다른 스레드가 점유한 리소스를 강제로 빼앗을 수 없다. 

 

4. 순환 대기 

두 개 이상의 스레드가 서로의 리소스를 기다리면서 순환 형태의 대기 상태가 된다.

 

ex) 

public class Main {

    public static Object object1 = new Object();
    public static Object object2 = new Object();

    public static void main(String[] args) {
        FirstThread thread1 = new FirstThread();
        SecondThread thread2 = new SecondThread();

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

    }

    private static class FirstThread extends Thread{
        @Override
        public void run() {
            synchronized (object1){
                System.out.println("First Thread has object1's lock");

                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("First Thread want to have object2's lock. so wait");

                synchronized (object2){
                    System.out.println("First Thread has object2's lock too");
                }
            }
        }
    }

    private static class SecondThread extends Thread{
        @Override
        public void run() {
            synchronized (object2){
                System.out.println("Second Thread has object2's lock");

                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("Second Thread want to have object1's lock, so wait");

                synchronized (object1){
                    System.out.println("Second Thread has object1's lock too");
                }
            }
        }
    }
}

  [코드 참조]

https://math-coding.tistory.com/175

 

[해결 방법]

1. 락은 순서를 항상 일정하게 유지 

모든 스레드가 lock1을 먼저 획득하고 그 다음 lock2 를 획득하도록 순서 강제하기

 

2. 타임 아웃

스레드가 락을 획득하기 위해 기다리는 시간을 정해놓기 

타임 아웃 시간이 다 되도록 락을 획득하지 못하면 이 스레드는 락을 포기한다

작업 처리하기 위해 락을 오래 잡고 있어도 lock 타임아웃에 걸려 포기하게 한다. 

-> java.util.concurrency를 사용함

'🦁아기사자' 카테고리의 다른 글

[Java] 애노테이션  (0) 2025.03.27
[Java] Enum  (0) 2025.03.26
[Java] 인터페이스  (0) 2025.03.20
[Java] 패키지  (0) 2025.03.19
[Java] 상속  (0) 2025.03.18