새소식

TIL

[TIL] 240501 <자바> 쓰레드

  • -

 

 

<프로세스> : 운영체제로부터 자원을 할당받는 작업의 단위

OS 위에서 실행되는 모든 프로그램은 OS가 만들어준 프로세스에서 실행
(프로세스를 통해 Code, Data, Memory (Stack, Heap)를 OS로부터 할당받음)

 

<프로세스 구조>

1. Code : Java main 메소드와 같은 코드

2. Data : 프로그램이 실행 중 저장할 수 있는 저장 공간
               (전역변수, 정적 변수(static), 배열 등 초기화된 데이터를 저장하는 공간)

3. Memory(메모리 영역)

  • Stack : 지역변수, 매개변수 리턴 변수를 저장하는 공간
  • Heap : 프로그램이 동적으로 필요한 변수를 저장하는 공간 (new(), mallock())

 

 

 

<쓰레드> : 프로세스가 할당받은 자원을 이용하는 실행의 단위

  • 프로세스가 작업 중인 프로그램에서 실행 요청이 들어오면 쓰레드를 만들어 명령 처리
  • 프로세스 안에는 여러 쓰레드들 존재
  • 쓰레드들은 실행을 위한 프로세스 내 주소 공간이나 메모리 공간(Heap)을 공유받음
  • 쓰레드들은 각각 명령 처리를 위한 자신만의 메모리 공간(Stack)도 할당받음

 

  • Java 프로그램을 실행하면 JVM 프로세스 위에서 실행됨
  • Java 프로그램 쓰레드는 Java Main 쓰레드부터 실행되며 JVM에 의해 실행됨

 

 

 

싱글 쓰레드

Java 프로그램의 경우 main() 메서드만 실행시켰을 때

  • Java 프로그램 main() 메서드의 쓰레드를 '메인 쓰레드'라고 부름
  • JVM의 메인 쓰레드가 종료되면, JVM도 같이 종료됨

 

 

멀티 쓰레드

하나의 프로세스는 여러 개의 쓰레드를 가질 수 있으며 이 쓰레드들은 프로세스의 자원을 공유함
Java 프로그램은 메인 쓰레드 외에 다른 작업 쓰레드들을 생성하여 여러 개의 실행 흐름 생성가능

여러개의 쓰레드는 서로 번갈아가면서 수행되며
실행 순서나 소요 시간은 OS의 스케줄러가 처리하기 때문에 알 수 없음

 

<멀티 쓰레드 장점>

  • 여러 개의 쓰레드를 통해 여러 개의 작업을 동시가능으로 성능이 좋아짐
  • 스택을 제외한 모든 영역에서 메모리를 공유하기 때문에 자원을 보다 효율적으로 사용가능
  • 응답 쓰레드와 작업 쓰레드를 분리하여 빠른 응답 가능 (비동기)

 

<멀티 쓰레드 단점>

  • 동기화 문제
    : 프로세스의 자원을 공유하면서 작업을 처리하기 때문에 자원을 서로 사용하려고 하는 충돌이 발생
  • 교착 상태(데드락)
    : 둘 이상의 쓰레드가 서로의 자원을 원하는 상태가 되었을 때 서로 작업이 종료되기만을 기다리며 작업을 더 이상 진행하지 못하게 되는 상태

 

 


자바에서의 쓰레드 구현 및 실행

 

Thread 클래스 상속받아 구현

public class TestThread extends Thread {  //Thread클래스 상속
	@Override  //오버라이드
	public void run() {  // 쓰레드가 수행할 작업 작성
		for (int i = 0; i <100; i++) {
			System.out.print("*");
        }
	}
}



public class Main {
    public static void main(String[] args) {
    
    	/* 싱글 쓰레드 */
        TestThread thread = new TestThread(); // 쓰레드 생성
        thread.start();  // 쓰레드 실행
    }
}

 

 

Runnable 인터페이스 사용하여 구현

Java는 다중 상속을 지원하지 않기 때문에 Thread를 상속받아 처리하는 방법은 확장성이 매우 떨어지지만

Runnable은 인터페이스이기 때문에 다른 필요한 클래스를 상속받을 수 있어 확장성에 매우 유리함

