K-Digital Training/내일배움캠프

자바 5주차

hoonssss 2023. 11. 1. 05:26
반응형
SMALL

프로세스와 쓰레드

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

실행중인 프로그램을 의미

OS가 프로그램 실행을 위한 프로세스를 할당해줄때 프로세스안에 프로그램 Code, Data, Memory영역(Stack, Heap)을 함께 할당

Code - 메소드와 같은 코드를 말함

Data

- 저장공간을 의미

- 전역변수, 정적변수, 배열 등 초기화된 데이터를 저장하는 공간

Memory(Stack, Heap)

- Stack : 지역변수, 매개변수, 리턴 변수를 저장하는 공간

- Heap : 프로그램이 동적으로 필요한 변수를 저장하는 공간(new(), mallock())

즉 각 프로그램은 프로세스를 통해 Code, Data, Memory를 OS로부터 할당받음

쓰레드 : 프로세스가 할당받은 자원을 이용하는 실행의 단위(코드실행의 흐름)

쓰레드의 생성 - 프로세스가 작업중인 프로그램에서 실행요청이 들어오면 쓰레드을 만들어 명령을 처리

쓰레드의 자원 - 프로세스 안에는 여러 쓰레드가 있고, 쓰레드들은 실행을 위한 프로세스 내 주소공간이나

메모리공간(Heap)을 공유받음

- 추가로 쓰레드들은 각 명령처리를 위한 자신만의 메모리공간(Stack)도 할당받음

.

Java에서의 쓰레드

Java프로그램을 실행하면 JVM프로세스 위에서 실행함

Java프로그램 쓰레드는 Java Main쓰레드로부터 실행되며 JVM에 의해 실행

멀티쓰레드( <-> 싱글쓰레드)

Java는 메인 쓰레드가 main() 메서드를 실행하면서 시작

메인쓰레드는 필요에 따라서 작업 쓰레드들을 생성해서 병렬로 코드 실행이 가능함

즉 Javs는 멀티쓰레드를 지원함

싱글쓰레드

프로세스 안에서 하나의 쓰레드만 실행되는 것을 의미

Java프로그램의 경우 main() 메서드만 실행시켰을때 이것을 싱글쓰레드라고 함

멀티쓰레드

프로세스 안에서 여러개의 쓰레드가 실행되는 것을 의미

메인쓰레드 외 다른 작업 쓰레드들을 생성하여 실행흐름을 만듬 즉 메인쓰레드 안에서 만듬

장점

여러개의 쓰레드을 통해 여러개의 작업을 동시에 진행함으로 성능이 좋아짐

스택을 제외한 모든 영역에서 메모리를 공유하기 때문에 자원을 효율적으로 사용함

응답 쓰레드와 작업 쓰레드를 분리하여 빠르게 응답을 줄 수있음(비동기)

단점

동기화 문제가 발생할 수 있음 (자원을 공유하면서 작업을 처리하기 때문에 자원을 서로 사용하려고 하는 충돌이 발생하는 경우를 의미)

교착 상태(데드락)이 발생할 수 있음 (둘 이상의 쓰레드가 서로의 자원을 원하는 상태가 되었을 때 서로 작업이 종료되기만을 기다리며 작업을 더 이상 진행하지 못하게 되는 상태)

    public static void main(String[] args) {
//        TestThread testThread = new TestThread(); //Thread Class 사용
//        testThread.start();

        Runnable run = new TestRunnable(); //Runnable interface사용
        Thread thread = new Thread(run);
        thread.start();
    }
----------------------------------------------------------
public class TestThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.print("*");
        }
    }
}
----------------------------------------------------------
public class TestRunnable implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.print("$");
        }
    }
}

싱글쓰레드

    public static void main(String[] args) {
        Runnable tesk = () -> {
            System.out.println("2번 -> " + Thread.currentThread().getName());
            for (int i = 0; i < 100; i++) {
                System.out.print("*");
            }
        };

        System.out.println("1번 -> " + Thread.currentThread().getName());
        Thread thread = new Thread(tesk);
        thread.setName("thread1");

        thread.start();
    }
//출력값
1번 -> main
2번 -> thread1
****...

즉 start 후 for문이 돌아감 main(1번) -> start -> for

