Posts 학원 #53일차: 계산기 Network App, 채팅 App, 스레드
Post
Cancel

학원 #53일차: 계산기 Network App, 채팅 App, 스레드

계산기 Network App

13단계: HTTP 프로토콜 기반 애플리케이션 실행

  • 웹 브라우저로부터 요청을 받아 응답한다.
  • 요청 및 응답 프로토콜은 웹 기반 프로토콜인 HTTP를 사용한다.
  • HTTP 요청에 대해 HTTP 응답을 수행한다.
  • 서버에 요청하는 방법: 웹 브라우저에서 다음과 같이 URL을 입력
    • http://localhost/plus?a=100&b=200

Server

CalculatorServer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class CalculatorServer {
  public static void main(String[] args) {
    
    try (ServerSocket serverSocket = new ServerSocket(80)) {
      System.out.println("서버 실행 중...");
      
      while (true) {
        RequestProcessor thread = new RequestProcessor(serverSocket.accept());
        thread.start();
      }
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}

RequestProcessor

  • url: [프로토콜]://서버주소:포트번호/자원의경로?파리미터명=값&파라미터명=값
  • HTTP 요청에 대해 HTTP 응답을 수행한다.
1
2
3
4
5
6
// HTTP 요청 프로토콜
// ---------------------------------
// GET [자원주소] HTTP/1.1 (CRLF)
// Host: [서버주소] (CRLF)
// (CRLF)
// ---------------------------------
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
public class RequestProcessor extends Thread {
  Socket socket;

  public RequestProcessor(Socket socket) {
    this.socket = socket;
  }

  @Override
  public void run() {
    try (Socket socket = this.socket;
        BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        PrintStream out = new PrintStream(socket.getOutputStream());) {
      // 웹 브라우저가 보낸 첫 줄에는 데이터가 포함되어 있기 때문에 따로 추출
      String requestLine = in.readLine();
      
      // 나머지 요청과 관련된 헤더 정보(부가 정보)는 현재 사용할 일이 없기 때문에 버림
      while (true) {
        if (in.readLine().length() == 0)
          break;
      }
      
      sendHttpResponse(out, compute(requestLine)); 
      
    } catch (Exception e) {
      System.out.printf("클라이언트 요청 처리 중 오류 발생! - %s\n", e.getMessage());
    }
  }

  private String compute(String request) {
    try {
      // 웹 브라우저가 보낸 request line에서 데이터를 추출한다.
      // 예) "GET /plus?a=100&b=200 HTTP/1.1"
      String[] values = request.split(" ")[1].split("\\?"); //["/plus", "a=100&b=200", "HTTP/1.1"]
      
      String op = getOperator(values[0]); // "/plus", "/multiple" 등
      
      String[] parameters = values[1].split("&"); //"a=100&b=200" => ["a=100", "b=200"]

      int a = 0;
      int b = 0;
      
      for (String parameter : parameters) {
        String[] kv = parameter.split("=");
        if (kv[0].equals("a"))
          a = Integer.parseInt(kv[0]);
        else if (kv[1].equals("b"))
          b = Integer.parseInt(kv[1]);
      }
      
      int result = 0;

      switch (op) {
        case "+": result = a + b; break;
        case "-": result = a - b; break;
        case "*": result = a * b; break;
        case "/": result = a / b; break;
        default:
          return "해당 연산자를 지원하지 않습니다.";
      }
      return String.format("결과는 %d %s %d = %d 입니다.", a, op, b, result);

    } catch (Exception e) {
      return String.format("계산 중 오류 발생! - %s", e.getMessage());
    }
  }
                   
  private String getOperator(String name) {
    switch(name) {
      case "/plus": return "+"; 
      case "/minus": return "-";
      case "/multiple": return "*";
      case "/devide": return "/";
      default: return "?";
    }
  }

  private void sendHttpResponse(PrintStream out, String message) {
    // HTTP 응답 형식에 맞춰 출력한다.
    out.println("HTTP/1.1 200 OK");
    out.println("Content-Type: text/plain;charset=UTF-8");
    out.println();
    out.println(message);
    out.flush();
  }
}

HTTP 프로토콜 형식에 맞춰 서버 프로그램을 만들었다. 따라서 이제 웹 브라우저에서 서버에 접속할 수 있다.

image

정규표현식

1
2
3
4
5
String requestLine = "GET /plus?a=100&b=200 HTTP/1.1";
String[] values = requestLine.split(" ")[1].split("\\?")[1].split("&");

for (String value : values)
  System.out.println(value);
  • split() 파라미터 안에 들어가는 문자열은 정규표현식이다.
  • ?는 정규표현식에서 하나의 문자를 의미하는 명령이다. 따라서 물음표에 대해 \를 삽입해 명령이 아님을 명시해야 한다. 그러나 자바에서 \는 특수 명령을 의미한다. 따라서 \문자를 하나 더해 (이스케이프 문자) 특수 명령이 아니라는 것을 명시한다.
  • 왜 정규 표현식을 사용할까? 많은 텍스트에서 원하는 규칙(패턴)을 가진 문자를 추출하는 프로그램을 만드는 것은 굉장히 어렵다. 그러나 어떤 프로그래밍 언어를 사용한다 하더라도 문자열을 다루는 것은 기본 중의 기본이다. 따라서 개발자들이 많은 텍스트에서 원하는 값을 찾아내는 프로그램을 미리 만들어 놓았다. 이것이 바로 정규표현식(Regular Expression; regexp)이다. 특정한 규칙을 가진 문자열의 집합을 표현하는 데 사용하는 명령이다.

채팅 프로그램

Client

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
public class ChatClient extends Frame implements ActionListener {
  private static final long serialVersion = 1L;
  
  TextField addressTF = new TextField(20);
  TextField portTF = new TextField(4);
  Button connectBtn = new Button("연결");
  TextArea chattingPane = new TextArea();
  Button sendBtn = new Button("보내기");
  
  Socket socket;
  BufferedReader in;
  PrintStream out;
  
  public ChatClient(String title) {
    super(title);
    this.setSize(600, 480);
    
      // 윈도우 관련 이벤트를 처리할 담당자를 설정한다.
    // => 옵저버 패턴
    // => 윈도우 이벤트가 발생했을 때 보고를 받을 객체를 등록한다.
    this.addWindowsListener(new WindowAdapter() {
      @Override
      public void windowClosing(WindowEvent e) {
        try {
          in.close();
        } catch (Exception ex) {

        }
        try {
          out.close();
        } catch (Exception ex) {

        }
        try {
          socket.close();
        } catch (Exception ex) {

        }
        System.exit(0);
      }
    });

    Panel topPane = new Panel();
    topPane.add(addressTF);
    topPane.add(portTF);
    topPane.add(connectBtn);
    this.add(topPane, BorderLayout.NORTH);

    this.add(chattingPane, BorderLayout.CENTER);

    Panel bottomPane = new Panel();
    bottomPane.setLayout(new BorderLayout());
    bottomPane.add(messageTF, BorderLayout.CENTER);
    bottomPane.add(sendBtn, BorderLayout.EAST);

    this.add(bottomPane, BorderLayout.SOUTH);

    // 연결 버튼에 대해 옵저버(리스너) 등록하기
    connectBtn.addActionListener(this);

    // 보내기 버튼에 대해 옵저버(리스너) 등록하기
    sendBtn.addActionListener(this);
  }
  
  @Override
  public void actionPerformed(ActionEvent e) {
    if (e.getSource() == sendBtn) {
      // 서버에 전송
      out.println(messageTF.getText());
      
      // 기존에 입력한 메시지는 지운다.
      messageTF.setText("");
    } else if (e.getSource() == connectBtn) {
      connectChatServer();
    }
  }
  
  private void connectChatServer() {
    try {
      socket = new Socket(addressTF.getText(), Integer.parseInt(portTF.getText()));
      out = new PrintStream(socket.getOutputStream());
      in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
      
      chattingPane.append("서버와 연결됨!\n");
      
      // 서버와 연결되면 메시지 수신을 별도의 담당자에게 맡긴다.
      new Thread(new MessageReceiver()).start();
      
      // 메시지 전송은 버튼을 누를 때마다 main 스레드가 처리할 것이다.
    } catch (Exception e) {
      chattingPane.append("서버 연결 오류!\n");
    }
  }
  
  class MessageReceiver implements Runnable {
    @Override
    public void run() {
      try {
        while (true) {
          String message = in.readLine();
          chattingPane.append(message + "\n");
        }
      } catch (Exception e) {
        chattingPane.append("메시지 수신 중 오류 발생!\n");
      }
    }
  }
  
  public static void main(String[] args) {
    ChatClient app = new ChatClient("비트 채팅!");
    app.setVisible(true);
  }
}

Server

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
public class ChatServer {
  
  ArrayList<PrintStream> outputStreams = new ArrayList<>();
  
  int port;
  
  public ChatServer(int port) {
    this.port = port;
  }
  
  public void service() {
    try (ServerSocket serverSocket = new ServerSocket(port)) {
      System.out.println("채팅 서버 시작!");
      
      while (true) {
        new Thread(new ChatAgent(serverSocket.accept())).start();
        System.out.println("채팅 클라이언트가 연결되었음!");
      }
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
  
  synchronized private void send(String message) {
    for (PrintStream out : outputStreams) {
      try {
        out.println(message);
      } catch (Exception e) {
        // 출력이 안 되는 스트림은 다음에 사용하지 않기 위해 목록에서 제거한다.
        outputStreams.remove(out);
      }
    }
  }
  
  class ChatAgent implements Runnable {
    
    Socket socket;
    
    public ChatAgent(Socket socket) {
      this.socket = socket;
    }
    
    @Override
    public void run() {
      try (Socket socket = this.socket;
          BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
          PrintStream out = new PrintStream(socket.getOutputStream())) {
        
        // 출력 스트림을 ChatServer에 보관한다.
        outputStreams.add(out);
        
        while (true) {
          String message = in.readLine();
          if (message.equals("quit"))
            break;
          
          // 채팅 방에 참여한 모든 사람들에게 메시지를 전달한다.
          // => 메시지를 전문적으로 보내는 일을 하는 객체에 맡긴다.
          new Thread(new MessageSender(message)).start();
        }
        
        // 채팅 방에 참여한 모든 사람들에게 퇴장 메시지를 전달한다.
      } catch (Exception e) {
        e.printStackTrace();
      }
      System.out.println("채팅 클라이언트가 종료되었음!");
    }
  }
  
  class MessageSender implements Runnable {
    String message;
    
    public MessageSender(String message) {
      this.message = message;
    }
    
    @Override
    public void run() {
      send(message);
    }
  }
  
  public static void main(String[] args) {
    ChatServer chatServer = new ChatServer(8888);
    chatServer.service();
  }
}

스레드

image

Concurrency(병행성) VS Pararellism(병렬성)

image-20201008021638098

  • Concurrency(병행성): 프로그램의 성질

    • Concurrency는 CPU가 APP을 돌아가면서 명령어를 실행하는 방식이다.
  • Parallel(병렬성): 기계의 성질

    • 병렬 프로그래밍은 자바에서 할 수 없다. 따라서 Parallels Programming은 우리의 학습 대상이 아니다.

멀티 태스킹

image

  • 한 개의 CPU가 여러 코드를 동시(?)에 실행하는 것
  • 실제는 일정한 시간을 쪼개 이 코드와 저 코드를 왔다갔다 하면서 실행한다.
  • 그럼에도 불구하고 외부에서 봤을 때는 명령어가 동시에 실행되는 것처럼 보인다.
    • CPU의 속도가 워낙 빠르기 때문이다.
  • 멀티태스킹을 구현하는 방법은 멀티 프로세싱과 멀티 스레딩으로 나뉜다.

CPU Scheduling

image-20201008021912954

  • CPU의 실행 시간을 쪼개서 배분하는 정책
  • CPU의 실행 시간을 쪼개 코드를 실행하는 방법
  • CPU Scheduling 또는 프로세스 스케줄링이라고 부른다.

image

Round-Robin 방식

  • Windows OS에서 사용하는 방식
  • CPU 실행 시간을 일정하게 쪼개서 각 코드에 분배하는 방식

Priority 방식

  • Unix, Linux에서 사용하는 방식
  • 우선순위가 높은 코드에 더 많은 실행 시간을 배정하는 방식
  • 문제점
    • 우선순위가 낮은 프로그램인 경우 CPU 시간을 배정 받지 못하는 문제가 발생한다.
    • 그리서 몇 년이 지나도록 실행되지 않는 경우가 있다.
  • 해결책
    • CPU 시간을 배정 받지 못할 때마다 우선순위를 높여서 언젠가는 실행되게 만들었다.
    • 이런 방식을 에이징(aging) 기법이라 부른다.

멀티 태스킹을 구현하는 방법

image

image

image

멀티 프로세싱

image

  • 프로세스(실행 중인 프로그램)를 복제하여 분기한다.
  • 분기된 프로세스를 실행시켜서 작업을 동시에 진행하게 한다.
  • 장점
    • 분기하기 쉽다(fort() 호출). 즉 구현(프로그래밍)하기가 쉽다.
  • 단점
    • 프로세스를 그대로 복제하기 때문에 프로세스가 사용하는 메모리도 그대로 복제된다.
    • 메모리 낭비가 심하다.
    • 복제된 프로세스는 독립적이기 때문에 실행 종료할 때도 일일이 종료해야 한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main() {
  int i = 0;
  pid_t processId = 0;
  
  processId = fork();
  
  for (i = 0; i < 10000; i++) {
    printf("[%d] ==> %d\n", processId, i);
    int temp = rand() * rand();
  }
  
  return 0;
}

멀티 스레딩

image

  • 특정 코드만 분리하여 실행한다.
  • 따라서 프로세스가 사용하는 메모리를 공유한다.
  • 장점
    • 프로세스의 힙 메모리를 공유하기 때문에 메모리 낭비가 적다.
    • 모든 스레드는 프로세스에 종속되기 때문에 프로세스를 종료하면 스레드도 자동 종료된다.
  • 단점
    • 프로세스 복제 방식에 비해 코드 구현이 복잡하다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Exam0120 {
  static class MyThread extends Thread {
    @Override
    public void run() {
      for (int i = 0; i < 1000; i++) {
        System.out.println("==> " + i);
      }
    }
  }
  
  public static void main(String[] args) {
    new MyThread().start();
    for (int i = 0; i < 1000; i++)
      System.out.println(">>>" + i);
  }
}

컨텍스트 스위칭 (context switching)

CPU를 실행 시간을 쪼개 이 코드 저 코드를 실행할 때마다 실행 위치 및 정보(context)를 저장하고 로딩하는 과정이 필요하다. 이 과정을 컨텍스트 스위칭이라고 부른다.

스레드 (thread)

  • ‘실’이라는 뜻이다.
  • 한 실행 흐름을 가리킨다.
  • 하나의 실은 끊기지 않은 하나의 실행 흐름을 의미한다.

스레드 생성

  • 새 실을 만든다는 것이다.
  • 즉 새 실행 흐름을 시작하겠다는 의미다.
  • CPU는 스레드를 프로세스와 마찬가지로 동일한 자격을 부여하여 스케줄링에 참여시킨다.
  • 즉 프로세스에 종속된 스레드라고 취급하며 한 프로세스에 부여된 실행 시간을 다시 쪼개 스레드에 나눠주는 방식이 아니다.
  • 그냥 단독적인 프로세스처럼 동일한 실행 시간을 부여한다.

실습

멀티태스킹 전과 후

멀티스레드 적용 전

일반적으로 코드는 위에서 아래로 순서대로 실행한다. 작업이 완료할 때까지 다음 줄로 가지 않는다. 자바는 main() 메서드를 실행하는 한 개의 실행 흐름이 있다. 실행 흐름에 따라 순서대로 코드가 실행된다.

1
2
3
4
5
6
7
8
9
10
// 멀티 스레드 적용 전
public class Exam0110 {
  public static void main(String[] args) {
    for (int i = 0; i < 1000; i++)
      System.out.println("==>" + i);
    
    for (int i = 0; i < 1000; i++)
      System.out.println(">>>" + i);
  }
}

멀티스레드 적용 후

CPU의 시간을 쪼개서 왔다갔다 하면서 동시에 실행하고 싶은 코드가 있다면 다음과 같이 Thread를 상속받아 run() 메서드에 그 코드를 둔다. 즉 기존 실행 흐름과 분리하여 따로 실행시킬 코드를 run() 메서드에 둔다.

main() 메서드 안에서는 동시에 실행할 코드를 담고 있는 Thread 객체를 생성한다. (new MyThread()) 그리고 현재 실행과 분리하여 작업을 시작시킨다. (start()) JVM은 이 스레드에 들어 있는 코드와 다음에 진행하는 코드를 왔다갔다 하면서 처리할 것이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Exam0120 {
  static class MyThread extends Thread {
    @Override
    public void run() {
      for (int i = 0; i< 1000; i++)
        System.out.println("==> " + i);
    }
  }
  
  public static main(String[] args) {
    new MyThread().start();
    
    for (int i = 0; i < 1000; i++)
      System.out.println("==> " + i);
  }
}

즉, main() 메서드를 실행하는 기본 실행 흐름에서 새로운 실행 흐름으로 분기하고 싶다면, Thread 클래스에서 분기해서 실행할 코드를 담으면 된다. 그러면 두 개의 실행 흐름이 서로 왔다갔다 하면서 실행된다.

JVM의 스레드 계층도

  • 스레드 그룹은 디렉토리이고 스레드는 파일이라고 생각하자.
  • 스레드 그룹은 다른 스레드 그룹을 포함할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public static void main(String[] args) {
  Thread mainThread = Thread.currentThread();
  ThreadGroup mainGroup = mainThread.getThreadGroup();
  ThreadGroup systemGroup = mainGroup.getParent();
  
  printThreads(systemGroup, "");
}

static void printThreads(ThreadGroup tg, String indent) {
  System.out.println(indent + tg.getName() + "(TG)");
  
  // 현재 스레드 그룹에 소속된 클래스를 출력하기
  Thread[] threads = new Thread[10];
  // 불린 값은 recursive 여부, 즉 하위 스레드 그룹까지 찾아들어갈 것인지
  int size = tg.enumerate(threads, false);
  for (int i = 0; i < size; i++)
    System.out.println(indent + "  ==> " + threads[i].getName() + "(T)");
  
  ThreadGroup[] groups = new ThreadGroup[10];
  int size = tg.enumerate(groups, false); 
  for (int i = 0; i < size; i++)
    printThreads(groups[i], indent + "  ");
  
}

/*
실행 결과
JVM의 스레드 계층도: (openjdk 8 기준)

system(TG)
  ==> Reference Handler(T)
  ==> Finalizer(T)
  ==> Signal Dispatcher(T)
  ==> Attach Listener(T)
  main(TG)
    ==> main(T)
*/

스레드 만들기

Thread를 상속 받기

  • 구현하기 편하다.
  • 다중 상속이 불가능하기 때문에 다른 클래스를 상속받을 수 없다.
  • 즉 MyThread가 다른 클래스를 상속 받으면서 스레드가 될 순 없다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Exam0110 {
  class MyThread extends Thread {
    @Override
    public void run() {
      // 별도로 분리해서 병행으로 실행할 코드를 두는 것
      for (int i = 0; i < 1000; i++)
        System.out.println("===> " + i);
    }
  }
  
  // 스레드 실행
  // => Thread의 서브 클래스는 그냥 인스턴스를 만들어 start()을 호출한다.
  MyThread t = new MyThread();
  t.start(); // 실행 흐름을 분리한 후 즉시 리턴한다. 비동기로 동작한다.
}
  • CPU 사용을 스레드에게 배분할 때, 스레드를 생성한 순서대로 배분하지는 않는다. OS의 CPU 스케줄링 정책에 따라 스레드가 실행된다. 즉 JVM에서 스레드를 실행하는 것이 아니라 OS가 실행한다.
  • 즉, 똑같은 자바의 스레드 코드가 OS에 따라 실행 순서가 달라질 수 있다.
  • 우선순위를 조정할 수 있을까?
    • Windows OS의 경우 우선 순위(priority) 값이 실행 순서나 실행 회수에 큰 영향을 끼치지 않는다. 따라서 우선순위의 값을 조정하여 스레드의 실행 횟수를 조정하려 해서는 안 된다.
    • OS에 따라 실행 정책이 다르기 때문이다.
    • 그냥 특정 코드를 동시에 실행하고 싶을 때 스레드를 사용한다고 생각하자!

위 코드에서 MyThread 클래스는 한 번만 인스턴스를 생성하여 사용한다. 따라서 익명 클래스로 구현할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Exam0120 {
  public static void main(String[] args) {
    new Thread() {
      @Override
      public void run() {
        for (int i = 0; i < 1000; i++)
          System.out.println("===> " + i);
      }
    }.start();
    
    for (int i = 0; i < 1000; i++)
      System.out.println(">>> " + i);
  }
}

Runnable 인터페이스 구현 + Thread

  • 실무에서 스레드를 만들 때 많이 사용한다.
  • 인터페이스를 구현하는 것이기 때문에 다른 클래스를 상속받을 수 있다.
  • 직접적으로 스레드가 아니기 때문에 실행할 때는 Thread의 도움을 받아야 한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Exoam0210 {
  public class void main(String[] args) {
    class MyRunnable implements Runnable {
      @Override
      public void run() {
        for (int i = 0; i < 1000; i++)
          System.out.println("===>" + i);
      }
    }
    
    // 스레드 실행하기
    // => Runnable 구현체를 Thread 객체에 실어서 실행한다.
    // => start()을 호출하여 기존 스레드에서 분리하여 스레드를 실행시킨다.
    Thread t = new Thread(new MyRunnable());
    t.start(); // 실행 흐름을 분리한 후 즉시 리턴
    
    // "main"스레드는 Thread와 상관 없이 병행하여 실행한다.
    for (int i = 0; i < 1000; i++)
      System.out.println(">>> " + i);
  }
}

Runnable 구현체도 한 번만 생성하기 때문에 익명 클래스로 정의할 수 있다. 또한 Runnable 인터페이스는 메서드가 하나밖에 없기 때문에 람다로도 구현할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
public class Exam0230 {
  public static void main(String[] args) {
    new Thread(() -> {
      for (int i = 0; i < 1000; i++)
        System.out.println("===>" + i);
    }).start();
    
    for (int i = 0; i < 1000; i++)
      System.out.println(">>> " + i);
  }
}

스레드와 프로그램 종료

  • 어떤 스레드에서 만든 스레드를 그 스레드의 자식 스레드라고 부른다.
  • 따라서 아래 코드의 t 스레드는 main스레드의 자식 스레드다.
  • 자식 스레드는 부모 스레드와 같은 우선순위를 갖는다.
  • 즉 다음 코드에서 메인메서드 호출이 끝나더라도 JVM은 종료하지 않고 기다린다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Exam0310 {
  
  static class MyThread extends Thread {
    @Override
    public void run() {
      Scanner keyboard = new Scanner(System.in);
      System.out.println("입력하시오> ");
      String input = keyboard.nextLine();
      System.out.println("입력한 문자열 => " + input);
      keyboard.close();
    }
  }
 
  public static void main(String[] args) {
    // main 스레드에서 새 스레드 객체 생성하기
    MyThread t = new MyThread();
    t.start();
    
    // 모든 스레드가 종료될 때까지 JVM은 종료되지 않는다.
    System.out.println("프로그램 종료?");
  }
}
This post is licensed under CC BY 4.0 by the author.

학원 #52일차: 계산기 Network App 만들기, 스레드

💻 HTTP #9: HTTP 컨텐츠 협상

Loading comments from Disqus ...