스레드(Thread)

김수민·2023년 3월 15일
0

백엔드 부트캠프

목록 보기
25/52

스레드란?

프로세스(Process)와 스레드(Thread)

  • 프로세스: 실행중인 애플리케이션
    - 데이터, 컴퓨터 자원, 스레드로 구성
  • 스레드: 데이터와 애플리케이션이 확보한 자원을 활용하여 소스 코드를 실행.
    - 하나의 코드 실행 흐름

메인 스레드(Main thread)

자바 애플리케이션을 실행하면 가장 먼저 실행되는 메서드는 main 메서드이며, 메인 스레드가 main 메서드를 실행시켜줌. 메인 스레드는 main 메서드의 코드를 처음부터 끝까지 순차적으로 실행시키며, 코드의 끝을 만나거나 return문을 만나면 실행을 종료함.

멀티 스레드(Multi-Thread)

  • 멀티 스레드: 하나의 프로세스가 여러 개의 스레드를 갖는 것
  • 멀티 스레딩: 여러 스레드가 동시에 작업을 수행하는 것
    예. 메신저 프로그램을 사용할 때 상대방에게 보낼 사진을 업로드하면서 동시에 메시지를 주고받는 것.

스레드의 생성과 실행

작업 스레드 생성과 실행

메인 스레드 외에 별도의 작업 스레드를 활용한다는 것: 작업 스레드가 수행할 코드를 작성하고, 작업 스레드를 생성하여 실행시키는 것
스레드가 수행할 코드도 클래스 내부에 작성해주어야 하며, run()이라는 메서드 내에 스레드가 처리할 작업을 작성하도록 규정되어 있음. run() 메서드는 Runnable 인터페이스와 Thread 클래스에 정의되어져 있음.

작업 스레드를 생성하고 실행하는 방법
1. Runnable 인터페이스를 구현한 객체에서 run()을 구현하여 스레드를 생성하고 실행하는 방법

public class ThreadExample1 {
    public static void main(String[] args) {
    	// Runnable 인터페이스를 구현한 객체 생성
        Runnable task1 = new ThreadTask1();
        
        // Runnable 구현 객체를 인자로 전달하면서 Thread 클래스를 인스턴스화하여 스레드 생성
        Thread thread1 = new Thread(task1);
        
        // 위의 두 줄을 아래와 같이 한 줄로 축약할 수 있음
        // Thread thread1 = new Thread(new ThreadTask1());

        // 작업 스레드를 실행시켜, run() 내부의 코드를 처리하도록 함
        thread1.start();

        // 반복문 추가
        for (int i = 0; i < 100; i++) {
            System.out.print("@");
        }
    }
}

// Runnable 인터페이스를 구현하는 클래스
class ThreadTask1 implements Runnable {
	// run() 메서드 바디에 스레드가 수행할 작업 내용 작성
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.print("#");
        }
    }
}

// 출력값 (매 실행 시마다 다를 수 있음)
@@@@@@@@@@@######@@@@@############################
@#########@@@@@@@@@@@@@@@@############@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@##@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@###########################################
Process finished with exit code 0
  • @main 메서드의 반복문에서 출력한 문자. 즉 @는 메인 스레드의 반복문 코드 실행에 의해 출력되었음
  • #run() 메서드의 반복문에서 출력한 문자. 즉 #는 작업 스레드의 반복문 코드 실행에 의해 출력되었음
  • @#는 섞여 있음. 즉 메인 스레드와 작업 스레드가 동시에 병렬로 실행되면서 각각 main 메서드와 run() 메서드의 코드를 실행시켰기 때문에 두 가지 문자가 섞여서 출력된 것.
  1. Thread 클래스를 상속 받은 하위 클래스에서 run() 을 구현하여 스레드를 생성하고 실행하는 방법
public class ThreadExample2 {
    public static void main(String[] args) {

        // Thread 클래스를 상속받은 클래스를 인스턴스화하여 스레드를 생성
        ThreadTask2 thread2 = new ThreadTask2();

        // 작업 스레드를 실행시켜, run() 내부의 코드를 처리하도록 합니다. 
        thread2.start();

        // 반복문 추가
        for (int i = 0; i < 100; i++) {
            System.out.print("@");
        }
    }
}