멀티쓰레드

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

        Thread thread = new Thread(task);
        thread.setName("thread");
        Thread thread1 = new Thread(task1);
        thread1.setName("thread1");

        thread.start();
        thread1.start();
    }

걸리는 시간이나 동작을 예착할 수 없음.

출력값이 매번 다름

데몬쓰레드

백그라운드(보이지않는 곳)에서 실행되는 낮은 우선순위를 가진 쓰래드를 말함

보조적인 역할을 담당하며 대표적인 데몬쓰레드로는 메모리 영역을 정리해주는 가비지 컬렉터(GC)가 있음

    public static void main(String[] args) {
        Runnable demon = () -> {
            for (int i = 0; i < 1000000; i++) {
                System.out.print(i + ". demon");
            }
        };
        //우선순위가 낮다 -> 상대적으로 다른 쓰레드에 비해 리소스를 적게 할당받음 1000000번까지 출력안함.
        Thread thread = new Thread(demon);
        thread.setDaemon(true);//데몬쓰레드설정

        thread.start();

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

사용자쓰레드

보이는 곳(foregorund)에서 실행되는 높은 우선순위를 가진 쓰레드를 말함

프로그램 기능을 담당하며 대표적인 사용자쓰레드는 메인쓰레드가 있음

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

쓰레드 우선순위

쓰레드 작업의 중요도에 따라서 쓰레드의 우선순위를 부여할 수 있음.

(우선순위를 많이 할당받는다면 리소스를 많이 할당받고 빨리 처리할 가능성이 높아짐. 절대적인 것은 아님 우선순위가 높다고 반드시 먼저 종료되는 것은 아님 확률이 높아질 뿐)

우선순위는 직접 지정하거나 JVM에 의해 지정될 수 있음(OS가 아님)

최대우선순위 (MAX_PRIORITY) = 10

최소우선순위 (MIN_PRIORITY) = 1

보통우선순위 (NROM_PRIORITY) = 5

기본 값은 보통 우선순위임

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

쓰레드그룹

쓰레드들은 기본적으로 그룹에 포함되어 있음

(JVM이 시작되면 system 그룹이 생성되고 쓰레드들은 기본적으로 system 그룹에 포함)

메인쓰레드는 system 그룹 하위에 있는 main 그룹에 포함

모든 쓰레드들은 반드시 하나의 그룹에 포함되어 있어야 함

(따라서 쓰레드 그룹을 지정하지 않으면 해당 쓰레드는 자동으로 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();

        //start -> while -> try(5초동안 기다림, group1.interrupt객체 실행을 멈춤) -> group1.interrupt 실행 시 while문 멈춤)
        try {
            // 현재 쓰레드를 지정된 시간동안 멈추게 합니다.
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // interrupt()는 일시정지 상태인 쓰레드를 실행대기 상태로 만듭니다.(일시정지->대기상태)
        group1.interrupt(); //그룹전체에 지정함
    }

쓰레드 상태와 제어

쓰레드 상태

Life cycle

Enum -> 상수

NEW -> RUNNABLE -> WAITING, TIMED_WAITING, BLOCKED -> TERMINATED

쓰레드 제어

sleep() -> 현재 쓰레드를 지정된 시간동안 멈추게

실행 -> 일시정지

    public static void main(String[] args) {
        Runnable task = () -> {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("task : " + Thread.currentThread().getName());
        };

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

        try {
            thread.sleep(1000); // = Thread.sleep(1000)
            System.out.println("sleep(1000) : " + Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
//출력값
sleep(1000) : main //1초 지나서
task : Thread //2초지나서
대략 2초쯤 두개가 동시에 완료(병렬)
마지막 try는 메인쓰레드임

interrupt() -> 일시정지 상태인 쓰레드를 실행대기 상태로 만듬(실행 중을 실행 대기로 바꿀 수도 있음)

일시정지 -> 실행대기

실행 중 -> 실행대기

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("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작업이 끝날때까지 main쓰레드가 기다림
          } catch (InterruptedException e) {
            e.printStackTrace();
          }

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

yield()

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

    public static void main(String[] args) {
        Runnable task = () -> {
            try {
                for (int i = 0; i < 10; i++) {
                    Thread.sleep(1000);
                    System.out.println(Thread.currentThread().getName());
                }
            } catch (InterruptedException e) {
                Thread.yield(); //4쌍까지 1,2동시에 나옴 5부터는 2,2만 나옴
            }
        };

        Thread thread1 = new Thread(task, "thread1");//thread1.interrupt()에 의해서 thread1 리소스가 thread2리소스에 할당
        Thread thread2 = new Thread(task, "thread2");

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

        try {
            Thread.sleep(5000); 
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        thread1.interrupt(); //thread1 interrupt(일시정지상태가 대기중 상태로 가는 것도 있지만, 실행중일떄 실행대기로 바꾸는 특징도 있음)
    }

synchronized

멀티 쓰레드의 경우 여러 쓰레드가 한 프로세스의 자원을 공유해서 작업하기 때문에 서로에게 영향을 줄 수 있습니다. 이로인해서 장애나 버그가 발생할 수 있음

이러한 일을 방지하기 위해 한 쓰레드가 진행중인 작업을 다른 쓰레드가 침범하지 못하도록 막는 것을 '쓰레드 동기화(Synchronization)'라고 함

동기화를 하려면 다른 쓰레드의 침범을 막아야하는 코드들을 ‘임계영역’으로 설정하면 됨

임계영역에는 Lock을 가진 단 하나의 쓰레드만 출입이 가능

즉, 임계영역은 한번에 한 쓰레드만 사용이 가능

실행할 메서드 또는 실행할 코드 묶음 앞에 synchronized 를 붙여서 임계영역을 지정하여 다른 쓰레드의 침범을 막을 수 있음 (침범을 막다. = Lock을 걸다.)

package week05.thread.sync;

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

        //3개의 쓰레드를 만들어서 start함
        //생성(NEW)과 동시에 start(NEW->RUNNABLE)
        for (int i = 0; i < 3; i++) {
            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); //1초를 기다렸다가 -=1 수행
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                storedApple -= 1;
            }
        }
    }
}
----------------------------OR---------------------------------
class AppleStore {
    private int storedApple = 10;

    public int getStoredApple() {
        return storedApple;
    }

    public synchronized void eatApple() {
        if (storedApple > 0) {
            try {
                Thread.sleep(1000); //1초를 기다렸다가 -=1 수행
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            storedApple -= 1;
        }
    }
}

synchronized (this) {

~} //동기화 해결

이부분 주석처리하면

남은 사과의 수 = 9
남은 사과의 수 = 8
남은 사과의 수 = 7
남은 사과의 수 = 6
남은 사과의 수 = 4
남은 사과의 수 = 5
남은 사과의 수 = 3
남은 사과의 수 = 3
남은 사과의 수 = 2
남은 사과의 수 = 1
남은 사과의 수 = 0       | synchronized(this)         남은 사과의 수 = 0
남은 사과의 수 = -1      |                            남은 사과의 수 = 0
남은 사과의 수 = -2      |                            남은 사과의 수 = 0

출력 3개의 쓰레드가 if문에 들어가기 때문

synchronized(this) 주석해제시 0,0,0출력

wait()

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

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

추후에 작업을 진행할 수 있는 상황이 되면 notify()를 호출해서, 작업을 중단했던 쓰레드가 다시 Lock을 얻어 진행할 수 있음

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

notify()

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

특정 쓰레드를 구분못함.

병목현상에 빠질 수 있으니 주의해야함

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

    }
}
Inventory 현황: [AirPods]
Inventory 현황: [AirPods, AirPods]
Customer2 Waiting!
Customer1 Waiting!
Inventory 현황: [AirPods, AirPods, MacBook]
Customer2 Waiting!
Inventory 현황: [AirPods, AirPods, MacBook, IPhone]
Customer1 Waiting!
Inventory 현황: [AirPods, AirPods, MacBook, IPhone, iMac]
Customer2 Waiting! //병목현상 
StoreClerk Waiting! //병목현상

Lock

블럭으로 동기화하면 자동적으로 Lock이 걸리고 풀리지만, 같은 메서드 내에서만 Lock을 걸 수 있다는 제약이 있음

이런 제약을 해결하기 위해 Lock 클래스를 사용

public class MyClass {
    private Object lock1 = new Object();
    private Object lock2 = new Object();
    
    public void methodA() {
        synchronized (lock1) {
            methodB();
        }
    }
    
    public void methodB() {
        synchronized (lock2) {
            // do something
            methodA();
        }
    }
}

1) ReentrantLock

  • 재진입 가능한 Lock, 가장 일반적인 배타 Lock
  • 특정 조건에서 Lock을 풀고, 나중에 다시 Lock을 얻어 임계영역으로 진입이 가능
  • methodA는 lock1을 가지고, methodB는 lock2를 가집니다.
  • methodB에서 methodA를 호출하고 있으므로, methodB에서 lock2를 가진 상태에서 methodA를 호출하면 lock1을 가지려고 할 것입니다.
  • 그러나 이때, methodA에서 이미 lock1을 가지고 있으므로 lock2를 기다리는 상태가 되어 데드락이 발생할 가능성이 있습니다.
  • 하지만 ReentrantLock을 사용하면, 같은 스레드가 이미 락을 가지고 있더라도 락을 유지하며 계속 실행할 수 있기 때문에 데드락이 발생하지 않습니다.
  • 즉, ReentrantLock을 사용하면 코드의 유연성을 높일 수 있습니다.

