[24.04.30] 내일배움캠프 12일차 JAVA TIL - Thread, 모던 자바

2024. 4. 30. 20:36T.I.L

오늘 한 일

  • 5주차 강의 수강

 


수기를 남기기에 앞서 교육 내용이 많아, 평소 헷갈렸던 개념 위주로 T.I.L을 작성했다.

 

wait와 notify

: 두 함수는 한 쌍으로 쓰인다.

sync로 침범을 막은 코드를 수행하다가 더이상 진행할 상황이 아니면, lock을 반납하고 wait()함수를 사용해 

waiting pool에서 대기를 한다.

그러다 notify(통제) 함수가 호출되면 다시 나와서 작업을 수행한다.

import java.util.*;

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) {
                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() + "full 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() + "empty 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();
            }
        }

    }
}

 

하지만 해당 코드에서는 특정 쓰레드에 wait과 notify를 걸 수는 없습니다.

그래서 등장한게 reentrantlock입니다. 같은 스레드가 이미 락을 가지고 있더라도 락을 유지하며 계속 실행할 수 있어 데드락을 방지해줍니다.

 

condition의 await와 signal을 사용하면 스레드를 지정하여 lock을 걸고 풀 수 있습니다.

package week05.thread.condition;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import java.util.*;

public class Main {
    public static final int MAX_TASK = 5;
    private ReentrantLock lock = new ReentrantLock();

    // lock으로 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(); // 임계영역 끝
        }
    }
}

 

아직 쓰레드 개념이 어려워 이건 다시 수강하긴 해야할거같습니다.

학부때도 리눅스 signal때문에 꽤나 고생했는데 그때 제대로 짚고 넘어가지 못한게 아쉽네요..

이번 기회에는 마스터 하도록 노력해야겠습니다.

 

 


모던 자바

 

함수형 프로그래밍에 익숙해지자.

함수형 프로그래밍 : 함수를 일급 값으로 사용 (함수를 변수처럼 사용)

순수 메소드 : 인자에 따라 값을 예측할 수 있는 경우 (람다식 구현 가능)

람다 : () -> 

        //ParkingLot
        weekendparkingLot.addAll(parkCars(carsWantToPark,(Car car)-> car.hasParkingTicket() && car.getParkingMoney() > 1000));

 

스트림 -> map, filter ...

컬렉션의 반복을 멋지게 처리하는 일종의 기능

스트림을 사용하는 방법
 
스트림을 받아오기 (.stream())
 
 
carsWantToPark.stream()
 
스트림 가공하기
 
 
.filter((Car car) -> car.getCompany().equals("Benz"))
 
스트림 결과 만들기
 
 
.toList();

 map의 역할은

예를 들어 [1,2,3,4] 를 [2,4,6,8]로 가공할 때 사용하는 함수로 일회성이다.