sussa
컴퓨터가 할 일은 컴퓨터가.
sussa
전체 방문자
오늘
어제

블로그 메뉴

    • 분류 전체보기 (58)
      • Language (58)
        • JAVA (58)

    공지사항

    인기 글

    태그

    • DI
    • binaryTree
    • java
    • BinarySearchTree
    • 상속
    • 캡슐화
    • Web
    • Class
    • 자료구조
    • inheritance
    • Method
    • Stream
    • spring
    • 재귀
    • tree
    • Database
    • CSS
    • recursion
    • Til
    • 조건문
    • Instance
    • HTML
    • 웹개발종합반
    • 최종연산
    • Algorithm
    • 개발자
    • 다형성
    • singleton
    • 스파르타코딩클럽
    • Thread

    최근 댓글

    최근 글

    티스토리

    hELLO · Designed By 정상우.
    sussa

    컴퓨터가 할 일은 컴퓨터가.

    Language/JAVA

    Java Thread_03

    2022. 9. 5. 20:12

    🎯 목표 :  Thread의 동기화의 개념과 관련된 메소드의 활용

    📒 Thread의 동기화

    • 진행중인 작업을 다른 쓰레드가 간섭하지 못하게 막는것.
    • 멀티쓰레드 프로세스에서 다른 쓰레드의 작업에 영향을 미칠수가 있으며,
    • 진행중인 작업이 다른 쓰레드에게 간섭받지 않게 하려면 동기화가 필요하다.
    • 동기화를 하려면 간섭받지 않아야 하는 문장들을 임계영역으로 설정하고 임계영역은 락을 얻은 단 하나의 쓰레드만 출입 가능하다(객체 1개마다 1개의 락을 가지고 있다.)

    📌 synchronized

    public synchronized void sum() {
    	// 임계영역
    }
    
    synchronized(객체의 참조변수) {
    	//임계영역
    }
    • 위 코드와 같이 두가지 방법으로 임계영역을 설정할수 있다.
    • 아래 예제는 잔고에서 출금하는 것을 예시로 들어서 작성했다.
     public class SynchronizationStudy {
        public static void main(String args[]) {
            Runnable r = new RunnableEx();
            new Thread(r).start(); // 출금을 하는 쓰레드 2개를 실행한다.
            new Thread(r).start();
        }
    }
    
    class Account2 {
        private int balance = 1000;
    
        public  int getBalance() {
            return balance;
        }
    
        public void withdraw(int money){
            // 1초동안 money값을 받아 balance에서 빼고 출력한다.
            if(balance >= money) { // balance가 money보다 크거나 같을때 까지 실행한다.
                try { Thread.sleep(1000);} catch(InterruptedException e) {}
                balance -= money;
            }
        }
    }
    
    class RunnableEx implements Runnable {
        Account2 a = new Account2(); // Account2의 객체 생성
    
        public void run() {
            while(a.getBalance() > 0) {
                int money = (int)(Math.random() * 3 + 1) * 100; // 100~300까지를 정의해준다.
                a.withdraw(money); // 메소드 호출
                System.out.println("balance:"+a.getBalance());// balance 출력
            }
        }
    }
    • 잔고를 계산해주는 Account2 클래스와 출금하는 쓰레드 클래스 RunnableEx를 만들었다.
    • 동기화 충돌을 재현하기위해 main 메소드에서는 두개의 쓰레드를 실행시켰고,
    • 실행시 1000값을 가진 balance는 두개의 쓰레드에 의해 출금이 된다.
    balance:700
    balance:700
    balance:400
    balance:400
    balance:100
    balance:-100
    • 코드에서 쓰레드 클래스 run의 while 조건에는 balance의 값이 0보다 클때까지 동작하도록 구현되어 있는데,
    • 출력값을 보면 balance의 값이 -100 으로 출력되어 있다.
    • 1초동안 withdraw 메소드에서 money값을 받아 balance의 값에 적용을 하고 있는 도중에 쓰레드 클래스의 run메소드에서 Account2클래스의 balance 값을 읽어들여 계산을하고 출력을 했기 때문에 발생한 현상이다.
    • withdraw 메소드에 임계영역을 설정해줘서 출력값을 보며 동기화에 대한 학습을 할수 있다.
     public class SynchronizationStudy {
        public static void main(String args[]) {
            Runnable r = new RunnableEx();
            new Thread(r).start(); // 출금을 하는 쓰레드 2개를 실행한다.
            new Thread(r).start();
        }
    }
    
    class Account2 {
        private int balance = 1000;
    
        public  int getBalance() {
            return balance;
        }
    
        public synchronized void withdraw(int money){
            // 1초동안 money값을 받아 balance에서 빼고 출력한다.
            if(balance >= money) { // balance가 money보다 크거나 같을때 까지 실행한다.
                try { Thread.sleep(1000);} catch(InterruptedException e) {}
                balance -= money;
            }
        }
    }
    
    class RunnableEx implements Runnable {
        Account2 a = new Account2(); // Account2의 객체 생성
    
        public void run() {
            while(a.getBalance() > 0) {
                int money = (int)(Math.random() * 3 + 1) * 100; // 100~300까지를 정의해준다.
                a.withdraw(money); // 메소드 호출
                System.out.println("balance:"+a.getBalance());// balance 출력
            }
        }
    }
    balance:800
    balance:600
    balance:300
    balance:100
    balance:100
    balance:100
    balance:100
    balance:100
    balance:100
    balance:100
    balance:0
    balance:0
    • 임계영역을 설정 후에 출력값을 보면,  balance 의 값이 100에서 계속 출력된 것을 볼수 있다. 이것은 쓰레드 클래스의 run 메소드에 있는 while 반복문이 돌고는 있지만 withdraw 메소드의 조건에 부합하지 못하여 balance 값이 초기화 되지 않고 그대로 출력되서 발생한 것이다.
    • 즉 money의 값이 100이 될때까지 =(락을 얻어 임계영역의 데이터를 읽을수 있을때까지) 반복해서 동작했다는 것이다.
    • withdraw메소드에 임계영역을 설정 해 줌으로써 멀티 쓰레드중 하나의 쓰레드만 withdraw에서 초기화 된 balance값을 읽어 들일수 있게 만들었다.

    📌 wait(), notify()

    • synchronized로 동기화 해서 공유 데이터를 보호하는 방법을 확인 했다.
    • 하지만, 바로 위 출력값 처럼 money의 값이 100이 될때까지 쓰레드는 반복해서 동작하고 있었다
    • 즉, 락을 얻어 임계영역에 들어가 필요한 데이터를 읽어오고 동작을 하기위해 쓰레드가 기다리고 있었다는 말과 같다.
    • 무의미한 작업을 하고 있었으며 이것은 다른 쓰레드의 작업에 문제를 발생 시킬수도 있다.
    • 위와 같은 문제를 해결하기 위해 wait()와 notify()를 활용할수 있다.
    • Object 클래스에 정의되어 있으며, 동기화 블록 내에서만 사용할 수 있다.
    • wait() = 객체의 lock(락)을 풀고 쓰레드를 해당 객체의 waiting pool에 넣는다.
    • notify() = waiting pool 에서 대기중인 쓰레드 중의 하나를 깨운다.
    • notifyAll() = waiting pool 에서 대기중인 모든 쓰레드를 깨운다.

    👉예제

    • 아래 예제는 두개의 쓰레드로 작업을 수행하는 예제다.
    • synchronized로 임계영역만 설정후에 실행 시켰을때, 번갈아 가며 작업 수행이 아닌, 하나의 쓰레드가 몰아서 작업을 했다가, 다시 다른 쓰레드가 작업을 몰아서 했다가 반복을 하고 있다. 제일 밑의 출력 값을 보고 확인할수 있다.
    public class WaitNotify_1 {
        public static void main(String[] args) throws InterruptedException {
            WorkPool work = new WorkPool();
            Thread a = new Thread(new WorkThread01(work));
            Thread b = new Thread(new WorkThread02(work));
            a.start();
            b.start();
            Thread.sleep(10000);
            System.out.println("End of Work");
            System.exit(0);
        }
    }
    class WorkPool {
        public synchronized void work_A() {
            System.out.println("Thread01 Working....");
            try { Thread.sleep(400); } catch (Exception e) {}
        }
        public synchronized void work_B() {
            System.out.println("Thread02 Working....");
            try { Thread.sleep(400); } catch (Exception e) {}
        }
    }
    class WorkThread01 implements Runnable{
        private WorkPool work;
        WorkThread01(WorkPool work) {
            this.work = work;
        }
        public void run(){
            for(int i=0; i<10;i++){
                work.work_A();
            }
        }
    }
    class WorkThread02 implements Runnable{
        private WorkPool work;
        WorkThread02(WorkPool work) {
            this.work = work;
        }
        public void run(){
            for(int i=0; i<10;i++){
                work.work_B();
    
            }
        }
    }
    출력값
    Thread01 Working....
    Thread02 Working....
    Thread02 Working....
    Thread01 Working....
    Thread01 Working....
    Thread02 Working....
    Thread02 Working....
    Thread02 Working....
    Thread02 Working....
    Thread02 Working....
    Thread02 Working....
    Thread02 Working....
    Thread02 Working....
    Thread01 Working....
    Thread01 Working....
    Thread01 Working....
    Thread01 Working....
    Thread01 Working....
    Thread01 Working....
    Thread01 Working....
    End of Work
    • synchronized로 임계영역만 설정하고 작업하면, 서로의 작업 영역에 영향을 미칠수는 없지만, 작업의 효율이 떨어지는 단점이 있다. 이런부분을 보완하기 위해 wait()와 notify()를 활용한다. 아래 예제와 출력값을 보면, 효과적으로 작업을 반복하며 프로그램이 실행된 것을 확인 할수 있다.
    public class WaitNotify_1 {
        public static void main(String[] args) throws InterruptedException {
            WorkPool work = new WorkPool();
            Thread a = new Thread(new WorkThread01(work));
            Thread b = new Thread(new WorkThread02(work));
            a.start();
            b.start();
            Thread.sleep(10000);
            System.out.println("End of Work");
            System.exit(0);
        }
    }
    class WorkPool {
        public synchronized void work_A() {
            System.out.println("Thread01 Working....");
            try { Thread.sleep(400); } catch (Exception e) {}
            notify();
            try { wait(); } catch (InterruptedException e){}
        }
        public synchronized void work_B() {
            System.out.println("Thread02 Working....");
            try { Thread.sleep(400); } catch (Exception e) {}
            notify();
            try { wait(); } catch (InterruptedException e){}
        }
    }
    class WorkThread01 implements Runnable{
        private WorkPool work;
        WorkThread01(WorkPool work) {
            this.work = work;
        }
        public void run(){
            for(int i=0; i<10;i++){
                work.work_A();
            }
        }
    }
    class WorkThread02 implements Runnable{
        private WorkPool work;
        WorkThread02(WorkPool work) {
            this.work = work;
        }
        public void run(){
            for(int i=0; i<10;i++){
                work.work_B();
            }
        }
    }
    출력값
    Thread01 Working....
    Thread02 Working....
    Thread01 Working....
    Thread02 Working....
    Thread01 Working....
    Thread02 Working....
    Thread01 Working....
    Thread02 Working....
    Thread01 Working....
    Thread02 Working....
    Thread01 Working....
    Thread02 Working....
    Thread01 Working....
    Thread02 Working....
    Thread01 Working....
    Thread02 Working....
    Thread01 Working....
    Thread02 Working....
    Thread01 Working....
    Thread02 Working....
    End of Work

    'Language > JAVA' 카테고리의 다른 글

    Java Lambda Expression(람다식)_02  (0) 2022.09.07
    Java Lambda Expression(람다식)_01  (1) 2022.09.07
    Java Thread_02  (0) 2022.09.05
    Java Thread_01  (0) 2022.09.04
    Java Annotation  (1) 2022.09.04
      'Language/JAVA' 카테고리의 다른 글
      • Java Lambda Expression(람다식)_02
      • Java Lambda Expression(람다식)_01
      • Java Thread_02
      • Java Thread_01
      sussa
      sussa
      Efforts to take on a challenge

      티스토리툴바