[Java] 5. Thread(2), Stream

Nam_JU·2022년 7월 17일
0

KaKao Cloud School

목록 보기
10/19

스레드 동기화 Synchronization

  • 멀티 스레드 프로세스에서는 다른 스레드의 작업에 영향을 미칠 수 있다
  • 진행중인 작업이 다른 스레드에게 간섭받지 않게 하려면 '동기화'가 필요하다
  • 동기화 하기 위해서는 간섭받지 않아야 하는 공용객체를 임계영역으로 설정하고 lock 을 사용하여 하나의 스레드만 출입 가능하게 해야한다.

영화관 예약 시나리오

  • 예약 스레드 1,2,3 생성
  • 시나리오
  1. 스레드1 이 사이트에 들어가 좌석 정보를 확인
    a. 1번 선택해서 들어감
    b. 결제하기 위해서 1번 선택하고 결제 페이지에서 입력
  2. 여기서 스레드2가 예약이 아직 안되었으니 같은 1번 좌석 선택
    a. 1번 스레드보다 빨리 카드를 꺼내서 결제함
    b. 1번 좌석이 상태가 변함
  3. 카드 결제를 위해 1번이 이제서야 결제를 완료하니까 상태가 바뀌었다고 뒤로감
    feat. 이미 결제한 좌석입니다
    이게바로 동기화 문제 - 여러스레드가 공유자원을 어떻게 처리하는가
    내가 처음 사용할때는 좌석이 있는데 나중에보니까 없음. 불일치
    이미 다른 스레드가 사용하니까 안의 데이터가 바뀜 데이터처리가 잘못들어가고있다
    여러개의 스레드가 공용 데이터를 사용할경우 어떻게 해야할까?
    이게바로 스레드의 동기화 문제이다 → 세마포어 뮤텍스…..
    운영체제 문제…

해결 방법

  1. t1의 스레드가 카드결제가 완료될때까지 다른 스레드가 해당 페이지를 접근하지 못하도록 막는다 (좌석 선택후 결제하고있는 과정에서 다른 스레드가 개입 못하도록 방지)
  2. 이 과정을 순차처리 할 수 있도록 해야한다
    ⇒ 한스레드가 특정 작업을 마치기 전까지 다른 스레드에 의해서 방해받지 않도록 하는것 : Critical Section 이라고함 (임계영역)
  3. 임계영역을 잡도록 해야한다 자바에서는 어떻게?
    • 하나의 스레드가 동시에 하나에 한번만 사용하도록 Lock이 들어오도록 한다
    • 임계영역을 설정하도록 하는것이 Lock monitor라고 한다
  4. 공용객체를 제어할수 있는 lock을 t1 스레드가 가져간다
  5. T1이 다쓰면 lock을 다쓰고 공용객체에 돌려준다 T2, T3가 기다리고 있음
  6. 동시에 들어가서 한놈만 lock을 잡고 실행 → 나머지는 대기상태

Thread 대기상태 3가지

  • sleep()
  • join()
  • lock()
  • java에서는 키워드를 사용하여 임계영역을 설정할 수 있다
  • 키워드를 사용해서 임계영역을 설정하고 lock을 사용할수있도록 할 수 있다

Java에서 lock을 얻어 임계영역 설정후 설정하려면 synchronization키워드를 사용한다
1. 동기화 메소드를 만드는 방법
2. 동기화 블록을 만드는 방법


동기화 예제

//Thread에 의해서 공유되는 공유 객체를 생성하기 위한 class
class Account{

    //계좌 잔고
    private int balance = 1000;

    public int getBalance() {
        return balance;
    }

    //출금하는 메소드 = 동기화 시킨다
    /**변경 - 동기화가 문제되는 메서드를 찾아서 키워드(synchronized)를 넣기
     1. 클래스에 키워드 넣기
     public synchronized void withdraw(int money)~~
     2. 필요한 부분의 블록에만 키워드 넣기
     예제코드가 짧으니까 문제가 없지만 실무에서는 긴 코드에서 일부분만 필요함
     비효율적인데 이것을 어떻게 효율적으로 할까? - 필요한 부분만 동기화 시키기
     */
    public void withdraw(int money) {
        //계좌가 금액보다 커야 출금 가능
        /**this. 현재 객체에 대해서 동기화 시킬겁니다
         블록에 키워드를 넣고 처리할 수 있다
         */
        synchronized (this) {
            if (balance >= money) {
                //예제를 위해 보여주기 위해서 스레드 잠재우기 / 속도가 빨라서
                try {
                    //1초 쉬고 빼고 쉬고빼고 함
                    /**문제! 두 스레드가 sleep을 잡아서 잔액이 줄다가 마이너스가 됨 코드를 변경하기*/
                    Thread.sleep(1000);
                } catch (Exception e) {
                }
                balance -= money;
            }
        }
    }
}

//객체를 new 해서 만들때 공용객체를 Account로 만듬
class ThreadEx_092 implements Runnable{