2) ReentrantReadWriteLock

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

3) StampedLock

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

Condition

wait() & notify()의 문제점인 waiting pool 내 쓰레드를 구분하지 못한다는 것을 해결한 것이 Condition 입니다.

wait()과 notify()는 객체에 대한 모니터링 락(lock)을 이용하여 스레드를 대기시키고 깨웁니다. 그러나 wait()과 notify()는 waiting pool 내에 대기중인 스레드를 구분하지 못하므로, 특정 조건을 만족하는 스레드만 깨우기가 어렵습니다.

이러한 문제를 해결하기 위해 JDK 5에서는 java.util.concurrent.locks 패키지에서 Condition 인터페이스를 제공합니다. Condition은 waiting pool 내의 스레드를 분리하여 특정 조건이 만족될 때만 깨우도록 할 수 있으며, ReentrantLock 클래스와 함께 사용됩니다. 따라서 Condition을 사용하면 wait()과 notify()의 문제점을 보완할 수 있습니다.

wait() & notify() 대신 Condition의 await() & signal() 을 사용

람다와 스트림 알아보기

package week05.thread.stat.condition;

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

public class Main {
    private 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(); // 임계영역 끝
        }
    }
}

모던자바

  1. 병렬처리 2.함수형

자바 8 이후 람다 스트림개념이 추가

