runnable 인터페이스로 구현하면 스레드 생성하는 곳에서 new Thread(Runnable 객체).start()
해주면 되고,
Thread 상속한 거는
그 클래스 이름.start() 해주면 된다.
상속 받은 클래스도 어차피 Thread 클래스라고 생각할 수 있으므로!
main/ 스레드 그룹에는 "main"스레드만 존재한다. 하위 그룹도 존재하지 않는다.
이 "main" 스레드는 main() 메소드를 호출한다.
Thread t = Thread.currentThread();
System.out.println("실행 흐름명 = " + t.getName());
MyThread t = new MyThread();
t.start();
start() → 별도의 실행 흐름을 만든 후, run()을 호출한다.
→ 그리고 즉시 리턴한다. → run() 실행이 끝날 때까지 기다리지 않는다.
⇒ 비동기 실행(Asynchronous)
Thread t = new Thread(new MyRunnable());
t.start();
com.bitcamp.board.RequestThread 클래스 삭제
com.bitcamp.board.ServerApp 클래스 변경
ServerApp class
public class ServerApp {
public static void main(String[] args) {
...
// 클라이언트가 연결되면,
Socket socket = serverSocket.accept();
// 클라이언트 요청을 처리할 스레드를 만든다.
RequestThread t = new RequestThread(socket, servletMap);
// main 실행 흐름에서 분리하여 별도의 실행 흐름으로 작업을 수행시킨다.
t.start();
...
}
static class RequestThread extends Thread {
private Socket socket;
private Map<String,Servlet> servletMap;
public RequestThread(Socket socket, Map<String,Servlet> servletMap) {
this.socket = socket;
this.servletMap = servletMap;
}
// 별도의 실행흐름에서 수행할 작업 정의
@Override
public void run() {
try (Socket socket = this.socket;
DataInputStream in = new DataInputStream(socket.getInputStream());
DataOutputStream out = new DataOutputStream(socket.getOutputStream());) {
System.out.println("클라이언트와 연결 되었음!");
String dataName = in.readUTF();
Servlet servlet = servletMap.get(dataName);
if (servlet != null) {
servlet.service(in, out);
} else {
out.writeUTF("fail");
}
System.out.println("클라이언트와 연결을 끊었음!");
} catch (Exception e) {
System.out.println("클라이언트 요청 처리 중 오류 발생!");
e.printStackTrace();
}
}
}
}
com.bitcamp.board.ServerApp 클래스 변경
ServerApp class
public class ServerApp {
public static void main(String[] args) {
...
class RequestThread extends Thread {
private Socket socket;
public RequestThread(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
...
}
}
...
while (true) {
// 클라이언트가 연결되면,
Socket socket = serverSocket.accept();
// 클라이언트 요청을 처리할 스레드를 만든다.
RequestThread t = new RequestThread(socket);
// main 실행 흐름에서 분리하여 별도의 실행 흐름으로 작업을 수행시킨다.
t.start();
}
...
}
}
com.bitcamp.board.ServerApp 클래스 변경
내가 Thread를 실행하면, Thread가 Runnable의 run() 메소드를 실행한다.
ServerApp class
public class ServerApp {
public static void main(String[] args) {
...
// 스레드로 만드는 대신에 Thread가 실행할 수 있는 클래스로 변경한다.
class RequestRunnable implements Runnable {
private Socket socket;
public RequestRunnable(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
...
}
}
...
Socket socket = serverSocket.accept();
// 클라이언트 요청을 처리할 스레드를 만들고
// main 실행 흐름에서 분리하여 별도의 실행 흐름으로 작업을 수행시킨다.
new Thread(new RequestRunnable(socket)).start();
...
}
}
com.bitcamp.board.ServerApp 클래스 변경
ServerApp class
public class ServerApp05 {
public static void main(String[] args) {
...
while (true) {
new Thread(new Runnable() {
Socket socket = serverSocket.accept();
@Override
public void run() {
try (Socket socket = this.socket;
DataInputStream in = new DataInputStream(socket.getInputStream());
DataOutputStream out = new DataOutputStream(socket.getOutputStream());) {
System.out.println("클라이언트와 연결 되었음!");
String dataName = in.readUTF();
Servlet servlet = servletMap.get(dataName);
if (servlet != null) {
servlet.service(in, out);
} else {
out.writeUTF("fail");
}
System.out.println("클라이언트와 연결을 끊었음!");
} catch (Exception e) {
System.out.println("클라이언트 요청 처리 중 오류 발생!");
e.printStackTrace();
}
}
}).start();
}
}
...
}
}
람다로 만들기 위해서
1. 인터페이스 껍데기 걷어내기
2. 파라미터와 메서드 바디만 남긴다.
틀 먼저 만들기:
Thread를 상속 받은 익명 클래스 생성 -> 생성자를 위하여 () 붙이기
new Thread().start();
public class ServerApp {
public static void main(String[] args) {
...
while (true) {
// 여러 클라이언트의 요청을 동시에 처리하기 위해서 클라이언트가 연결되면,
//람다 문법에서는 인스턴스 필드는 처리할 수 없다. 따라서, 다시 로컬변수로 전환한다.
Socket socket = serverSocket.accept(); // 생성자가 생성될 때 함께 실행된다., 클라이언트가 들어올때까지 넘어가지 않는다. blocking method
new Thread(() -> {
try (Socket socket2 = socket;
// socekt을 가지고 입출력 stream 얻기
DataInputStream in = new DataInputStream(socket.getInputStream());
DataOutputStream out = new DataOutputStream(socket.getOutputStream());) {
System.out.println("클라이언트와 연결 되었음!");
// 클라이언트와 서버 사이에 정해진 규칙(protocol)에 따라 데이터를 주고 받는다.
String dataName = in.readUTF();
Servlet servlet = servletMap.get(dataName);
if (servlet != null) {
servlet.service(in, out);
} else {
out.writeUTF("fail");
}
System.out.println("클라이언트와 연결을 끊었음!");
} // 안쪽 try
catch(Exception e) {
System.out.println("클라이언트 요청 중 오류 발생!");
e.printStackTrace();
}
} // Runnable인터페이스 (lambda 문법으로 작성)
).start(); // Thread()
}//while()
} catch (Exception e) {
e.printStackTrace();
} // 바깥 쪽 try
System.out.println("서버 종료!");
}
}
socket.accept()를 Thread 클래스 안에 넣어서 client의 요청을 받든, Thread 클래스 밖에서 client의 요청을 받든 상관없다.
하지만, run() 메소드 안에서 client의 요청을 받으면 큰일난다.
→ 왜냐! accept() 메소드는 client의 요청이 들어올 때까지 while문을 멈추는 역할을 하는 blocking method인데,
이 메소드가 run() 안으로 들어가 버리면, start()는 run() 메소드가 끝나지 않아도 계속 실행하는 비동기적 실행이기에 while문이 멈추지 않고 계속 돌아가서 start()가 계속 호출되어,
이를 통해 무수히 많은 스레드가 계속 생성되어 그 스레드의 run()메서드가 계속 실행되게 되고,
그 무수히 많은 스레드들이 client의 요청을 기다리게 된다.
(메모리를 다 쓸때까지 스레드가 만들어짐.)
⇒ 즉, 너무 많은 스레드가 만들어지는 것을 방지하기 위해 blocking 역할을 하기 위해 accpet()를 run() 메소드 밖에서 받아준다.
run() 밖에서 걸리면 Runnable 객체가 생성이 안되어서 start()가 실행이 안된다.
한 번 끝난 스레드 변수는 재 start() 할 수 없다. → 그래서 굳이 레퍼런스 변수에 저장할 필요가 없다.
익명 클래스는 클래스가 어떤 코드를 가지고 있는지 바로 확인할 수 있다.
하지만, 너무 코드가 길고 복잡하다면, 일반 클래스로 분리하는 것이 좋다.