public class TestRunnable implements Runnable {  //Runnable인터페이스
	@Override
	public void run() {  // 쓰레드 수행작업
		for (int i = 0; i <100; i++) {
            System.out.print("$");
        }
	}
}


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

        Runnable run = new TestRunnable();
        
        /* 싱글 쓰레드 */
        Thread thread = new Thread(run); // 쓰레드 생성

        thread.start(); // 쓰레드 실행
    }
}

 

 

람다식의 사용

public class Main {
    public static void main(String[] args) {
    
    	// 람다식의 사용
        Runnable task = () -> {  //쓰레드가 수행할 작업
            int sum = 0;
            for (int i = 0; i < 50; i++) {
                sum += i;
                System.out.println(sum);
            }
            
            // Thread.currentThread().getName() : 현재 실행 중인 쓰레드의 이름 반환
            System.out.println(Thread.currentThread().getName() + " 최종 합 : " + sum);
        };

		/* 멀티 쓰레드 */
        Thread thread1 = new Thread(task);  //쓰레드 생성
        thread1.setName("thread1");  //쓰레드에 이름부여
        Thread thread2 = new Thread(task);  //쓰레드 생성
        thread2.setName("thread2");  //쓰레드에 이름부여

        thread1.start();  //쓰레드 실행
        thread2.start();  //쓰레드 실행
    }
}

 

 


데몬 쓰레드

보이지 않는 곳(background) 에서 실행되는 낮은 우선순위를 가진 쓰레드
(ex. 메모리 영역을 정리해 주는 가비지 컬렉터(GC))

