BE전문가 프로젝트

16-1. 스레드 - 멀티스레드 본문

JAVA

16-1. 스레드 - 멀티스레드

원호보고서 2023. 11. 20. 11:42

애플리케이션 실행 시 OS에서 실행에 필요한 메모리를 할당 받아 애플리케이션이 실행이 되는 것을 프로세스라고 한다. 이 프로세스의 흐름을 스레드라고 한다.

 

멀티 스레드란 하나의 프로세스 안에 두가지 이상의 흐름을 의미한다. 따라서 한 프로세스 내에 스레드가 2개라면 2개의 코드 실행 흐름이 있다는 것이다.

 

프로세스는 운영체제로 부터 메모리를 할당받아 처리하기 때문에 하나의 프로세스에 문제가 생겼다고해서 다른 프로세스에 영향을 미치지는 않는다. 하지만 스레드의 경우 한 프로세스 내부에서 실행되기 때문에 하나의 스레드에서 예외가 발생하게 된다면 프로세스 자체가 종료가 될 수 있기에 다른 스레드 역시 영향을 받는다.

 

메인 스레드

자바는 이전 포스팅에서 말했듯 메인스레드가 main()메소드를 실행하면서 시작한다.

애플리케이션이 싱글 스레드로 구성되어 있는 경우 메인 스레드가 종료되면 프로세스 역시 종료된다. 하지만 멀티스레드의 경우 끝나지 않은 스레드가 하나라도 존재할 시 프로세스는 종료되지 않는다.

따라서 메인 스레드가 작업 스레드보다 먼저 종료되더라도 작업 스레드가 종료되지 않았다면 프로세스는 종료되지 않는다는 것을 의미한다.

 

멀티 스레드 생성

애플리케이션을 멀티 스레드로 구현할 시 먼저 몇개의 작업을 벙렬로 실행할 지 결정 후 작업별로 스레드를 생성해야한다.

스레드를 만드는 방법에는 2가지 방법이 있다.

 

1. Thread 클래스로부터 직접 생성

Thread 클래스로 부터 스레드 객체를 직접 생성하려면 Runnable을 매개값으로 갖는 생성자를 호출해야한다.

여기서 나온 Runnable는 java.lang의 인터페이스이다. 따라서 우리는 구현 클래스를 만들어 Runnable의 메소드인 run()을 재정의하여 작업 스레드가 실행할 코드를 작성한다.

public class Task implements Runnable {
    @Override
    public void run() {
    }
}

 

구현 클래스를 만들어 줬으니 Thread를 생성할 때 매개값으로 Runnable 구현 객체를 생성하여 매개값으로 Thread 생성자를 호출 후 start() 메소드를 통해 실행이 가능하다.

public class HelloWorld {

    public static void main(String[] args) {

        Runnable runnable = new Task();

        Thread thread = new Thread(runnable);
        
        thread.start();
    }
}

 

익명 객체를 사용하여 코드 절약을 하는 것도 가능하다.

public class HelloWorld {

    public static void main(String[] args) {
        
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                
            }
        });
        
        thread.start();
    }
}

 

실행

메인 스레드만 이용한 경우

public class HelloWorld {

