Thread란?
필자는 컴퓨터 조립을 좋아해서 CPU에 관심이 많아 많이 들어봤던 단어였다. 물리적으로 가볍게 생각해보면
CPU코어는 내가 부릴 노예의 수 이고 그 CPU코어의 쓰레드는 내가 부릴 노예의 손 개수라고 생각하면 된다.
그럼 정확하게 자바에서 말하는 Thread는 무엇일까?
프로그램 내에서 실행의 흐름을 가지고 있는 최소 단위. 라고 말한다.
우리가 처음에 항상 프로그래밍하던 main문도 main 쓰레드가 실행된다는 것이다.
그리고 쓰레드는 싱글쓰레드와 멀티쓰레드로 나뉘는데 둘의 차이는 쓰레드의 개수이다.
또 CPU의 일처리 방식은 멀티쓰레드라고 해서 (손이 많다고해서) 여러가지 일을 동시에 진행시키는게 아니라
엄청나게 빠른속도로 프로그램을 왔다갔다 하면서 우리에게는 마치 동시에 실행하는 것처럼 보여지는 것이다.
- 싱글 쓰레드 : 쓰레드가 하나이므로 A프로세스와 B프로세스를 동시에 처리하려면 A가 끝나고 B를 실행해야한다.
- 멀티 쓰레드 : 쓰레드가 여러개이다. A프로세스와 B프로세스를 엄청나게 빠른 속도로 왔다갔다하면서 실행하는 것이다.
그럼 진짜 병렬로 실행시키는게 무엇이 있을까?
GPU다. GPU는 컴퓨터 부품중에 하나인 그래픽카드에 있는 코어이며 GPU는 병렬로 처리가 가능하다.
즉 CPU처럼 빠르게 왔다갔다하며 실행시키는 가짜멀티가 아니라 진짜로 동시에 실행시키는 병렬진행이다.
또 멀티프로세스와 멀티쓰레드라는 말이 있는데
이 둘은 다른 말이고 간단하게 설명하자면
- 멀티프로세스 : 그냥 프로세스(프로그램)이 여러개 실행되는 것이다.
- 멀티쓰레드 : 한 개의 프로세스(프로그램)을 여러개의 쓰레드로 빠르게 진행시킨다.
여기까지가 쓰레드에 대해 간단히 알아보았고 이후로는 예전에 포스팅했었던 I/O 입출력을 이용하여
단방향 채팅이 아닌 양방향 채팅으로 만들어보겠다.
대략적인 흐름은 이렇게 짜여져있다.
1. ServerReceive : 클라이언트로부터 메세지를 받아서 보여주는 클래스
2. ServerSend : 클라이언트에게 메세지를 보내는 클래스
3. ClientRecevie : 서버에게 메세지를 받아서 보여주는 클래스
4. ClientSend : 서버에게 메세지를 보내는 클래스
5. TCPServer : ServerSocket과 Socket 클래스를 사용하여 받아주는 서버를 만들고 Thread 클래스를 사용하여 메세지를 보내는 역할과 받는 역할을 동시에 실행시킴
6. TCPClient : Socket 메소드로 접속할 host의 IP와 Port를 입력하고 Thread 클래스로 받기, 보내기를 동시에 실행시킴.
마지막으로 사용됐던 클래스에 대해 알아보고 코드를 봐보도록하자 !
ServerSocket 클래스
서버에서 사용되는 클래스이고 클라이언트에서 연결이 오면 새로운 Socket을 만들어서 클라이언트의 Socket과 연결을 주고 받는다. accept를 사용하여 클라이언트의 연결을 기다린다. (기다렸다 받아준다)
Socket 클래스
서버에서도 클라이언트에서도 사용되고 보통 클라이언트가 Server에게 연결을 위해, 데이터 전송을 위해 사용한다.
Thread클래스
간단하게 main 문의 run이 실행되면 다른 작업은 할 수 없지만, main문의 run을 다른 클래스에서 오버라이딩하여 동시에 실행할 수 있게 해준다.
* implements Runnable 을 해줘야한다.
* "멀티쓰레드로 진행시켜는" .start() 메소드이다.
1. ServerReceive
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
public class ServerReceive implements Runnable{ //멀티 쓰레드로 사용하기 위해서 추상클래스Runnable의 run메소드를 오버라이딩 해줘야한다.
private Socket socket;
public ServerReceive(Socket socket) {
this.socket=socket;
}
@Override
public void run() {
try { //I/O Stream은 오류예외처리가 필수적이다
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream())); //I/O를 사용하여 입력받는 통로 열어준다.
while(true) { //한쪽의 연결이 끝날 때 까지 계속에서 실행
String message = br.readLine();
System.out.println("클라이언트로부터 전달된 메세지 :" + message);
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
2.ServerSend
import java.io.IOException;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;
public class ServerSend implements Runnable{ //멀티 쓰레드로 사용하기 위해서 추상클래스Runnable의 run메소드를 오버라이딩 해줘야한다.
private Socket socket;
public ServerSend(Socket socket) {
super();
this.socket = socket;
}
@Override
public void run() { // I/O Stream은 예외처리가 필수 !
try (PrintWriter writer = new PrintWriter(socket.getOutputStream(),true);
Scanner sc = new Scanner(System.in)){
while(true) {
String message = sc.nextLine();
writer.println(message); //버퍼에 message를 쓰고
writer.flush(); //버퍼에 쓴 message를 flush로 밀어줘야 보내진다.
}
}catch(IOException e) {
e.printStackTrace();
}
}
}
3. ClientRecevie
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
public class ClientReceive implements Runnable{ //멀티 쓰레드로 사용하기 위해서 추상클래스Runnable의 run메소드를 오버라이딩 해줘야한다.
private Socket socket;
public ClientReceive(Socket socket) {
this.socket = socket;
}
@Override
public void run() { // I/O Stream은 예외처리가 필수!
try {
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream())); // Input 스트림 열어주고!
while(true) { //한쪽의 연결이 끝날 때 까지 실행
String message = br.readLine();
System.out.println("서버부터 전달된 메세지 :" + message);
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
4. ClientSend
import java.io.IOException;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;
public class ClientSend implements Runnable{ //멀티 쓰레드로 사용하기 위해서 추상클래스Runnable의 run메소드를 오버라이딩 해줘야한다.
private Socket socket;
public ClientSend(Socket socket) {
super();
this.socket = socket;
}
@Override
public void run() { // I/O Stream은 예외처리가 필수 !
try (PrintWriter writer = new PrintWriter(socket.getOutputStream(),true); //출력 통로 만들어주고!
Scanner sc = new Scanner(System.in)) {
while(true) {
String message = sc.nextLine();
writer.println(message); //버퍼에 메세지 입력하고!
writer.flush(); //flush로 밀어서 보내기!
}
}catch(IOException e) {
e.printStackTrace();
}
}
}
5. TCPServer
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class TCPserver {
public static void main(String[] args) {
int port = 3000; //임의의 포트번호 설정
try {
ServerSocket serverSocket = new ServerSocket(port); //ServerSocket 클래스에 port번호를 넣어서 객체 생성
System.out.println("서버 연결준비 완료....");
Socket socket = serverSocket.accept(); //accept 메소드를 사용하여 입력대기 !
System.out.println(socket.getInetAddress().getHostAddress() + "에서 연결 함...");//입력이 오게 되면 나에게 입력한 사람의 주소 출력!
//클라이언트에게 메세지를 받는 구역
ServerReceive receive = new ServerReceive(socket); //우리가 위에 만들었던 ServerReceive 클래스에 socket 넣어서 객체생성
Thread receiveTask = new Thread(receive); //Thread로 실행하기 위해 방금 만들었던 receive 객체를 넣어준다.
receiveTask.start(); // start 메소드를 사용하면 main의 run과 같이 실행된다!
//클라이언트로부터 메세지를 보내는 쓰레드
ServerSend send =new ServerSend(socket); // 메세지를 보내기위해 만들었던 ServerSend클래스에 Socket객체인 socket을 넣어서 객체생성
Thread sendTask = new Thread(send); // Thread로 실행하기위해 마지막으로 만든 객체 send를 넣어주고
sendTask.start(); //start 메소드로 멀티쓰레드 실행
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
6. TCPClient
import java.io.IOException;
import java.net.Socket;
public class TCPClient2 {
public static void main(String[] args) {
try {
Socket socket = new Socket("localhost",3000); //내가 접속하고자 하는 Server의 IP주소를 입력해준다.
// localhost는 자기자신의 IP를 가져온다.
//서버로부터 메세지를 받는 쓰레드
ClientReceive task1 = new ClientReceive(socket); //메세지를 받기위해 접속할 IP와 포트번호를 가지고 ClientRecevie클래스로 객체 생성
Thread t1 = new Thread(task1); //멀티쓰레드로 돌리기 위해 Thread 클래스로 다시 객체 생성
t1.start(); //start메소드로 멀티쓰레드 진행
//서버에 메세지를 보내는 쓰레드
ClientSend task2 = new ClientSend(socket); //메세지를 보내기위해 ClientSend에 socket객체를 보내어 task2 객체생성
Thread t2 = new Thread(task2); //멀티쓰레드로 진행시키기위해 task2 객체를 넣어줌
t2.start(); //start메소드
} catch (IOException e) {
e.printStackTrace();
}
}
}
마지막으로 Thread의 장단점에 대해 알아보고 포스팅을 마치겠다!
/ | 멀티쓰레드 (multi Thread) | 싱글 쓰레드(single Thread) |
안정성 | 낮음 | 높음 |
코딩 | 어려움 | 쉬움 |
디버깅 | 어려움 | 쉬움 |
확장성 | 좋음 | 나쁨 |
안정성 외 다른 것들은 상상해보면 그러려니 할 수 있다.
하지만 안정성은 왜 문제가 될까?
간단한 코드로 예를 들어보면
밑에 코드는 각자 0~19까지의 홀수와 짝수를 멀티쓰레드로 동시에 출력하는 코드이다.
public class Run {
public static void main(String[] args) {
task1 t1 = new task1();
task2 t2 = new task2();
t1.start();
t2.start();
}
public static class task1 extends Thread{
public void run() {
for(int i = 0; i < 20 ; i++) {
if(i % 2 ==0) {
System.out.println("task 1 : " + i + "짝수");
}
}
}
}
public static class task2 extends Thread{
public void run() {
for(int i = 0; i < 20 ; i++) {
if(i % 2 !=0) {
System.out.println("task 2 : " + i + "홀수");
}
}
}
}
}
만약 우리가 멀티쓰레드를 사용하지 않았다면, 먼저 실행된 task1 이 모두 실행되고 task2 가 실행되어 짝수가 쭉 나오고 홀수가 나와야 정상인데, 멀티쓰레드로 실행시킨 결과는
지멋대로 나온다.
컴퓨터는 컴퓨터의 사정이 있겠지만 이런 특징때문에 개발자의 의도를 벗어날 수 있어 안정성이 떨어진다고 할 수 있겠다.
'JAVA 정리노트' 카테고리의 다른 글
Spring, 중첩 트랜잭션(Nested Transaction) (0) | 2024.10.08 |
---|---|
JAVA 메모리에 관하여 (0) | 2024.07.21 |
Big-O Notation, (시간,공간 복잡도) (0) | 2024.02.11 |
Java 객체 비교해보기(ArrayList, Collections, Random를 사용한 정렬) (2) | 2024.02.04 |
JAVA [GC, Garbage Collection] 정리본 (0) | 2024.02.03 |