스레드

김주언·2022년 7월 27일

JAVA

목록 보기
5/10
post-thumbnail

1. 멀티 스레드

애플리케이션을 실행하면 운영체제로부터 실행에 필요한 메모리를 할당받아 애플리케이션이 실행되는데 이를 프로세스라고 한다. 그리고 프로세스 내부에서 코드의 실행 흐름을 스레드라고 한다.

1.1 스레드

운영체제는 두 가지 이상의 작업을 동시에 처리하는 멀티 태스킹을 지원한다.
멀티태스킹이 반드시 멀티 프로세스를 뜻하는 것은 아님. 하나의 프로세스가 두 가지 이상의 작업을 처리할 수도 있다. 이는 멀티스레드 덕분임.

각 프로세스는 서로 독립적이지만, 하나의 프로세스 내부의 스레드는 서로에게 영향을 미친다. 따라서 스레드를 사용할 때는 예외처리를 잘 해줘야한다.

멀티 스레드는 다양하게 사용된다. 대용량 데이터의 처리 시간을 줄이기 위해 데이터를 분할하여 병렬로 처리 / UI를 가진 애플리케이션에서 네트워크 통신을 위해 사용하기 / 다수의 클라이언트의 요청을 처리하기 위한 서버 개발하기 ... etc

1.2 메인 스레드

자바의 모든 애플리케이션은 메인스레드main() 메소드를 실행하며 시작한다. 메인스레드는 필요에 따라 작업 스레드들을 만들어서 병렬로 코드를 실행 가능. 즉 멀티 스레드를 생성하여 멀티 태스킹을 수행하는 것

1.3 작업 스레드 생성과 실행

자바에서는 작업 스레드도 객체로 생성되기 때문에 클래스가 필요하다.

Thread 클래스로부터 직접 생성하기

java.lang.Thread 클래스로부터 작업 스레드 객체를 직접 생성하기 위해서는 Runnable을 매개값으로 갖는 생성자를 호출해야 한다.

Thread thread = new Thread(Runnable target);

Runnable

작업 스레드가 실행할 수 있는 코드를 가지고 있는 객체
인터페이스 타입이기 때문에 구현 객체를 만들어 대입해야 한다. Runnable에는 run() 메서드 하나가 정의되어 있다.

package example;

public class Task implements Runnable{
    @Override
    public void run() {
        // 스레드가 실행할 코드
    }
}

Runnable은 작업 내용을 가지고 있는 객체. 실제 스레드가 아님! 따라서 Runnable 구현 객체를 생성한 후에 이를 매개값으로 하여 Thread 생성자를 호출해야 작업 스레드가 생성된다.

예제

메인 스레드만 이용한 경우의 코드

package example;

import java.awt.*;

public class BeepPrintEx {
    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) {
            }
        }

        for (int i = 0; i < 5; i++) {
            System.out.println("ding");
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
            }
        }
    }
}

위 코드는 소리 5번 난 뒤에 출력 5번 됨

소리와 출력이 동시에 나게 하기 위해서는 두 작업 중 하나를 메인 스레드가 아닌 다른 스레드에서 실행해야 한다.
Runnable 구현 클래스를 작성한다.

BeepTask.java

package example;

import java.awt.*;

public class BeepTask implements Runnable{
    @Override
    public void run() {
        Toolkit toolkit = Toolkit.getDefaultToolkit();
        for (int i = 0; i < 5; i++) {
            toolkit.beep();
            try {
                Thread.sleep(500);
            } catch (Exception e) {
            }
        }
    }
}

BeepPrintEx.java

package example;

import java.awt.*;

public class BeepPrintEx {
    public static void main(String[] args) {
//        Runnable beepTask = new BeepTask();
//        Thread thread = new Thread(beepTask);
//        thread.start();
//
//        for (int i = 0; i < 5; i++) {
//            System.out.println("ding");
//            try {
//                Thread.sleep(500);
//            } catch (InterruptedException e) {
//            }
//        }
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                Toolkit toolkit = Toolkit.getDefaultToolkit();
                for (int i = 0; i < 5; i++) {
                    toolkit.beep();
                    try {
                        Thread.sleep(500);
                    } catch (Exception e) {
                    }
                }
            }
        });
        thread.start();

        for (int i = 0; i < 5; i++) {
            System.out.println("ding");
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
            }
        }
    }
}