    public static void main(String[] args) {
        Toolkit toolkit = Toolkit.getDefaultToolkit();

        for (int i = 0; i < 5; i++) {
            toolkit.beep();

            try {
                Thread.sleep(500);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        for (int i = 0; i < 5; i++) {
            System.out.println("뚜");

            try {
                Thread.sleep(500);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

위에 코드를 실행하게 되면 비프음 5번이 들린 후 뚜라는 문자가 5번 출력되는 것을 볼 수 있다.

 

작업 스레드 생성 후 실행한 경우

public class HelloWorld {

    public static void main(String[] args) {
        Toolkit toolkit = Toolkit.getDefaultToolkit();

        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {

                for (int i = 0; i < 5; i++) {
                    toolkit.beep();

                    try {
                        Thread.sleep(500);
                    } catch (Exception e){
                        e.printStackTrace();
                    }
                }
            }
        });

        thread.start();

        for (int i = 0; i < 5; i++) {
            System.out.println("뚜");

            try {
                Thread.sleep(500);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

실행 결과를 보면 비프음과  뚜라는 문자열이 동시에 출력되는 것을 알 수 있다.

 

2. Thread 하위 클래스로부터 생성

작업 스레드를 runnable의 구현 객체로 만들지 않고 Thread의 자식 클래스로 정의하는 것도 가능하다.

public class Task extends Thread {

    @Override
    public void run() {
    }
}

 

이것 역시 익명 객체로 생성하는 것이 가능하다.

Thread thread = new Thread() {
    @Override
    public void run() {
    };
};

 

스레드 이름

작업에 영향을 미치지 않지만 업떤 작업을 조사하는지 확인할 때 유용하게 사용된다.

코드 설명
setName(); 이름 설정
getName(); 이름 호출
currentThread(); 현재 참조하고 있는 스레드 객체 얻기

 

동기화 메소드

멀티 스레드의 경우 하나의 객체를 여러개의 스레드가 공유하여 사용하는 경우가 있다. 

예들 들어 스레드-1이 사용하던 A라는 객체를 스레드-2에서 사용할 시 객체가 변경되기 때문에 다른결과가 나올 수 있다.

예)

public class Calculator {
    private int memory;

    public int getMemory() {
        return memory;
    };

    public void setMemory(int memory) {
        this.memory = memory;
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " : " + this.memory);
    }
}

 

객체의 상태가 바뀌는 시간을 고려하여 sleep()을 통해 텀을 두었다.

 

쓰레드 자식 클래스 작성

public class Task1 extends Thread {

    private Calculator calculator;

    public void setCalculator(Calculator calculator) {
        this.setName("Task1");
        this.calculator = calculator;
    }

    @Override
    public void run(){
        calculator.setMemory(1);
    }
}
public class Task2 extends Thread {

    private Calculator calculator;

    public void setCalculator(Calculator calculator) {
        this.setName("Task2");
        this.calculator = calculator;
    }

    @Override
    public void run(){
        calculator.setMemory(2);
    }
}

 

실행

public class HelloWorld {

    public static void main(String[] args) {

        Calculator calculator = new Calculator();

        Task1 task1 = new Task1();
        task1.setCalculator(calculator);
        task1.start();

        Task2 task2 = new Task2();
        task2.setCalculator(calculator);
        task2.start();
    }
}

 

실행하고 나면

Task : 2

Task : 2

출력이 된 것을 알 수 있다.

 

동기화 메소드

위의 코드와 같이 스레드가 사용중인 객체를 다른 스레드의 개입이 없도록 해야한다.

멀티 스레드 프로그램에서 단 하나의 스레드만 실행할 수 있느 코드 영역을 임계영역(Critical section)이라고 하며 자바에서는 임계영역을 지정하기 위해 동기화 메소드를 제공한다.

 

동기화 메소드는 인스턴스와 정적 메소드 둘다 선언이 가능하며 synchronized 키워드를 붙여서 사용하며

실행하게 되면 객체에 잠금을 걸어 다른 스레드에서 동기화 메소드를 실행하지 못하도록 한다.

동기화 메소드의 전체 내용은 임계영역이 됨으로 실행 즉시 객체에는 잠금이 걸린다. 메소드 실행이 종료되어야 잠금이 풀린다.

 

public class Calculator {
    private int memory;

    public int getMemory() {
        return memory;
    };

    public synchronized void setMemory(int memory) {
        this.memory = memory;
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " : " + this.memory);
    }
}

제일 처음에 작성한 setMemory에 synchronized 키워드를 붙여서 동기화 메소드로 만들어 주었다.

따라서 Task1 스레드가 setMemory를 실행하면 Task2 스레드에서는 Task1의 setMemory가 끝나기 전까지 setMemory를 실행하지 못하게 된다.

 

'JAVA' 카테고리의 다른 글

17. 입출력 스트림  (1) 2023.11.23
16-2 스레드 - 제어  (0) 2023.11.21
15. java.lang에 대하여  (0) 2023.11.15
14. Object 클래스(hashcode() 및 equals())  (0) 2023.11.15
13. 익명 객체  (0) 2023.11.11
Comments