JAVA/about java

[thread] 스레드 우선순위와 동기화를 하는 이유

dev_rosieposie 2023. 8. 31. 21:37

시작하기 전 ...

해당 포스팅은 이전 포스팅(멀티 스레드와 객체 생성)에 이어집니다

 

[thread] 멀티 스레드와 작업 스레드 생성 방법

Goal multi thread에 대해 알아본다 작업스레드의 생성과 실행을 해본다 Thread 클래스로부터 직접 생성 Thread 하위 클래스로부터 생성 Thread 이름 multi-thread 란 ? 프로세스와 스레드 프로세스란, 운영체

dev-rosiepoise.tistory.com

Goal

  1. 스레드 우선순위에 대해 알아본다
  2. 스레드 동기화를 하는 이유를 이해한다.

 스레드의 우선순위  

멀티 스레드는 동시성 또는 병렬성으로 실행된다. 

동시성멀티 작업을 위해 하나의 코어에서 멀티 스레드가 번갈아가며 실행하는 성질을 말하며,

병렬성 멀티 작업을 위해 멀티 코어에서 개별 스레드를 동시에 실행하는 성질을 말한다.

동시성과 병렬성

싱글 코어 cpu를 이용한 멀티 스레드 작업은 병렬적으로 실행되는 것처럼 보이나, 사실 번갈아 실행하는 동시성이다. 워낙 빠르다보니 병렬성으로 보일 뿐이다.

 

스케쥴링

스레드의 개수가 코어의 수보다 많을 경우, 스레드를 어떤 순서에 의해 동시성으로 실행할 것인지 결정하는데 이것을 스케쥴링이라고 한다.

스레드 스케쥴링에 의해 스레드들은 아주 짧은 시간에 번갈아가며 그들의 run()메소드를 조금씩 실행한다.

  • 우선순위 방식은 우선순위가 높은 스레드가 실행 상태를 더 많이 가지고도록 스케줄링하는 것을 말한다.
  • 순환할당 방식은 시간 할당량을 정해서 하나의 스레드를 정해진 시간만큼 실행하고 다시 다른 스레드를 실행하는 방식을 말한다.

우선순위 방식

thread.setPriority(Thread.MIN_PRIORITY);
thread.setPriority(Thread.NORM_PRIORITY);
thread.setPriority(Thread.MAX_PRIORITY);
  • 1에서부터 10까지 부여되며, 1이 가장 낮고 10이 가장 높다
  • 우선순위를 부여하지 않으면 기본적으로 5 우선순위를 할당받는다
  • 우선순위 변경은 Thread 클래스가 제공하는 setPriority() 메소드를 이용하면 된다
  • 싱글 코어에서 동시성으로 실행할 경우, 우선순위가 높은 스레드가 실행 기회를 더 많이 가지기 때문에 작업을 빨리 끝낸다.
  • 쿼드 코어일 경우 4개의 스레드가 병렬성으로 실행될 수 있기 때문에 4개 이하의 스레드를 실행할 경우 우선순위 방식이 크게 영향을 미치지 못하고 최소 5개 이상의 스레드가 실행되어야 우선순위의 영향을 받는다.
public class CalcThread extends Thread{
    public CalcThread(String name) {
        setName(name);
    }

    @Override
    public void run() {
        for(int i=0; i<2000000000; i++){
        }
        System.out.println(getName());
    }
}
public class PriorityExample {

    public static void main(String[] args){

        for(int i=1; i<=10; i++){
            Thread thread = new CalcThread("thread"+i);
            if(i != 10){
                // 가장 낮은 우선순위 설정
                thread.setPriority(Thread.MIN_PRIORITY);
            }else{
                // 가장 높은 우선순위 설정
                thread.setPriority(Thread.MAX_PRIORITY);
            }
            thread.start();
        }
    }
}

결과

thread7
thread4
thread6
thread5
thread10
thread8
thread1
thread3
thread2
thread9

 스레드 동기화를 해야하는 이유 

공유 객체를 사용할 때 주의할 점

멀티 스레드에서는 스레드들이 객체를 공유해서 작업해야 할 경우, 스레드 A를 사용하던 객체가 스레드 B에 의해 상태가 변경될 수 있기에 의도하지 않은 결과를 얻을 수 있다. 

 

백문이 불여일타.

예제 코드로 이해하자.

 

공유 객체

public class Calculator {
    private int memory;

    public int getMemory() {
        return memory;
    }

    // 계산기에 값을 저장하는 메소드
    public void setMemory(int memory) {

        // 매개값을 memory 필드에 저장
        this.memory = memory;
        try{
            Thread.sleep(2000);
        }catch (InterruptedException e){}
        System.out.println(Thread.currentThread().getName() +": "+this.memory);
    }
}
public class User1 extends Thread{
    private Calculator calculator;

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

    @Override
    public void run() {
        calculator.setMemory(100);
    }
}
public class User2 extends Thread{
    private Calculator calculator;

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