// Thread 클래스를 상속받는 클래스 작성
class ThreadTask2 extends Thread {
	// run() 메서드 바디에 스레드가 수행할 작업 내용 작성
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.print("#");
        }
    }
}

익명 객체를 사용하여 스레드 생성하고 실행하기

Runnable 익명 구현 객체를 활용한 스레드 생성 및 실행

public class ThreadExample1 {
    public static void main(String[] args) {
				
        // 익명 Runnable 구현 객체를 활용하여 스레드 생성
        Thread thread1 = new Thread(new Runnable() {
            public void run() {
                for (int i = 0; i < 100; i++) {
                    System.out.print("#");
                }
            }
        });

        thread1.start();

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

Thread 익명 하위 객체를 활용한 스레드 생성 및 실행

public class ThreadExample2 {
    public static void main(String[] args) {

        // 익명 Thread 하위 객체를 활용한 스레드 생성
        Thread thread2 = new Thread() {
            public void run() {
                for (int i = 0; i < 100; i++) {
                    System.out.print("#");
                }
            }
        };

        thread2.start();

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

스레드의 이름

메인 스레드는 "main"이라는 이름을 가지며 그 외에 추가적으로 생성한 스레드는 기본적으로 "Thread-n"이라는 이름을 가짐

스레드의 이름 조회하기

스레드의 이름은 스레드의_참조값.getName()으로 조회할 수 있음.

public class ThreadExample3 {
    public static void main(String[] args) {

        Thread thread3 = new Thread(new Runnable() {
            public void run() {
                System.out.println("Get Thread Name");
            }
        });

        thread3.start();

        System.out.println("thread3.getName() = " + thread3.getName());
    }
}

// 출력값
/Library/Java/JavaVirtualMachines/zulu-11.jdk/Contents/Home/bin/java -javaagent:/Users/0hyun.cho/Library/Application Support/JetBrains/Toolbox/apps/IDEA-C/ch-0/221.5080.210/IntelliJ IDEA CE.app/Contents/lib/idea_rt.jar=52285:/Users/0hyun.cho/Library/Application Support/JetBrains/Toolbox/apps/IDEA-C/ch-0/221.5080.210/IntelliJ IDEA CE.app/Contents/bin -Dfile.encoding=UTF-8 -classpath /Users/0hyun.cho/study/example/out/production/classes ThreadExample3
Get Thread Name
thread3.getName() = Thread-0

Process finished with exit code 0

스레드의 이름 설정하기

스레드의 이름은 스레드의_참조값.setName() 으로 설정

public class ThreadExample4 {
    public static void main(String[] args) {

        Thread thread4 = new Thread(new Runnable() {
            public void run() {
                System.out.println("Set And Get Thread Name");
            }
        });

        thread4.start();

        System.out.println("thread4.getName() = " + thread4.getName());

        thread4.setName("Code States");

        System.out.println("thread4.getName() = " + thread4.getName());
    }
}

// 출력값
/Library/Java/JavaVirtualMachines/zulu-11.jdk/Contents/Home/bin/java -javaagent:/Users/0hyun.cho/Library/Application Support/JetBrains/Toolbox/apps/IDEA-C/ch-0/221.5080.210/IntelliJ IDEA CE.app/Contents/lib/idea_rt.jar=52282:/Users/0hyun.cho/Library/Application Support/JetBrains/Toolbox/apps/IDEA-C/ch-0/221.5080.210/IntelliJ IDEA CE.app/Contents/bin -Dfile.encoding=UTF-8 -classpath /Users/0hyun.cho/study/example/out/production/classes ThreadExample4
Set And Get Thread Name
thread4.getName() = Thread-0
thread4.getName() = Code States

Process finished with exit code 0

스레드 인스턴스의 주소값 얻기

스레드의 이름을 조회하고 설정하는 위 두 메서드는 모두 Thread 클래스로부터 인스턴스화된 인스턴스의 메서드이므로, 호출할 때에 스레드 객체의 참조가 필요함.
만약 실행 중인 스레드의 주소값을 사용해야 하는 상황이 발생한다면 Thread 클래스의 정적 메서드인 currentThread()를 사용.

public class ThreadExample1 {
    public static void main(String[] args) {

        Thread thread1 = new Thread(new Runnable() {
            public void run() {
                System.out.println(Thread.currentThread().getName());
            }
        });

        thread1.start();
        System.out.println(Thread.currentThread().getName());
    }
}

// 출력 결과
/Library/Java/JavaVirtualMachines/zulu-11.jdk/Contents/Home/bin/java -javaagent:/Users/0hyun.cho/Library/Application Support/JetBrains/Toolbox/apps/IDEA-C/ch-0/221.5080.210/IntelliJ IDEA CE.app/Contents/lib/idea_rt.jar=52293:/Users/0hyun.cho/Library/Application Support/JetBrains/Toolbox/apps/IDEA-C/ch-0/221.5080.210/IntelliJ IDEA CE.app/Contents/bin -Dfile.encoding=UTF-8 -classpath /Users/0hyun.cho/study/example/out/production/classes ThreadExample1
main
Thread-0

Process finished with exit code 0

스레드의 동기화

try { Thread.sleep(1000); } catch (Exception error) {}

  • Thread.sleep(1000);

    • 스레드를 일시 정지시키는 메서드. (어떤 스레드가 일시 정지되면 대기열에서 기다리고 있던 다른 스레드가 실행됨)
    • Thread.sleep()은 반드시 try ~ catch 문의 try 블럭 내에 작성해주어야 함
    • 스레드의 동작을 1초동안 멈추는 코드
  • try { ... } catch ( ~ ) { ... }

    • try ... catch 문은 예외 처리에 사용되는 문법
    • try의 블록 내의 코드를 실행하다가 예외 또는 에러가 발생하면 catch문의 블럭에 해당하는 내용을 실행하라는 의미

임계 영역(Critical section)과 락(Lock)

  • 임계영역: 오로지 하나의 스레드만 코드를 실행할 수 있는 코드 영역
  • 락: 임계 영역을 포함하고 있는 객체에 접근할 수 있는 권한
  • withdraw() 메서드를 임계 영역으로 설정해야함
  • 특정 코드 구간을 임계 영역으로 설정할 때에는 synchronized라는 키워드를 사용. 두 가지 방법으로 사용 가능
  1. 메서드 전체를 임계 영역으로 지정하기
    withdraw()가 호출되면 withdraw()를 실행하는 스레드는 withdraw()가 포함된 객체의 락을 얻으며 해당 스레드가 락을 반납하기 이전에 다른 스레드는 해당 메서드의 코드를 실행하지 못하기 됨
class Account {
	...
	public synchronized boolean withdraw(int money) {
	    if (balance >= money) {
	        try { Thread.sleep(1000); } catch (Exception error) {}
	        balance -= money;
	        return true;
	    }
	    return false;
	}
}
  1. 특정한 영역을 임계 영역으로 지정하기
    특정 영역을 임계 영역으로 지정하려면 아래와 같이 synchronized 키워드와 함께 소괄호(()) 안에 해당 영역이 포함된 객체의 참조를 넣고, 중괄호({})로 블럭을 열어 블럭 내에 코드를 장성함. 임계 영역으로 설정한 블럭의 코드로 코드 실행 흐름이 진입할 때, 해당 코드를 실행하고 있는 스레드가 this에 해당하는 객체의 락을 얻고, 배타적으로 임계 영역 내의 코드를 실행함.
class Account {
	...
	public boolean withdraw(int money) {
			synchronized (this) {
			    if (balance >= money) {
			        try { Thread.sleep(1000); } catch (Exception error) {}
			        balance -= money;
			        return true;
			    }
			    return false;
			}
	}
}

0개의 댓글