쓰레드

JONGCHAN SEO·2024년 8월 13일

자바 기본

목록 보기
8/10

쓰레드의 개념

쓰레드는 실행 중인 프로그램 내에서 "또 다른 실행의 흐름을 형성하는 주체"를 의미한다.

쓰레드를 생성하는 방법

  • 1단계 : Runnable을 구현한 인스턴스 생성
  • 2단계 : Thread 인스턴스 생성
  • 3단계 : start 메소드 호출
class MakeThreadDemo {
	public static void main(String[] args) {
    	Runnable task = () -> { // 쓰레드가 실행하게 할 내용
        	int n1 = 10;
            int n2 = 20;
            String anme = Thread.currentThread().getName();
            System.out.println(name + ": " + (n1 + n2));
        };
        
        Thread t = new Thread(task);
        t.start(); // 쓰레드 생성 및 실행
        System.out.println("End " + Thread.currentThread().getName());
}
// Runnable   void run()

쓰레드를 생성하는 두 번째 방법

  • 1단계 : Thread를 상속하는 클래스의 정의와 인스턴스 생성
  • 2단계 : start 메소드 호출
class Task extends Thread {
	public void run() { // Thread의 run 메소드 오버라이딩
    	int n1 = 10;
        int n 2 = 20;
        String name = Thread.currentThread().getName();
        System.out.println(name + ": " + (n1 + n2));
    }
 }
 
 class MakeThreadDemo2 {
 	public static void main(String[] args) {
    	Task t1 = new Task();
        Task t2 = new Task();
        t1.start();
        t2.start();
        System.out.println("End " + Thread.currentThread().getName());
    }
}

쓰레드의 동기화

쓰레드의 메모리 접근 방식과 그에 따른 문제점

class Counter {
	int count = 0; // 공유되는 변수
    
    public void increment() {
    	count++; // 첫 번째 쓰레드에 의해 실행
    }
    public void decrement() {
    	count--; // 두 번째 쓰레드에 의해 실행
  	}
    public int getCount() { return count; }
}

class MutualAccess {
	public static Counter cnt = new Counter();
    
    public static void main(String[] args) throws interruptedException {
    Runnable task1 = () -> {
    	for(int i = 0; i < 1000; i++)
        	cnt.increment(); // 값을 1 증가
    };
    
    Runnable task2 = () -> {
    	for(int i = 0; i < 1000; i++)
        	cnt.decrement(); // 값을 1 감소
    };
    Thread c1 = new Thread(task1);
    Thread c2 = new Thread(task2);
    t1.start();
    t2.start();
    t1.join();	// t1이 참조하는 쓰레드의 종료를 기다림
    t2.join();  // t2가 참조하는 쓰레드의 종료를 기다림
    System.out.println(cnt.getCount());
  }
} // 결과 -12 or 36 or ...

위 코드의 결과를 봤을때 +1000 과 -1000을 했으니 0으로 생각하는게 당연하다. 하지만 결과는 항상 바뀌게 도출된다. 왜일까?

동일한 메모리 공간에 접근을 하면 쓰레드의 코어가 두개가 있으면 동시에 접근이 가능하다는 것이다.

그 말인즉 thread1이 99를 가져와 1 증가할때 thread2도 99를 가져와 1증가시켜 둘다 100인 값을 다시 저장시킨다는 것이다. 그렇다면 이런 과정을 여러번 일으킨다면 어떻게 될까? 당연히 증가하는 숫자가 매번 다르게 형성될 것이다.

그렇다면 이 문제를 어떻게 해결할까?
둘 이상의 쓰레드가 동일한 변수에 동시에 접근해서 생긴 문제이니,
한순간에 한 쓰레드만 변수에 접근하도록 제한하면 문제는 해결된다.
(동기화)

동기화 메소드

synchronized public void increment() {
	count++;
}
synchronized public void decrement() {
	count--;
}

이와 같이 synchronized선언이 추가되면 이 메소드는 한순간에 한 쓰레드의 접근만을 허용하게 된다.

예를 들어서 이 메소드를 두 쓰레드가 동시에 호출하면, 조금이라도 빨리 호출한 쓰레드가 메소드를 실행하게 되고, 다른 한 쓰레드는 대기하고 있다가 실행이 마치면 메소드를 실행하게 된다.

하지만, 이 방법을 사용하면 메소드 전체에 동기화를 걸어야 한다는 단점이 있다. 전체에 걸게 되면 지금 당장은 메소드가 2개밖에 없어서 그렇지만, 몇백개의 메소드가 있다고 가정하면 1개의 메소드를 실행할 때 몇백개의 다른 메소드는 대기를 해야하는 비효율적인 방법으로 도출된다.

그렇기 때문에 부분적인 동기화를 하는 방법이 있다.

class Counter {
	int count = 0;
    
    public void increment() { // 동기화 블록
    	sysnchronized(this) {
        	count++;
        }
    }
    
    public void decrement() { // 동기화 블록
    	synchronized(this) {
        	count--;
        }
    }
    
    public int getCount() { return count; }
 }

여기서 this의 의미는 "이 인스턴스의 다른 동기화 블록과 더불어 동기화하겠다" 라는 뜻이다.

쓰레드 풀(Thread Pool)

쓰레드의 생성과 소멸은 그 자체로 시스템에 부담을 주는 일이다. 따라서 성능 저하로 이어질수 있다. 그래서 쓰레드 풀이라는것을 만들고 그 안에 미리 제한된 수의 쓰레드를 생성해두고 이를 재활용하는 기술이다.

class ExceutorsDemo {
	public static void main(String[] args) {
    	Runnable task = () -> { // 쓰레드에게 시킬 작업
        	int n1 = 10;
            int n2 = 20;
            String name = Thread.currentThread().getName();
            System.out.println(name + ": " + (n1 + n2));
        };
        
        ExcutorService exr = Excutors.newSingleThreadExecutor();
        exr.submit(task); // 쓰레드 풀에 작업을 전달
        
        System.out.println("End" + Thread.currentThread().getName());
        exr.shutdown(); // 쓰레드 풀과 그 안에 있는 쓰레드의 소멸

쓰레드 풀의 유형

  • newSingleThreadExecutor
    풀 안에 하나의 쓰레드만 생성하고 유지한다.

  • newFixedThreadPool
    풀 안에 인자로 전달된 수의 쓰레드를 생성하고 유지한다.

  • newCachesThreadPool
    풀 안의 쓰레드의 수를 작업의 수에 맞게 유동적으로 관리한다.

Callable & Future

profile
잘 배워가겠습니다.

0개의 댓글