    //runnable 구현한 클래스의 객체를 만들면 여러 스레드가 같이 사용한다
    //공용객체
    Account acc = new Account();
    @Override
    public void run() {
                //잔액이 마이너스가 되지 않으면 계속 뽑아냄
        while (acc.getBalance() > 0){
                                        //램덤하게 100, 200, 300 뽑아냄
            int money = ((int)(Math.random()*3 +1) * 100);
                        //10. ~ 4.0 -> 정수로 변경 1~4
            acc.withdraw(money);
            System.out.println("남은 잔액은 : " + acc.getBalance());
        }
    }
}

public class ThreadExam092 {
    public static void main(String[] args) {
        ThreadEx_092 r = new ThreadEx_092(); //runnable 객체
        Thread t1 = new Thread(r);
        Thread t2 = new Thread(r);

        t1.start();
        t2.start();
    }
}

  • Thread 객체를 만들기 전에 ThreadEx092에 Account 인스턴스를 생성한다.
  • 그렇게 되면 각각의 스레드가 생성되어도 참조 변수가 힙메모리에 생성된 Account를 참조하여 공통으로 사용할 수 있다

Wait() Notify()

  • Thread가 공유자원에 대한 lock을 획득한후 오랜시간을 가져가는게 문제
  • 임계영역에 들어가서 계속 스레드가lock을 붙잡고 있으면 다른 스레드들이 대기중
  • 다른 스레드들이 starvation이 된다 임계영역을 해제해줘야 권한을 가지게 할 수 있다
  • 빵집에 빵사러갔다가 단팥빵 없음…
    근데 만들어지고 있어 난 버티고있는데 뒤에 사람들이 기다리고있다 다른사람들은 다른빵 먹을예정 난 양보를 하고 자리를 비켜줘야함
    내가 lock을 놓고가지 않는이상 뒤에서는 계속기다린다.

lock을 오래가지고 있는 문제에 대해서는 어떻게 해결할까?

  1. wait() : 임계영역을 해제 - 일시대기상태로 돌아가 다른 스레드에게 lock을 준다
    • 내가 스스로 놓고 나가는 것
  2. notify() : 나대신 들어간놈이 notify()를 해주면 wait()에 들어간 나를 깨워주는 일
    • block을 해제(통지)시켜 주는 일


1. 새로 생성된 스레드들이 Runnable 에서 기다린다
2. 이중 t1이 Running 상태에 있다가 자의로 wait()을 사용하여 lock을 던진다
3. t1은 Blooked Object's lock pool로 이동
4. cpu에게 선택된 t2가 lock을 받고 작업을 수행한다.
5. 작업을 하다가 notify()로 block에 있는 t1을 꺼내준다
t1은 Runnable 상태로 이동한다
6. 4번에서 사실 t3는 t2에 갔다가 lock을 얻지 못했었다
그래서 blocked Objcts look pool에 들어갔다.
t2가 작업을 끝내자 t3는 곧장 Running으로 들어가서 작업을 수행한다


notify() & waite() 예제

notify() & waite()를 사용하여 스레드를 주고받을 수 있도록 하기