package week05.thread.modern;

import java.security.PublicKey;
import java.util.ArrayList;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        ArrayList<Car> carsWantToPark = new ArrayList<>();
        ArrayList<Car> parkingLot = new ArrayList<>();
        ArrayList<Car> weekendParkingLog = new ArrayList<>(); //주말 주차

        Car car1 = new Car("Benz", "Class E", true, 0);
        Car car2 = new Car("BMW", "Series 7", false, 100);
        Car car3 = new Car("BMW", "X9", false, 0);
        Car car4 = new Car("Audi", "A7", true, 0);
        Car car5 = new Car("Hyundai", "Ionic 6", false, 10000);

        carsWantToPark.add(car1);
        carsWantToPark.add(car2);
        carsWantToPark.add(car3);
        carsWantToPark.add(car4);
        carsWantToPark.add(car5);

//        parkingLot.addAll(parkingCarWithTicket(carsWantToPark));
//        Car안에 있는 hasParkingTicket을 넘겨주겠다. 위 로직을 밑 로직으로 대체
        parkingLot.addAll(parkCars(carsWantToPark,Car::hasParkingTicket));
//        parkingLot.addAll(parkingCarWithMoney(carsWantToPark));
        parkingLot.addAll(parkCars(carsWantToPark, Car::noTicketButMoney));
        //람다식(input) -> {함수} 적용 -> hasParkingTicket이 있고 getParkingMoney가 1000원 이상
        parkingLot.addAll(parkCars(carsWantToPark, (Car car) -> car.hasParkingTicket() && car.getParkingMoney() >= 1000));
        for (Car car : parkingLot) {
            System.out.println("Parked Car : " + car.getCompany() + "-" + car.getModel());
        }
    }

    //    public static List<Car> parkingCarWithTicket(List<Car> carsWantToPark) {
