[Java] 스레드 이름, 우선순위(priority), 동기화(synchronized) 실습하면서 알아보기

    스레드의 이름

    스레드는 자신의 이름을 가지고 있따. 스레드의 이름으로 디버깅할 때 어떤 스레드가 어떤 작업을 하는지 조사할 목적으로 가끔 사용된다. 메인 스레드는 "main"이라는 이름을 가지고 있고, 우리가 직접 생성한 스레드는 자동적으로 "Thread-n"이라는 이름으로 설정된다. Thread-n 대신 다른 이름으로 설정하고 싶다면 Thread 클래스의 setName() 메소드로 변경하면 된다. 


    public class ThreadNameExample {
        public static void main(String[] args) {
            Thread mainThread = Thread.currentThread();
            System.out.println("프로그램 시작 스레드 이름: " + mainThread.getName());
            
            ThreadA threadA = new ThreadA();
            System.out.println("작업 스레드 이름: " + threadA.getName());
            threadA.start();
            
            ThreadB threadB = new ThreadB();
            System.out.println("작업 스레드 이름: " + threadB.getName());
            threadB.start();
        }
    }
    cs


    public class ThreadA extends Thread {
        public ThreadA() {
            setName("ThreadA");
        }
        
        @Override
        public void run() {
            for (int i = 0; i < 5; i++) {
                System.out.println(getName() + "가 출력한 내용");
            }
        }
    }
    cs


    public class ThreadB extends Thread {
        public ThreadB() {
            setName("ThreadB");
        }
        
        @Override
        public void run() {
            for (int i = 0; i < 5; i++) {
                System.out.println(Thread.currentThread().getName() + "가 출력한 내용");
            }
        }
    }
    cs


    setName() 메소드로 ThreadA의 이름을 정해주었다 만약에 정해주지 않는다면 스레드가 생성되는 순으로 Thread-n으로 스레드 이름이 설정된다. setName()과 getName()를 사용하려면 객체를 생성해줘야 하는데 만약 객체를 생성하지 않고 이름을 출력하려면 Thread의 정적 메소드인 currentThread()를 이용하면 된다.




    스레드 우선순위

    멀티 스레드는 동시성(Concurrency) 또는 병렬성(Parallelism)으로 실행된다. 동시성은 멀티 작업을 위해 하나의 코어에서 멀티 스레드가 번갈아가며 실행하는 성질을 말하고, 병렬성은 멀티 작업을 위해 멀티 코어에서 개별 스레드를 동시에 실행하는 성질을 말한다. 싱글 코어 CPU를 이용한 멀티 스레드 작업은 병렬적으로 실행되는 것처럼 보이지만, 사실은 번갈아가며 실행하는 동시성 작업이다. 번갈아 실행하는 것이 워낙 빠르다보니 병렬성으로 보일 뿐이다. 


    스레드의 개수가 코어의 수보다 많을 경우, 스레드를 어떤 순서에 의해 동시성으로 실행할 것인가를 결정해야 하는데, 이것을 스레드 스케줄링이라고 한다. 스레드 스케줄링에 의해 스레드들은 아주 짧은 시간에 반갈아가면서 그들의 run() 메소드를 조금씩 실행한다.


    자바의 스레드 스케줄링은 우선순위(Priority) 방식과 순환 할당 (Round-Robin)방식을 사용한다. 우선순위 방식은 우선순위가 높은 스레드가 실행 상태를 더 많이 가지도록 슼케줄링하는 것을 말한다. 


    우선순위는 1 ~ 10 까지 부여된다. 1이 가장 우선순위가 낮고, 10이 가장 높다. 기본적으로 우선순위 5를 할당받고 변경하고 싶으면 setPriority() 메소드를 이용한다. 


    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());
        }
    }
    cs


    public class PriorityExample {
        public static void main(String[] args) {
            for (int i = 0; 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();
            }
        }
    }
    cs


    MIN_PRIORITY는 1이고 MAX_PRIORITY는 10이다. thread 1~9는 우선순위 1로 정해줬고, thread 10은 우선순위 10으로 정해주었다.  그래서 실행결과 thread10이 가장 먼저 처리된걸 알수 있다.




    공유 객체 사용시 주의할 점

    싱글 스레드 프로그램에서는 한 개의 스레드가 객체를 독차지해서 사용하면 되지만, 멀티 스레드 프로그램에서는 스레드들이 객체를 공유해서 작업해야 하는 경우가 있다. 이 경우 자신이 의도했던 것과는 다른 결과를 산출할 수 있다.


    public class MainThreadExample {
        public static void main(String[] args) {
            thread.Calculator calculator = new Calculator();
            
            User1 user1 = new User1();
            user1.setCalculator(calculator);
            user1.start();
            
            User2 user2 = new User2();
            user2.setCalculator(calculator);
            user2.start();
        }
    }
    cs


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

    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);
        }
    }
    cs

    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);
        }
    }
     
    cs


    User1은 메모리에 100을 저장하였고 User2는 메모리에 50을 저장하였다. setMemory() 메소드에서 값을 저장하고 나서 2초후 메모리 값을 출력 하도록 하였다.  User1 이 100을 저장하고  2초 동안 User2가 50을 저장하였기 때문에 User1도 50을 출력한 것이다. 같은 객체를 참조할 경우 이런 불상사가 일어난다.


    동기화(synchronized)

    스레드가 사용 중인 객체를 다른 스레드가 변경할 수 없도록 하려면 스레드 작업이 끝날 때까지 객체에 잠금을 걸어서 다른 스레드가 사용할 수 없도록 해야 한다. 멀티 스레드 프로그램에서 단 하나의 스레드만 실행할 수 있는 코드 영역을 임계 영역(critical section)이라고 한다. 자바는 임계 영역을 지정하기 위해 동기화(synchronized)메소드와 동기화 블록을 제공한다. 스레드가 객체 내부의 동기화 메소드 또는 블록에 들어가면 즉시 객체에 잠금을 걸어 다른 스레드가 임계 영역 코드를 실행하지 못하도록 한다. 동기화 메소드를 만드는 방법은 다음과 같이 메소드 선언에 synchronized 키워드를 붙이면 된다. synchronized 키워드는 인스턴스와 정적 메소드 어디든 붙일 수 있다.


    저 위 코드에서 setMemory() 메소드를 동기화 메소드를 만들어서 User1 스레드가 setMemory()를 실행할 동안 User2 스레드가 setMemory() 메소드를 실행할 수 없도록 했다.


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


    synchronized로 setMemory() 메소드는 임계 영역이 되었고 어떤 스레드가 실행중이면 끝날때 까지 다른 스레드는 실행될수 없다. 그렇기 때문에 2초동안에도 다른 스레드가 실행하지 못했고 User1은 100이 출력될 수 있었다.



    신용권, 이것이 자바다, 한빛미디어

    댓글

    Designed by JB FACTORY