class Shared {
    public synchronized void printNum() {
        try {
            for(int i=0; i < 10; i++) {
                Thread.sleep(1000);
                System.out.println(Thread.currentThread().getName() + " : " + i);
                notify();
                wait();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
class ThreadEx_10 implements Runnable {
	private Shared shared;
    public ThreadEx_10(Shared shared) {
        this.shared = shared;
    }
    @Override
    public void run() {            
        shared.printNum();
    }        
}
public class ThreadExam10 {
	public static void main(String[] args) {
		Shared obj = new Shared();
        Thread t1 = new Thread(new ThreadEx_10(obj));
        Thread t2 = new Thread(new ThreadEx_10(obj));
        t1.setName("첫번째 Thread!!");
        t2.setName("두번째 Thread!!");
        t1.start();
        t2.start();
	}
}
  1. 메인스레드 생성
  2. t1 과 t2가 생성되고 runnable 상태가 되어 run()을 실행
  3. 메인 스레드는 종료된다
  4. 스케줄러에 의해 t1 or t2가 Running 상태로 변경
  5. t1이 running을 갔다면 sleep 1초를 잔다 -> block 상태로 변경
  6. t2가 그사이 running이 되어 또 잔다 -> block 상태
  7. CPU가 고를 애가 없어서 기다리다 먼저 끝난 t1이 println을 찍고 (notify() 해줄 애가 없어서 지나감)
    wait()에 걸림 -> block으로 들어간다
  8. 그사이 t2는 다시 running이 되어 찍고 notify로 t1을 깨워준다. 본인은 wait으로 block 들어감
  9. t1과 t2가 서로를 깨워주고 자면서 10번 반복하며 수행하다 한명은 모두 마치고 종료된다 -> 한명은 못빠져나옴
    결과적으로 번갈아가면서 한번씩 찍고 나오게 된다

조심해야할것

공용객체에서 notify를 해줌
하나의 스레드가 공용객체를 사용하고 있는데 lock을 기다리겠음
공용객체에 대한 notify를 해준다
스레드가 사용하는 공용객체가 많을 수 있다.
공용객체마다 wait과 notifty를 해줄수 있다

  • 특정 자원에 대해서 wait과 notify를 해준다

JAVA IO

  • Stream이라는 객체를 이용해서 입력과 출력을 처리한다
  • Java에서 특정 장치에서 date를 읽거나 특정장치로 data를 보낼때 사용하는 매개객체
  • 읽는 스트림, 받는 스트림 2개를 만들어야 한다

Stream 객체 특징

  • 단방향이다 (데이터가 한방향으로만 흘러간다)
  • 파일에 데이터를 저장하거나 자바프로그램에서 실행하고 싶다라고 한다면 통로가 두개있어야함
  • 스트림 생성시 스트림의 종류가 결정이 되고 방향이
  • FIFO 구조 - first in first out 구조이다
    먼저들어간 데이터가 먼저나온다
  • stream을 결합해서 사용할 수 있다
    사용하기 쉬운 스트림을 만들어서 사용하기 쉽다

JAVA 입출력

  • IO (Input/ output)

    대용량의 데이터를 보내려면 IO

  • NIO(new I/o) JAVA4 → JAVA7

    적은량의 데이터를 보낼떄는 NIO → 채널

Java IO

  • Stream객체를 사용하여 입력과 출력을 처리한다
  • java.io pacakge로 제공한다

표준입력 & 표준출력 → System.out이 제공

  • 표준입력 : keyboard
  • 표준출력: monitor

키보드로부터 내가 입력을 받겠다 → System.in

Stream 구분

  • Input / output
  • Byte / 문자 Stream

Object Stream을 통해서 객체도 전달 할 수 있다

단 모든 객체가 다 되는것은 아니다
만약 인스턴스를 생성한 class가 serializable interface를 구현하고 있으면 가능하다 그렇지 않으면 불가능하다


  • 메모장 만들기 (JavaFx)
public class MyNotepad extends Application {
    TextArea textArea;
    Button openBtn, saveBtn;
    File file;

    //함수로 뺀다 -> printMsg 호출
    private void printMsg(String msg){
        Platform.runLater( ()->{
            textArea.appendText(msg + "\n");
        });
    }
    @Override
    public void start(Stage primaryStage) throws IOException {
        //화면을구성하는 전체 판
        BorderPane root = new BorderPane();
        //BoarderPane의 가로 세로 길이
        root.setPrefSize(700,500);

        //TextArea 생성
        textArea = new TextArea();
        root.setCenter(textArea);

        //버튼을 만든다
        openBtn = new Button("파일 열기");
        openBtn.setPrefSize(150, 40);
                                    //이벤트 핸들러를 만들어야 한다
//        openBtn.setOnAction(new EventHandler<ActionEvent>() {
//            @Override
//            public void handle(ActionEvent actionEvent) {
//                System.out.println("버튼이 눌렸어요!"); //deligation event model
//            }
//        });
                        /**람다로 변경*/
        openBtn.setOnAction(e -> {
            //오픈할 파일을 찾아서 그 내용을 textArea에 출력한다
            textArea.clear(); //textArea의 내용을 다지운다 새로운 내용을 넣어야 하기 때문
            FileChooser fileChooser = new FileChooser();
            fileChooser.setTitle("오픈할 파일을 선택해 주세요!");
            file = fileChooser.showOpenDialog(primaryStage);

            //오류가 날 여지가 있다
            try {
                //관뚫고 파일로부터 문자열을 읽어올것이다
                FileReader fr = new FileReader(file);
                //위의 관보다 더 좋은 통로로 만든다
                //파일의 내용을 한줄씩 읽을 수 있다
                BufferedReader br = new BufferedReader(fr);
                String line = "";
                while((line= br.readLine()) != null){
                    //동기화 - runnable 인터페이스로 구현한 객체가 온다
                    printMsg(line);
                }
                br.close();
            } catch (FileNotFoundException ex) {
                throw new RuntimeException(ex);
            } catch (IOException e2){

            }
        });
        saveBtn = new Button("파일저장");
        saveBtn.setPrefSize(150,40);
        //save butn을 눌리면 이벤트를 발생한다
        saveBtn.setOnAction(e ->{
            String text = textArea.getText();
            try {
                FileWriter fw = new FileWriter(file);
                   //내가 갖고온 text를 저장
                fw.write(text);
                    //저장후 닫기
                fw.close();
            } catch (IOException ex) {
                throw new RuntimeException(ex);
            }
        });

        FlowPane flowPane = new FlowPane();
        flowPane.setPadding(new Insets(10,10,10,10));
        flowPane.setColumnHalignment(HPos.CENTER);
        flowPane.setPrefSize(700,40);
        flowPane.setHgap(10); // 간격

        //flowPane에 버튼을 붙인다
        flowPane.getChildren().add(openBtn);
        flowPane.getChildren().add(saveBtn);

        root.setBottom(flowPane);

        //Scene 객체를 생성
        Scene scene = new Scene(root);
        primaryStage.setScene(scene);
        primaryStage.setTitle("메모장");
        primaryStage.show();
    }

    public static void main(String[] args) {
        launch();
    }
}
profile
개발기록

0개의 댓글