    @Override
    public void run() {
        calculator.setMemory(50);
    }
}
public class MainThreadExample {

    public static void main(String[] args){
        Calculator calculator = new Calculator();

        User1 user1 = new User1();
        user1.setCalculator(calculator);
        user1.start();

        User2 user2 = new User2();
        user2.setCalculator(calculator);
        user2.start();
    }
}

결과

50

50

 

=> User1 스레드가 Calculator 객체의 memory 필드에 100을 먼저 저장하고 2초간 일시 정지 상태가 된다. 그동안 User2 스레드가 memory 필드값을 50으로 변경한다. 2초가 지나 User1 스레드가 다시 실행 상태가 되어 memory 필드의 값을 출력하면 User2가 저장한 50이 나온다.

 

동기화 메소드 및 동기화 블록

스레드가 사용 중인 객체를 다른 스레드가 변경할 수 없도록 스레드 작업이 끝날 때까지 객체에 잠금을 걸어 다른 스레드가 사용할 수 없도록 해야 한다. 멀티 스레드 프로그램에서 단 하나의 스레드만 실행할 수 있는 코드영역을 임계 영역이라고 한다.

자바는 임계 영역을 지정하기 위해 synchronized 메소드와 동기화 블록을 제공한다.

 

동기화 메소드를 만드는 방법 

public synchronized void method() {
	임계 영역; // 단 하나의 스레드만 실행
}
  • 메소드 전체 내용이 임계 영역이므로 스레드가 동기화 메소드를 실행하는 즉시 객체는 잠금이 일어나고, 스레드가 동기화 메소드를 실행 종료하면 잠금이 풀린다. 

동기화 블록을 만드는 방법 

public void method() {
    // 여러 스레드가 실행 가능 영역
    ...
    synchronized(공유객체){		// 공유 객체가 자기 자신이면 this 넣을 수 있다
    	임계 영역 // 단 하나의 스레드만 실행 - 동기화 블록
    }
    // 여러 스레드가 실행 가능 영역
}
  • 메소드의 일부 내용만 임계 영역으로 만들 때 동기화 블록을 사용한다.
  • 동기화 블록의 외부 코드들은 여러 스레드가 동시에 실행할 수 있지만, 동기화 블록의 내부 코드는 임계 영역이므로 한 번에 한 스레드만 실행할 수 있고 다른 스레드는 실행 할 수 없다.

동기화 메서드로 수정된 공유 객체

public class Calculator {
    private int memory;

    public int getMemory() {
        return memory;
    }

    public synchronized void setMemory(int memory) {

        // 매개값을 memory 필드에 저장
        this.memory = memory;
        try{
            Thread.sleep(2000);
        }catch (InterruptedException e){}
        System.out.println(Thread.currentThread().getName() +": "+this.memory);
    }
}
  1. User1 스레드는 Calculator 객체의 동기화 메소드인 setMemory()를 실행하는 순간 Calculator 객체를 잠근다
  2. 메인 스레드가 User2 스레드를 실행시키지만, 동기화 메소드인 setMemory()를 실행시키지 못하고, User1이 setMemory()를 모두 실행할 동안 대기해야 한다.
  3. User1이 setMemory()를 모두 실행하면 User2 스레드가 setMemory()를 실행한다.

=> thread-safe

 

동기화 블록으로 수정된 공유 객체

public class Calculator {
    private int memory;

    public int getMemory() {
        return memory;
    }

    public void setMemory(int memory) {
        
        synchronized (this){	// 공유 객체인 Calculator의 참조(잠금 대상)
            this.memory = memory;
            try{
                Thread.sleep(2000);
            }catch (InterruptedException e){}
            System.out.println(Thread.currentThread().getName() +": "+this.memory);   
        }
    }
}
  1. 스레드가 동기화 블록으로 들어가면 this(Calculator 객체)를 잠그고, 동기화 블록을 실행한다.
  2. 동기화 블록은 모두 실행할 때까지 다른 스레드들은 this(Calculator 객체)의 모든 동기화 메소드 또는 동기화 블록을 실행할 수 없다.

=> thread-safe

 

참고

Thread-safe하게 만드는 방법은 이외에도 여러가지가 있다. 다음 포스팅 참고

 

[Singleton Pattern] 싱글톤 패턴

Goal Singleton이 무엇인지 이해한다 Singleton을 생성하는 방법에 대해 알아보고 이해한다 Singleton Pattern 싱글턴 패턴이란 인스턴스를 하나만 만들어 사용하기 위한 패턴이다. 프로그램 시작부터 종료

dev-rosiepoise.tistory.com

 

 

[Thread safe] Thread safe / unsafe 기준과 코드 설계 방법

Goal Thread safe이란 무엇인지 알아본다 Thread safe vs Thread unsafe Thread safe한 코드 설계하는 방법을 알아본다 Thread safe 여러 개의 스레드에서 동시에 해당 코드를 실행하더라도 문제가 발생하지 않고,

dev-rosiepoise.tistory.com

 

 

참고

이것이 자바다 by 신용권