위 코드를 실행하면 소리와 출력이 동시에 실행된다.


Thread 클래스 상속 받아서 생성하기

작업 클래스가 실행할 작업을 Runnable로 만들지 않고, Thread의 하위 클래스로 작업 스레드를 정의하며 작업 내용을 포함시킬 수 있다.

Thread 클래스를 상속한 후 run() 메소드를 재정의하여 스레드가 실행할 코드를 작성한다.

package example;

public class WorkerThread extends Thread{
    @Override
    public void run() {
        // 스레드가 실행할 코드
    }
}

일반적인 객체를 생성하는 방법 외에도 익명 객체를 사용해도 된다.

Thread thread = new Thread() {
        @Override
        public void run() {
            // 스레드가 실행할 코드
        }
    }

이처럼 생성된 작업 스레드 객체에서 start() 메소드를 호출하면 작업 스레드는 자신의 run() 메소드를 실행한다.

예제

package example;

import java.awt.*;

public class BeepThread extends Thread{
    @Override
    public void run() {
        Toolkit toolkit = Toolkit.getDefaultToolkit();
        for (int i = 0; i < 5; i++) {
            toolkit.beep();
            try {
                Thread.sleep(500);
            } catch (Exception e) {
            }
        }
    }
}
package example;

import java.awt.*;

public class BeepPrintEx {
    public static void main(String[] args) {
        Thread thread = new BeepThread();
        thread.start();

        for (int i = 0; i < 5; i++) {
            System.out.println("ding");
            try {
                Thread.sleep(500);} catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}


1.4 동기화 메소드

싱글 스레드 프로그램의 경우 1개의 스레드가 객체를 독차지하여 사용하면 되지만 멀티스레드 프로그램에서는 스레드들이 객체를 공유해서 작업해야 하는 경우가 있다.

하나의 객체를 공유하기 때문에 해당 객체의 상태가 변경되면 이 변경이 다른 스레드에게까지 영향을 미치는 점을 유의해야 한다.

스레드가 사용중인 객체를 다른 스레드가 변경할 수 없도록 하게 하려면 스레드 작업이 끝날 때까지 객체에 잠금을 걸어서 다른 스레드의 접근을 막아햐한다.

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

동기화 메소드를 생성하기 위해서는 synchronized 키워드를 사용한다.

public synchronized void method(){
    // 임계 영역
}

동기화 메소드는 전체 내용이 임계 영역이다.

만약 동기화 메소드가 여러 개 있을 경우, 스레드가 이들 중 하나를 실행 시 다른 스레드는 해당 메소드와 다른 동기화 메소드도 실행할 수 없다. 하지만 다른 스레드에서 일반 메소드는 실행 가능

예제

User1.java

package example;

public class User1 extends Thread{
    private Calculater calculater;

    public void setCalculater(Calculater calculater) {
        this.setName("User1");		// 스레드 이름을 User1으로 설정
        this.calculater = calculater;	// 공유객체인 calculator를 필드에 저장
    }

    @Override
    public void run() {
        calculater.setMemory(100);	// 공유객체인 calculator의 메모리에 100 저장
    }
}

User2.java

package example;

public class User2 extends Thread{
    private Calculater calculater;

    public void setCalculater(Calculater calculater) {
        this.setName("User2");
        this.calculater = calculater;
    }

    @Override
    public void run() {
        calculater.setMemory(50);
    }
}

Calculater.java

package example;

public class Calculater {
    private int memory;
    
    public int getMemory() {
        return memory;
    }
    
    public synchronized void setMemory(int memory){
        this.memory = memory;
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e){
            System.out.println(Thread.currentThread().getName() + ": " + this.memory);
        }
    }
}

메인

package example;

import java.awt.*;

public class BeepPrintEx {
    public static void main(String[] args) {
        Calculater calculater = new Calculater();
        
        User1 user1 = new User1();
        user1.setCalculater(calculater);
        user1.start();
        
        User2 user2 = new User2();
        user2.setCalculater(calculater);
        user2.start();
    }
}
profile
학생 점심을 좀 차리시길 바랍니다

0개의 댓글