public class Main {
    public static void main(String[] args) {
        Runnable demon = () -> {
            for (int i = 0; i < 1000000; i++) {
                System.out.println("demon");
            }
        };

        Thread thread = new Thread(demon);
        thread.setDaemon(true); // true로 설정시 데몬스레드로 실행됨

        thread.start();  //쓰레드 실행

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

demon 쓰레드는 우선순위가 낮고 다른 쓰레드가 모두 종료되면 강제 종료 당하기 때문에
main() 쓰레드의 task가 100번이 먼저 찍히면, demon 쓰레드는 종료되어 1000000번 수행되지 못하고 종료됨.

 

 

 

사용자 쓰레드

보이는 곳(foregorund)에서 실행되는 높은 우선순위를 가진 쓰레드, 프로그램 기능을 담당
(ex. 메인 쓰레드)


JVM 은 사용자 쓰레드의 작업이 끝나면 데몬 쓰레드도 자동으로 종료시켜버림!!!

 

 


쓰레드 우선순위

쓰레드는 생성될 때 우선순위가 정해짐 (직접 지정 / JVM에 의한 지정)

  • 최대 우선순위 (MAX_PRIORITY) = 10
  • 최소 우선순위 (MIN_PRIORITY) = 1
  • 보통 우선순위 (NROM_PRIORITY) = 5 (기본값)
  • 자세하게는 1~10 사이의 숫자로 지정 가능
  • OS가 아니라 JVM에서 설정한 우선순위임

우선순위가 높다고 반드시 쓰레드가 먼저 종료되는건 아님!!!

public class Main {
    public static void main(String[] args) {
        Runnable task1 = () -> {
            for (int i = 0; i < 100; i++) {
                System.out.print("$");
            }
        };

        Runnable task2 = () -> {
            for (int i = 0; i < 100; i++) {
                System.out.print("*");
            }
        };

        Thread thread1 = new Thread(task1);  //쓰레드 생성
        thread1.setPriority(8);  //쓰레드 우선순위 설정
        int threadPriority = thread1.getPriority();  //쓰레드 우선순위 반환
        System.out.println("threadPriority = " + threadPriority);

        Thread thread2 = new Thread(task2);  //쓰레드 생성
        thread2.setPriority(2);  //쓰레드 우선순위 설정

        thread1.start();  //쓰레드 실행
        thread2.start();  //쓰레드 실행
    }
}
  • setPriority(우선순위) : 쓰레드 우선순위 설정
  • getPriority() : 쓰레드 우선순위 반환

 

 

 

쓰레드 그룹

서로 관련이 있는 쓰레드들을 그룹으로 묶어서 다룰 수 있음

  • JVM 이 시작되면 system 그룹이 생성되고 쓰레드들은 기본적으로 system 그룹에 포함
  • 메인 쓰레드는 system 그룹 하위에 있는 main 그룹에 포함
  • 쓰레드 그룹을 지정받지 못한 쓰레드는 자신을 생성한 부모 쓰레드의 그룹과 우선순위를 상속받게 되는데
    쓰레드 그룹을 지정하지 않으면 해당 쓰레드는 자동으로 main 그룹에 포함

  • Thread(ThreadGroup group, Runnable target, String name)
public class Main {
    public static void main(String[] args) {
        Runnable task = () -> {
            while (!Thread.currentThread().isInterrupted()) {
                try {
                    Thread.sleep(1000);
                    System.out.println(Thread.currentThread().getName());
                } catch (InterruptedException e) {
                    break;
                }
            }
            System.out.println(Thread.currentThread().getName() + " Interrupted");
        };

        // ThreadGroup 클래스로 객체 생성
        ThreadGroup group1 = new ThreadGroup("Group1");

        // Thread 객체 생성시 첫번째 매개변수로 넣어줌
        // Thread(ThreadGroup group, Runnable target, String name)
        Thread thread1 = new Thread(group1, task, "Thread 1");
        Thread thread2 = new Thread(group1, task, "Thread 2");

        // Thread에 ThreadGroup이 할당된것을 확인가능
        System.out.println("Group of thread1 : " + thread1.getThreadGroup().getName());
        System.out.println("Group of thread2 : " + thread2.getThreadGroup().getName());

        thread1.start();
        thread2.start();

        try {
            // 현재 쓰레드를 5000ms(5초)동안 정지시킴
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // interrupt() : 일시정지 상태인 쓰레드를 실행대기 상태로 만듦
        group1.interrupt();  //쓰레드 그룹에 적용

    }
}

 

 


쓰레드 상태

쓰레드는 실행과 대기를 반복하며 run() 메서드를 수행하고, run() 메서드가 종료되면 실행 멈춤

 

 

일시정지 상태에서는 쓰레드가 실행을 할 수 없는 상태가 되고,
다시 실행 상태로 넘어가기 위해서는 우선 일시정지 상태에서 실행 대기 상태로 넘어가야 함

 

 

상태 Enum 설명
객체생성 NEW 쓰레드 객체 생성. 아직 start()메서드 호출 전의 상태
실행대기 RUNNABLE 실행 상태로 언제든지 갈 수 있는 상태
일시정지 WAITING 다른 쓰레드가 통지(notify)할 때까지 기다리는 상태
TIMED_WATING 주어진 시간 동안 기다리는 상태
BLOCKED 사용하고자 하는 객체의 Lock이 풀릴 때까지 기다리는 상태
종료 TERMINATED 쓰레드의 작업이 종료된 상태

 

 

쓰레드 제어

쓰레드는 생성될 때 우선순위가 정해짐 (직접 지정 / JVM에 의한 지정)

 

sleep() : 현재 쓰레드를 지정된 시간 동안 멈추게함 (쓰레드 자기 자신에 대해서만 가능)

  • Thread.sleep(ms);

  • 예외 처리를 해야함
    • sleep 상태에 있는 동안 interrupt()를 만나면 다시 실행되기 때문에 InterruptedException이 발생할 수 있음!
  • 특정 쓰레드를 지목해서 멈추게 하는 것은 불가능!
public class Main {
    public static void main(String[] args) {
        Runnable task = () -> {
            try {
                Thread.sleep(2000);  //2초동안 Thread 쓰레드 멈추게함
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("task : " + Thread.currentThread().getName());
        };

        Thread thread = new Thread(task, "Thread");  //쓰레드 생성
        thread.start();  //쓰레드 실행


        try {
            thread.sleep(1000);  //1초동안 main 쓰레드 멈추게함
            System.out.println("sleep(1000) : " + Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

 

 


 

 

interrupt() : 일시정지 상태의 쓰레드를 실행 대기 상태로 만듦

  • Thread 클래스 내부에 interrupted 되었는지를 체크하는 boolean 변수 존재
  • 쓰레드가 start()된 후 동작하다가, interrupt()를 만나 실행하면, interrupted 상태가 true가 됨
  • isInterrupted() 메서드를 사용하여 상태 값 확인가능

 

sleep() 실행 중 interrupt()가 실행되면 예외 발생!!!

public class Main {
    public static void main(String[] args) {
        Runnable task = () -> {
            try {
            	//sleep 도중에 interrupt 발생 시 catch!!!
                Thread.sleep(1000);
                System.out.println(Thread.currentThread().getName());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("task : " + Thread.currentThread().getName());
        };

        Thread thread = new Thread(task, "Thread");
        thread.start();

        thread.interrupt();  //interrupt

        System.out.println("thread.isInterrupted() = " + thread.isInterrupted());

    }
}

 🔻 🔻 🔻 🔻 🔻 🔻 🔻 🔻 🔻 🔻

!Thread.currentThread().isInterrupted()로 interrupted 상태를 체크하여 처리 시 오류 방지가능

public class Main {
    public static void main(String[] args) {
        Runnable task = () -> {
        	//interrupted 상태를 미리 체크해서 false일때만 실행되도록 함!
            while (!Thread.currentThread().isInterrupted()) {
                try {
                    Thread.sleep(1000);
                    System.out.println(Thread.currentThread().getName());
                } catch (InterruptedException e) {
                    break;
                }
            }
            System.out.println("task : " + Thread.currentThread().getName());
        };

        Thread thread = new Thread(task, "Thread");
        thread.start();

        thread.interrupt();

        System.out.println("thread.isInterrupted() = " + thread.isInterrupted());
        
    }
}

 

 


 

 

join() : 지정한 쓰레드가 작업하는 것을 기다림 (시간 지정 가능) 

public class Main {
    public static void main(String[] args) {
        Runnable task = () -> {
            try {
                Thread.sleep(5000); // 5초
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };

        Thread thread = new Thread(task, "thread");  //쓰레드 생성

        thread.start();  //쓰레드 실행

        long start = System.currentTimeMillis();  //현재시간


		//메인메서드
        try {
            thread.join();  //thread의 작업이 끝날때까지 기다림
            
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // thread의 소요시간인 5000ms 동안 main쓰레드가 기다렸기때문에 5000이상이 출력
        System.out.println("소요시간 = " + (System.currentTimeMillis() - start));
    }
}

 

 


 

 

 

yield() : 남은 시간을 다음 쓰레드에게 양보하고 쓰레드 자신은 실행대기 상태가 됨

public class Main {
    public static void main(String[] args) {
        Runnable task = () -> {
            try {
                for (int i = 0; i < 10; i++) {  //10번(한번당 1초정지니까 총10초)
                    Thread.sleep(1000);  //1초 일시정지
                    System.out.println(Thread.currentThread().getName());
                }
            } catch (InterruptedException e) {
                Thread.yield();  
                //interrupt된 thread1은 남은 시간을 다음 쓰레드 thread2에게 양보하고 
                //thread1 자신은 실행대기 상태가 됨
            }
        };

        Thread thread1 = new Thread(task, "thread1");
        Thread thread2 = new Thread(task, "thread2");

        thread1.start();
        thread2.start();


		//메인 쓰레드
        try {  //5초 일시정지
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        thread1.interrupt();  //5초 후에 thread1 interrupt

    }
}

thread1과 thread2가 같이 1초에 한 번씩 출력되다가,
5초 뒤에 thread1에서 InterruptedException이 발생하면서 Thread.yield(); 실행되어,
thread1은 실행 대기 상태로 변경되면서 남은 시간은 thread2에게 리소스가 양보됨

 

 


 

 

 

synchronized : 남은 시간을 다음 쓰레드에게 양보하고 쓰레드 자신은 실행대기 상태가 됨

멀티 쓰레드의 경우 여러 쓰레드가 한 프로세스의 자원을 공유해서 작업하기 때문에 장애나 버그가 발생할 수 있음
한 쓰레드가 진행 중인 작업을 다른 쓰레드가 침범하지 못하도록 막는 것  -> '쓰레드 동기화(Synchronization)'

  • 동기화를 하려면 다른 쓰레드의 침범을 막아야 하는 코드들을 '임계 영역'으로 설정
  • 임계 영역에는 Lock을 가진 단 하나의 쓰레드만 출입이 가능 (한 번에 한 쓰레드만)

 

 

<임계영역 지정>

1. 메서드 전체

public synchronized void asyncSum() {
	  ...침범을 막아야하는 코드...
}

 

2. 특정 영역

synchronized(해당 객체의 참조변수) {
		...침범을 막아야하는 코드...
}

 

 

 

public class Main {
    public static void main(String[] args) {
        AppleStore appleStore = new AppleStore();

        Runnable task = () -> {
            while (appleStore.getStoredApple() > 0) {
                appleStore.eatApple();
                System.out.println("남은 사과의 수 = " + appleStore.getStoredApple());
            }

        };

        for (int i = 0; i < 3; i++) {  //3개의 쓰레드 생성 및 실행
            new Thread(task).start();
        }
    }
}

class AppleStore {
    private int storedApple = 10;

    public int getStoredApple() {
        return storedApple;
    }

    public void eatApple() {
        synchronized (this) {  //특정영역을 임계영역으로 지정
            if(storedApple > 0) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                storedApple -= 1;
            }
        }
    }
}

 

 


 

 

wait() : 실행 중이던 쓰레드는 해당 객체의 대기실(waiting pool)에서 통지를 기다림

1. 침범을 막은 코드를 수행하다가 작업을 더 이상 진행할 상황이 아니면, wait()를 호출하여 쓰레드가 Lock을 반납하고 기다리게 할수있음

2. 그러면 다른 쓰레드가 락을 얻어 해당 객체에 대한 작업을 수행할 수 있게되고

 

 

notify() : 해당 객체의 대기실(waiting pool)에 있는 모든 쓰레드 중에서 임의의 쓰레드만 통지를 받게됨

3. 추후에 작업을 진행할 수 있는 상황이 되면 notify()를 호출해서,

4. 작업을 중단했던 쓰레드가 다시 Lock을 얻어 진행할 수 있게됨

 

public class Main {
    public static String[] itemList = {
            "MacBook", "IPhone", "AirPods", "iMac", "Mac mini"
    };
    public static AppleStore appleStore = new AppleStore();
    public static final int MAX_ITEM = 5;

    public static void main(String[] args) {

        // 가게 점원
        Runnable StoreClerk = () -> {
                while (true) {
                	// Math.random() : 0~1 사이 랜덤값
                    int randomItem = (int) (Math.random() * MAX_ITEM);
                    appleStore.restock(itemList[randomItem]);  //랜덤으로 아이템 재입고
                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException ignored) {
                    }
                }
        };

        // 고객
        Runnable Customer = () -> {
                while (true) {
                    try {
                        Thread.sleep(77);
                    } catch (InterruptedException ignored) {
                    }

                    int randomItem = (int) (Math.random() * MAX_ITEM);
                    appleStore.sale(itemList[randomItem]);  //아이템 구매
                    System.out.println(Thread.currentThread().getName() + " Purchase Item " + itemList[randomItem]);
                }
        };


        new Thread(StoreClerk, "StoreClerk").start();
        new Thread(Customer, "Customer1").start();
        new Thread(Customer, "Customer2").start();

    }
}

class AppleStore {
    private List<String> inventory = new ArrayList<>();

    public void restock(String item) {  //아이템 재입고
        synchronized (this) {  //동기화
            while (inventory.size() >= Main.MAX_ITEM) {  //최대 보관 물량 이상이면
                System.out.println(Thread.currentThread().getName() + " Waiting!");
                try {
                    wait(); // 재고가 꽉 차있어서 재입고하지 않고 기다리는 중!
                    Thread.sleep(333);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            // 보관할 자리가 나면 재입고
            inventory.add(item);
            notify(); // 재입고 되었음을 고객에게 알려주기-(문제는 모든 고객이 아니라 임의의 쓰레드만 통지받음!!)
            System.out.println("Inventory 현황: " + inventory.toString());
        }
    }

    public synchronized void sale(String itemName) {  //아이템 구매
        while (inventory.size() == 0) {  //보관함에 물건이 없으면
            System.out.println(Thread.currentThread().getName() + " Waiting!");
            try {
                wait(); // 재고가 없기 때문에 고객 대기중
                Thread.sleep(333);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        while (true) {  //보관함에 물건이 있으면
            // 고객이 주문한 제품이 있는지 확인
            for (int i = 0; i < inventory.size(); i++) {
                if (itemName.equals(inventory.get(i))) {
                    inventory.remove(itemName);  //해당 제품 팔려서 보관함에서 나감
                    notify(); // 제품 하나 팔렸으니 재입고 하라고 알려주기
                    return; // 메서드 종료
                }
            }

            // 고객이 찾는 제품이 없을 경우
            try {
                System.out.println(Thread.currentThread().getName() + " Waiting!");
                wait();  //찾는 제품이 없으니 기다림
                Thread.sleep(333);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}

notify()를 사용 시 해당 객체의 대기실에 있는 모든 쓰레드가 아닌 임의의 쓰레드만 통지를 받게 되어 통지받아야하는 쓰레드가 받지 못하는 문제 발생가능성!!!

 

 


 

 

Lock 클래스 사용

synchronized 블럭으로 동기화 시 자동적으로 Lock이 걸리고 풀리지만, 같은 메서드 내에서만 Lock을 걸 수 있다는 제약 해결위해 사용.

 

<Lock 클래스의 종류>

1. ReentrantLock 

  • 재진입 가능한 Lock, 가장 일반적인 배타 Lock
  • 특정 조건에서 Lock을 풀고, 나중에 다시 Lock을 얻어 임계 영역으로 진입 가능
  • 코드의 유연성을 높일 수 있음

2. ReentrantReadWriteLock

  • 읽기를 위한 Lock과 쓰기를 위한 Lock을 따로 제공
  • 읽기에는 공유적이고, 쓰기에는 베타적인 Lock
  • 읽기 Lock이 걸려있으면 다른 쓰레드들도 읽기 Lock을 중복으로 걸고 읽기를 수행가능 (read-only)
  • 읽기 Lock이 걸려있는 상태에서 쓰기 Lock을 거는 것은 허용X (데이터 변경 방지)


3. StampedLock  

  • ReentrantReadWriteLock에 낙관적인 Lock의 기능을 추가
    • 낙관적인 Lock : 데이터를 변경하기 전에 락을 걸지 않는 것.  데이터 변경 시 충돌이 일어날 가능성이 적은 상황에서 사용
    • 낙관적인 락을 사용하면 읽기와 쓰기 작업 모두가 빠르게 처리됨.
      쓰기 작업이 발생했을 때 데이터가 이미 변경된 경우, 다시 읽기 작업을 수행하여 새로운 값을 읽어들이고, 변경 작업을 다시 수행. 이러한 방식으로 쓰기 작업이 빈번하지 않은 경우에는 낙관적인 락을 사용하여 더 빠른 처리가 가능
  • 낙관적인 읽기 Lock은 쓰기 Lock에 의해 바로 해제 가능
  • 무조건 읽기 Lock을 걸지 않고, 쓰기와 읽기가 충돌할 때만 쓰기 후 읽기 Lock.

 

 


 

 

Condition(인터페이스) : waiting pool 내의 스레드를 분리하여 특정 조건이 만족될 때만 깨우도록 가능,
                                                 ReentrantLock 클래스와 함께 사용

Condition의 await() & signal() 을 사용하여
wait() & notify()의 문제점인 waiting pool 내 쓰레드를 구분하지 못하여 특정 조건을 만족하는 스레드만 깨우기가 어렵다는 것을 해결.

public class Main {
public static final int MAX_TASK = 5;

//ReentrantLock 클래스 사용
private ReentrantLock lock = new ReentrantLock();

// lock으로 condition 생성 (Condition 인터페이스 사용)
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();

private ArrayList<String> tasks = new ArrayList<>();

// 작업 메서드
public void addMethod(String task) {
			lock.lock(); // 임계영역 시작
	
			try {
				while(tasks.size() >= MAX_TASK) {
						String name = Thread.currentThread().getName();
						System.out.println(name+" is waiting.");
						try {
							condition1.await(); // wait(); condition1(지정) 쓰레드를 기다리게함
							Thread.sleep(500);
						} catch(InterruptedException e) {}	
				}
	
				tasks.add(task);
				condition2.signal(); // notify();  기다리고 있는 condition2(지정)를 깨워줌
				System.out.println("Tasks:" + tasks.toString());
			} finally {
				lock.unlock(); // 임계영역 끝
			}
		}
	}

 

Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.