//        ArrayList<Car> cars = new ArrayList<>();
//
//        for (Car car : carsWantToPark) {
//            if (car.hasParkingTicket()) {
//                cars.add(car);
//            }
//        }
//        return cars;
//    }
//
//    public static List<Car> parkingCarWithMoney(List<Car> carsWantToPark) {
//        ArrayList<Car> cars = new ArrayList<>();
//
//        for (Car car : carsWantToPark) {
//            if (!car.hasParkingTicket() && car.getParkingMoney() > 1000) {
//                cars.add(car);
//            }
//        }
//        return cars;
//    }
    //위의 두 메소드를 하나로 -> 내부 주요 로직을 함수로 전달
    public static List<Car> parkCars(List<Car> carsWantToPark, Predicate<Car> function){
        List<Car> cars = new ArrayList<>();
        for (Car car : cars){
            if(function.test(car)){
                cars.add(car);
            }
        }
        return cars;
    }
}

    class Car {
        private final String company;
        private final String model;

        private final boolean hasParkingTicket;
        private final int parkingMoney;

        public Car(String company, String model, boolean hasParkingTicket, int parkingMoney) {
            this.company = company;
            this.model = model;
            this.hasParkingTicket = hasParkingTicket;
            this.parkingMoney = parkingMoney;
        }

        public String getCompany() {
            return company;
        }

        public String getModel() {
            return model;
        }

        public boolean hasParkingTicket() {
            return hasParkingTicket;
        }

        public int getParkingMoney() {
            return parkingMoney;
        }

        public static boolean hasTicket(Car car) {  //Ticket확인
            return car.hasParkingTicket;
        }

        public static boolean noTicketButMoney(Car car) {
            return !car.hasParkingTicket && car.getParkingMoney() >= 1000; //티켓이 없지만 1000원 이상
        }
    }

    interface Predicate<T> { //인터페이스가 타입 역할을 함
        boolean test(T t);
    }

주석 메소드는 기존 메소드

따로 주석 친 부분은 리팩토링 한 부분

람다식

(x,y) -> x + y //return이 한문장이면 중괄호 생략가능

= (x,y) -> { return x+y }

스트림(흐름)

자료구조(리스트, 맵, 셋 등)의 흐름을 객체로 제공해주고 그 흐름동안 사용할 수 있는 메서드들을 api로 제공

특징

원본의 데이터를 변경하지 않음

일회용임(한 번 사용한 데이터는 어디에도 저장X)

List<Car> benzParkingLot =
								// carsWantToPark의 스트림값을 받아와서
                carsWantToPark.stream()         
												// 거기 구현되어 있는 filter()메서드를 사용합니다.
												// filter메서드는 함수를 파라미터로 전달받습니다.
												// 여기서 함수는 제조사가 벤츠면 true를 반환하는 함수네요.
												// 필터 메서드는 이름처럼 false를 반환한 스트림의 원소들을 제거합니다.
                        .filter((Car car) -> car.getCompany().equals("Benz"))
												// 이 결과도 반환을 받아서 다시 리스트로 묶어줍니다.
                        .toList();
-------------------------------------------------------------------------------------
ArrayList<Car> benzParkingLotWithoutStream = new ArrayList<>();

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

car.WantToPark.stream().filter((Car car) -> car.getCompany().equals("Benz")).toList();
------------------------------------------------------------------------------------
List<String> carNames = Arrays.asList("Series 6", "A9", "Ionic 6");

carNames.stream()
    .forEach(System.out::println);

// 결과 
// Series 6
// A9
// Ionic 6
------------------------------------------------------------------------------------
carNames.stream()
	.map(name -> name.toUpperCase()).toList();

// 결과
// ["SERIES 6", "A9", "IONIC 6"]

filter -> 조건에 맞는 것만 반환

map -> 모든 요소를 가공해서 반환

foreach 제외하고는 .toList()

null

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

        SomeDBClient myDB = new SomeDBClient();
        
        String userId = myDB.findUserIdByUsername("HelloWorldMan");

        System.out.println("HelloWorldMan's user Id is : " + userId);
    }
}

class SomeDBClient {

