영화관 예약 시나리오
- 예약 스레드 1,2,3 생성
- 시나리오
- 스레드1 이 사이트에 들어가 좌석 정보를 확인
a. 1번 선택해서 들어감
b. 결제하기 위해서 1번 선택하고 결제 페이지에서 입력- 여기서 스레드2가 예약이 아직 안되었으니 같은 1번 좌석 선택
a. 1번 스레드보다 빨리 카드를 꺼내서 결제함
b. 1번 좌석이 상태가 변함- 카드 결제를 위해 1번이 이제서야 결제를 완료하니까 상태가 바뀌었다고 뒤로감
feat. 이미 결제한 좌석입니다
이게바로 동기화 문제 - 여러스레드가 공유자원을 어떻게 처리하는가
내가 처음 사용할때는 좌석이 있는데 나중에보니까 없음. 불일치
이미 다른 스레드가 사용하니까 안의 데이터가 바뀜 데이터처리가 잘못들어가고있다
여러개의 스레드가 공용 데이터를 사용할경우 어떻게 해야할까?
이게바로 스레드의 동기화 문제이다 → 세마포어 뮤텍스…..
운영체제 문제…
해결 방법
- t1의 스레드가 카드결제가 완료될때까지 다른 스레드가 해당 페이지를 접근하지 못하도록 막는다 (좌석 선택후 결제하고있는 과정에서 다른 스레드가 개입 못하도록 방지)
- 이 과정을 순차처리 할 수 있도록 해야한다
⇒ 한스레드가 특정 작업을 마치기 전까지 다른 스레드에 의해서 방해받지 않도록 하는것 : Critical Section 이라고함 (임계영역)- 임계영역을 잡도록 해야한다 자바에서는 어떻게?
- 하나의 스레드가 동시에 하나에 한번만 사용하도록 Lock이 들어오도록 한다
- 임계영역을 설정하도록 하는것이 Lock monitor라고 한다
- 공용객체를 제어할수 있는 lock을 t1 스레드가 가져간다
- T1이 다쓰면 lock을 다쓰고 공용객체에 돌려준다 T2, T3가 기다리고 있음
- 동시에 들어가서 한놈만 lock을 잡고 실행 → 나머지는 대기상태
- sleep()
- join()
- 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를 참조하여 공통으로 사용할 수 있다
- Thread가 공유자원에 대한 lock을 획득한후 오랜시간을 가져가는게 문제
- 임계영역에 들어가서 계속 스레드가lock을 붙잡고 있으면 다른 스레드들이 대기중
- 다른 스레드들이 starvation이 된다 임계영역을 해제해줘야 권한을 가지게 할 수 있다
- wait() : 임계영역을 해제 - 일시대기상태로 돌아가 다른 스레드에게 lock을 준다
- 내가 스스로 놓고 나가는 것
- 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(); } }
- 메인스레드 생성
- t1 과 t2가 생성되고 runnable 상태가 되어 run()을 실행
- 메인 스레드는 종료된다
- 스케줄러에 의해 t1 or t2가 Running 상태로 변경
- t1이 running을 갔다면 sleep 1초를 잔다 -> block 상태로 변경
- t2가 그사이 running이 되어 또 잔다 -> block 상태
- CPU가 고를 애가 없어서 기다리다 먼저 끝난 t1이 println을 찍고 (notify() 해줄 애가 없어서 지나감)
wait()에 걸림 -> block으로 들어간다- 그사이 t2는 다시 running이 되어 찍고 notify로 t1을 깨워준다. 본인은 wait으로 block 들어감
- t1과 t2가 서로를 깨워주고 자면서 10번 반복하며 수행하다 한명은 모두 마치고 종료된다 -> 한명은 못빠져나옴
결과적으로 번갈아가면서 한번씩 찍고 나오게 된다
공용객체에서 notify를 해줌
하나의 스레드가 공용객체를 사용하고 있는데 lock을 기다리겠음
공용객체에 대한 notify를 해준다
스레드가 사용하는 공용객체가 많을 수 있다.
공용객체마다 wait과 notifty를 해줄수 있다
Stream 객체 특징
- 단방향이다 (데이터가 한방향으로만 흘러간다)
- 파일에 데이터를 저장하거나 자바프로그램에서 실행하고 싶다라고 한다면 통로가 두개있어야함
- 스트림 생성시 스트림의 종류가 결정이 되고 방향이
- FIFO 구조 - first in first out 구조이다
먼저들어간 데이터가 먼저나온다- stream을 결합해서 사용할 수 있다
사용하기 쉬운 스트림을 만들어서 사용하기 쉽다
IO (Input/ output)
대용량의 데이터를 보내려면 IO
NIO(new I/o) JAVA4 → JAVA7
적은량의 데이터를 보낼떄는 NIO → 채널
Java IO
표준입력 & 표준출력 → System.out이 제공
키보드로부터 내가 입력을 받겠다 → System.in
단 모든 객체가 다 되는것은 아니다
만약 인스턴스를 생성한 class가 serializable interface를 구현하고 있으면 가능하다 그렇지 않으면 불가능하다
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();
}
}