    public String findUserIdByUsername(String username) {
        // ... db에서 찾아오는 로직
		String data = "DB Connection Result";

        if (data != null) {
            return data;
        } else {
            return null; //null을 반환하면 nullPointerException 발생
        } 
    }
}
------------------------------------------------------------------------
public class NullIsDanger {
    public static void main(String[] args) {

        SomeDBClient myDB = new SomeDBClient();

        String userId = myDB.findUserIdByUsernameOrThrowNull("HelloWorldMan");
        // 개선 1: null이 반환 될 수 있음을 인지한 메서드 사용자는, null을 대비.
        if (userId != null) {
            System.out.println("HelloWorldMan's user Id is : " + userId);
        }
    }
}

class SomeDBClient {
    // 개선 1: 이 메서드는 null이 반환 될 수 있음을 명시합니다.
    public String findUserIdByUsernameOrThrowNull(String username) {
        // ... db에서 찾아오는 로직
				String data = "DB Connection Result";

        if (data != null) {
            return data;
        } else {
            return null;
        }
    }
}
------------------------------------------------------------------------
// 개선 2: 결과값을 감싼 객체를 만듭니다.
class SomeObjectForNullableReturn {
    private final String returnValue;
    private final Boolean isSuccess;

    SomeObjectForNullableReturn(String returnValue, Boolean isSuccess) {
        this.returnValue = returnValue;
        this.isSuccess = isSuccess;
    }

    public String getReturnValue() {
        return returnValue;
    }

    public Boolean isSuccess() {
        return isSuccess;
    }
}

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

        SomeDBClient myDB = new SomeDBClient();

        // 개선 2 : 이제 해당 메서드를 사용하는 유저는, 객체를 리턴받기 때문에 더 자연스럽게 성공여부를 체크하게 됩니다.
        SomeObjectForNullableReturn getData = myDB.findUserIdByUsername("HelloWorldMan");

        if (getData.isSuccess()) {
            System.out.println("HelloWorldMan's user Id is : " + getData.getReturnValue());
        }
    }
}

class SomeDBClient {
    // 개선 2 : 결과값을 감싼 객체를 리턴합니다.
    public SomeObjectForNullableReturn findUserIdByUsername(String username) {
        // ... db에서 찾아오는 로직
        String data = "DB Connection Result";

        if (data != null) {
            return new SomeObjectForNullableReturn(data, true);
        } else {
            return new SomeObjectForNullableReturn(null, false);
        }
    }
}
------------------------------------------------------------------------
개선 3
class SomeObjectForNullableReturn<T> {
    private final T returnValue;
    private final Boolean isSuccess;
   
    SomeObjectForNullableReturn(T returnValue, Boolean isSuccess) {
        this.returnValue = returnValue;
        this.isSuccess = isSuccess;
    }

    public T getReturnValue() {
        return returnValue;
    }

    public Boolean isSuccess() {
        return isSuccess;
    }
}
개선 3의 아이디어를 발전시킨것이 java.util.Optional 객체
Optional 기본 정리
Java8에서는 Optional<T> 클래스를 사용해 Null Pointer Exception을 방지할 수 있도록 도움
Optional<T>는 null이 올 수 있는 값을 감싸는 Wrapper 클래스.
Optional이 비어있더라도, 참조해도 Null Pointer Exception가 발생X
Optional 간단 사용법
값이 null 인 Optional 생성
Optional<Car> emptyOptional = Optional.empty();
​
값이 있는 Optional 생성하기
Optional<Car> hasDataOptional = Optional.of(new Car());
​
값이 있을수도 없을수도 있는 Optional 생성하기
Optional<Car> hasDataOptional = Optional.ofNullable(getCarFromDB());
​
Optional 객체 사용하기 (값 받아오기)
Optional<String> carName = getCarNameFromDB();
// orElse() 를 통해 값을 받아옵니다, 파라미터로는 null인 경우 반환할 값을 적습니다.
String realCarName = carName.orElse("NoCar");

// 위는 예시코드고 실제는 보통 아래와 같이 사용하겠죠?
String carName = getCarNameFromDB().orElse("NoCar");

// orElseGet()이라는 메서드를 사용해서 값을 받아올 수 있습니다.
// 파라미터로는 없는 경우 실행될 함수를 전달합니다.
Car car = getCarNameFromDB().orElseGet(Car::new);

// 값이 없으면, 그 아래 로직을 수행하는데 큰 장애가 되는경우 에러를 발생시킬수도 있습니다.
Car car = getCarNameFromDB()
						.orElseThrow(() -> new CarNotFoundException("NO CAR!)

5주차 숙제

  public class Main {
     public static void main(String[] args) {
         List<Book> bookList = Arrays.asList(
                new Book(1L, "모두의 딥러닝", "조태호", "IT", 21600),
                new Book(2L, "이득우의 게임 수학", "이득우", "IT", 40500),
                .....
                new Book(17L, "레버리지", "롭 무어", "경제", 16200)
        );

        List<Book> books = travelName(bookList, "여행");
        for(Book book : books){
            System.out.println(book.getBookName());
        }
        bookList.stream().
                filter(book -> book.getCategory().equals("여행"))
                        .forEach(book -> System.out.println(book.getBookName()));
        System.out.println("------------------------------------");
        List<Book> bookPrice = getBooks(bookList,16200);
        for(Book book : bookPrice){
            System.out.println(book.getBookName() + " | " + book.getPrice());
        }
        bookList.stream().filter(book -> book.getPrice()<=16200)
                .forEach(book-> System.out.println(book.getBookName() + " | " +book.getPrice()));
        System.out.println("------------------------------------");
        List<Book> books1 = getBookName(bookPrice, "경제");
        for(Book book : books1){
            System.out.println(book.getBookName());
        }
        bookList.stream().filter(book->book.getBookName().contains("경제"))
                        .forEach(book-> System.out.println(book.getBookName()));
        System.out.println("------------------------------------");
        OptionalDouble maxPirce = bookList.stream()
                .mapToDouble(Book::getPrice)
                .max();
        if(maxPirce.isPresent()){
            double max = maxPirce.getAsDouble();
            System.out.println(max);
        }
        Optional maxPrice2 = bookList.stream()
                .map(Book::getPrice)
                .max(Double::compare);
        if(maxPrice2.isPresent()){
            System.out.println(maxPrice2);
        }
        System.out.println("------------------------------------");
        Double sum = bookList.stream()
                .filter(book->"IT".equals(book.getCategory()))
                .mapToDouble(Book::getPrice)
                .sum();
        System.out.println(sum);
        System.out.println("------------------------------------");
        List<Book> discount = bookList.stream()
                .filter(book -> "IT".equals(book.getCategory()))
                .map(book -> {
                    book.setPrice(book.getPrice() * 0.6);
                    return book;
                })
                .toList();
        for(Book book : discount){
            System.out.println(book.getBookName() + " | " + book.getPrice());
        }
    }
    public static List<Book> travelName(List<Book> books, String bookList){
        List<Book> bookName = new ArrayList<>();
        for(Book book : books){
            if(book.getCategory().equals(bookList)){
                bookName.add(book);
            }
        }
        return bookName;
    }
    public static List<Book> getBooks(List<Book> bookList, double price){
        List<Book> books = new ArrayList<>();
        for(Book book : bookList){
            if(book.getPrice() <= 16200){
                books.add(book);
            }
        }return books;
    }
    public static List<Book> getBookName(List<Book> books, String name){
        List<Book> bookList = new ArrayList<>();
        for(Book book : books){
            if(book.getBookName().contains("경제")){
                bookList.add(book);
            }
        }return bookList;
    }
}

class Book {
    // 분류번호
    private Long id;
    // 책 이름
    private String bookName;
    // 작가 이름
    private String author;
    // 카테고리
    private String category;
    // 가격
    private double price;

    public Book(Long id, String bookName, String author, String category, double price) {
        this.id = id;
        this.bookName = bookName;
        this.author = author;
        this.category = category;
        this.price = price;
    }

    public String getBookName() {
        return bookName;
    }

    public String getCategory() {
        return category;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }

    public Long getId() {
        return id;
    }

    public String getAuthor() {
        return author;
    }
}
반응형
LIST

'K-Digital Training > 내일배움캠프' 카테고리의 다른 글

입문 2주차/Spring  (2) 2023.11.21
입문 1주차/Spring  (1) 2023.11.21
자바 4주차  (1) 2023.11.01
자바 3주차  (0) 2023.10.20
자바 2주차  (0) 2023.10.20