2022.08.29 [MON]
- Day 21-
수업 주요 목차
- synchronized (동기화)
- Network
- Multi threading
- Single thread
- Thread-safe
🤖Review
Daemon Thread : 자신을 생성한 thread가 종료되면 함께 종료되는 thread
java.lang.Thread의 setDaemon(true)로 설정한다.
ex) Word Thread가 종료되면 BackUp thread또한 함께 종료되어야 하므로 BackUp thread를 Daemon thread로 설정한다.
🔎 간단리뷰
1. Thread 1
* thread: process 내의 세부적 실행단위
* process: 현재 실행중인 프로그램
* multi-threading: 다수의 스레드가 동시에 시작
package step1;
public class TestThread8 {
public static void main(String[] args) {
//main method 실행하는 main thread
String threadName=Thread.currentThread().getName();
System.out.println(threadName+"Thread Start");
ServerWorker worker=new ServerWorker();
Thread thread=new Thread(worker,"1번"); //aggregation || composition
thread.start();
Thread thread2=new Thread(worker,"2번"); //aggregation || composition
thread2.start();
System.out.println(threadName+"Thread Finished");
}
}
package step1;
public class ServerWorker implements Runnable {
@Override
public void run() {
String threadName=Thread.currentThread().getName();
for(int i=0;i<5;i++) {
System.out.println(i+"_"+threadName+" 실행");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
↓
mainThread Start
mainThread Finished
0_1번 실행
0_2번 실행
1_2번 실행
1_1번 실행
2_2번 실행
2_1번 실행
3_2번 실행
3_1번 실행
4_2번 실행
4_1번 실행
지역 변수(local variable)를 저장하는 stack 메모리 영역은 thread 별로 생성되어 독립적으로 관리된다. | 인스턴스 변수(instance variable)를 저장하는 heap 메모리 영역은 하나가 존재하여 여러 thread 가 공유할 수 있다. |
Multi threading 시 data(heap 영역에 있는 instance variable 정보)를 공유할 수 있다.
예) 로그인서블릿 객체는 하나만 생성하여 여러 thread에서 공유해서 사용하고 client 담당 thread는 client 수만큼 생성해서 서비스한다.
synchronized (동기화) : 특정 영역을 단일 thread 환경으로 만드는 것
예 1) 여러 고객들이 영화 좌석을 예매할 때 동일한 좌석을 예매하는 상황을 방지하기 위해 예매 시점에서는 단일 thread 환경으로 처리
예 2) 여러 손님들이 카페 하나의 화장실을 이용할 때 그 시점에서는 동시에 사용할 수 없으므로 단일 thread 환경으로 처리
* multi threading => 여러 thread가 하나의 자원을 공유해서 사용할 때
=> multi threading시 특정 영역에서는 반드시 단일 thread 환경으로 처리해야 하는 경우 synchronized keyword를 이용한다.
=> multi threading시 동기화 처리를 하면 공유 정보에 대한 안전성과 신뢰성을 높인다.(thread-safe)
* Single thread(단일 thread) 환경=>다른 thread들이 작업에 참여할 수 없게 하는 것이다.
1) method 단위에서 동기화 처리
public synchronized void reserve();
2) 특정 영역 단위에서 동기화 처리
public void reserve(){
synchronized(this){
}
}
app이 DB와 연결할 때마다 connection을 생성하고 다 쓴 뒤 소멸시키는 방식은 성능저하를 일으킨다. 몇개의 connection을 사용할지 먼저 만들어둔 후 빌려주고 반납하는 식으로 성능을 향상시킬 수 있는데 이 때 빌려주고 반납하는 과정은 동기화 시켜 각각 단일 thread 환경을 만들어준다.
-java 주요 class들의 thread synchronized (thread-safe)
1) 문자열 관련
String : 불변, 변경시 새로 생성, 문자열 영역에 공유, 동일한 문자열이 자주 쓰일 때
StringBuilder: 가변, 변경시 문자열 자체가 변경, 문자열 자체가 자주 변경될 때, 단일 thread 환경에 적합
StringBuffer: 가변, 변경시 문자열 자체가 변경, 문자열 자체가 자주 변경될 때, 멀티 thread 환경에 적합
2) Collection 계열 관련
thread-safe | |
ArrayList | Vector |
HashMap | HashTable |
위의 Vector와 Hashtable은 동기화 처리되어 있지만 최근 List나 Map, Set 계열의 동기화 처리 시에는
Collections.synchronizedList()
Collections.synchronizedMap()
Collections.synchronizedSet()을 이용하는 것을 추천한다.
(이후 네트워크 채팅 구현에서 사용해볼 예정)
🔎 Eclipse 실습 내용
1. Thread 2
- 지역 변수(local variable)를 저장하는 stack 메모리 영역은 thread 별로 생성되어 독립적으로 관리
- 인스턴스 변수(instance variable)를 저장하는 heap 메모리 영역은 하나가 존재하여 여러 thread 가 공유할 수 있는 것을 확인하는 예제
package step2;
public class TestThread9 {
public static void main(String[] args) {
System.out.println("main thread start");
SearchWorker worker=new SearchWorker();
new Thread(worker,"1번 검색 일꾼 thread").start();
new Thread(worker,"2번 검색 일꾼 thread").start();
new Thread(worker,"3번 검색 일꾼 thread").start();
System.out.println("main thread finished");
}
}
package step2;
public class SearchWorker implements Runnable {
private String info="heap영역에 저장된 정보";
private int count; // 인스턴스 변수이므로 0으로 초기화
@Override
public void run() {
searchService();
}
public void searchService() {
String threadName=Thread.currentThread().getName();
for(int i=0;i<5;i++) {
System.out.println(threadName+"지역변수: "+i+" 인스턴스변수: "+info+count++);
}
}
}
↓
main thread start
main thread finished
1번 검색 일꾼 thread지역변수: 0 인스턴스변수: heap영역에 저장된 정보0
2번 검색 일꾼 thread지역변수: 0 인스턴스변수: heap영역에 저장된 정보1
3번 검색 일꾼 thread지역변수: 0 인스턴스변수: heap영역에 저장된 정보3
1번 검색 일꾼 thread지역변수: 1 인스턴스변수: heap영역에 저장된 정보2
1번 검색 일꾼 thread지역변수: 2 인스턴스변수: heap영역에 저장된 정보6
3번 검색 일꾼 thread지역변수: 1 인스턴스변수: heap영역에 저장된 정보5
2번 검색 일꾼 thread지역변수: 1 인스턴스변수: heap영역에 저장된 정보4
3번 검색 일꾼 thread지역변수: 2 인스턴스변수: heap영역에 저장된 정보8
1번 검색 일꾼 thread지역변수: 3 인스턴스변수: heap영역에 저장된 정보7
3번 검색 일꾼 thread지역변수: 3 인스턴스변수: heap영역에 저장된 정보10
2번 검색 일꾼 thread지역변수: 2 인스턴스변수: heap영역에 저장된 정보9
3번 검색 일꾼 thread지역변수: 4 인스턴스변수: heap영역에 저장된 정보12
1번 검색 일꾼 thread지역변수: 4 인스턴스변수: heap영역에 저장된 정보11
2번 검색 일꾼 thread지역변수: 3 인스턴스변수: heap영역에 저장된 정보13
2번 검색 일꾼 thread지역변수: 4 인스턴스변수: heap영역에 저장된 정보14
지역변수 i는 별도의 stack에서 각각 저장되므로 자신만의 누적값이 출력된다.
인스턴스변수 info는 하나의 heap에 저장되므로 여러 thread에서 공유하여 사용할 수 있다.
count 인스턴스 변수는 여러 thread에 의해 공유되므로 thread별이 아니라 실행 횟수만큼 누적되어 출력된다.
2. Thread 3
- Synchronized 필요성을 확인하는 예제
- 여러 thread가 하나의 자원을 공유할 때(multi threading 시) 특정 업무 처리 영역에서는 단일 스레드(single thread) 환경으로 만들어 실행시켜야 한다.
package step3;
public class TestThread10 {
public static void main(String[] args) {
Toilet toilet=new Toilet();
Thread thread1=new Thread(toilet, "클레오");
Thread thread2=new Thread(toilet, "파트라");
thread1.start();
thread2.start();
}
}
package step3;
public class Toilet implements Runnable {
private String room="1번 화장실칸";
@Override
public void run() {
String threadName=Thread.currentThread().getName();
try {
use(threadName);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public synchronized void use(String userName) throws InterruptedException {
System.out.println(userName+"님"+room+"입장");
Thread.sleep(2000);
System.out.println(userName+"님"+room+"사용중");
Thread.sleep(2000);
System.out.println(userName+"님"+room+"나감");
}
}
// 화장실을 사용하는 method는 multi thread가 아니라 single thread 환경으로 실행된다. (Thread1 완료->Thread2...
↓
클레오님1번 화장실칸입장
클레오님1번 화장실칸사용중
클레오님1번 화장실칸나감
파트라님1번 화장실칸입장
파트라님1번 화장실칸사용중
파트라님1번 화장실칸나감
3. Thread 4
- multi threading 시 Synchronized(동기화) 처리
- 여러 고객이(thread) 공유 자원인 극장 좌석을 예매할 때 예매 지점에서는 동시에 실행되어 동일한 좌석이 예매되지 않도록 동기화 처리를 해서 single thread환경으로 만든다.
package step4;
public class TestThread11 {
public static void main(String[] args) {
Theater theater=new Theater();
new Thread(theater,"아이유").start();
new Thread(theater,"손흥민").start();
}
}
package step4;
public class Theater implements Runnable{
private int seat=1; // 공유 자원
@Override
public void run() {
String threadName=Thread.currentThread().getName();
try {
//reserve(threadName);
reserve2(threadName);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//동일한 좌석이 예매될 수 있다.
public void reserve(String customer) throws InterruptedException {
Thread.sleep(100);
System.out.println(customer+"님 "+seat+"번 예매하셨습니다.");
Thread.sleep(100);
seat++;
}
public void reserve2(String customer) throws InterruptedException {
Thread.sleep(100);
//아래 영역만 동기화 해서 single thread 환경으로 만든다
synchronized(this) {
System.out.println(customer+"님 "+seat+"번 예매하셨습니다.");
seat++;
}
Thread.sleep(100);
}
}
↓
아이유님 1번 예매하셨습니다.
손흥민님 2번 예매하셨습니다.
Network
Java 기반 TCP/IP 네트워크 프로그래밍
Protocol(프로토콜) : 약속, 통신규약
TCP/IP : 인터넷 통신 규약
TCP(Transmission Control Protocol) : 전송을 제어하는 프로토콜 -> 데이터 전달 보증
IP(Internet Protocol) : IP Address -> 명령 프롬프트에서 ifconfig | grep inet 로 확인
DNS(Domain Name System) :사용자 접근성을 높이기 위해 ip에 연결된 Domain Name을 사용; 내 컴퓨터 도메인 네임 localhost
예) www.google.com, www.naver.com
Port(포트) : 서비스 번호(입구), 가상의 연결단위
http://127.0.0.1:8888
http -> protocol
127.0.0.1 -> ip
8888 -> port
일반적으로 사용하는 웹 port인 80 port는 생략가능
Socket : 네트워크 연결의 양끝단위 (end point) 로서 통신을 위한 인터페이스를 의미
ex) 전화기
ex) client 의 예
Socket(server ip,port)
socket.getOutputStream() => 서버로 출력하기 위한 스트림
socket.getInputStream() => 서버에서 입력받기 위한 스트림
ServerSocket : 서버에서 생성하는 서버소켓
ex) 대표전화의 역할, 접수처의 역할
accept() : Socket => 클라이언트의 접속을 대기하다 접속하면 실행, 일반 Socket을 return해준다. 이 일반 socket이 클라이언트와 통신하게 된다.
ex) Server의 예
ServerSocket(port)
serverSocket.accept() : Socket
serverSocket.getInputStream() ;
serverSocket.getOutputStream() ;
🔎 Eclipse 실습 내용
package common에 IP주소들 String에 담아 interface class에 저장되어 있음
1. Network 1
-Server
package step1.server;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
public class NetServer1 {
public void go() throws IOException {
ServerSocket serverSocket=new ServerSocket(5432); // 대표전화, 접수처 역할
System.out.println("**NetServer1 서버 실행**");
Socket socket=serverSocket.accept(); // 클라이언트 접속을 대기, 클라이언트가 접속하면 실행, 클라이언트의 연결 정보를 가진 Socket을 반환 (직원 전화기)
String clientIp=socket.getInetAddress().toString();
System.out.println(clientIp+"client 접속");
PrintWriter pw=new PrintWriter(socket.getOutputStream(),true); // true - auto flush
pw.println("Hello IU");
socket.close();
serverSocket.close();
System.out.println("**NetServer1 서버가 클라이언트에게 메세지 출력 후 종료**");
}
public static void main(String[] args) {
try {
new NetServer1().go();
} catch (IOException e) {
e.printStackTrace();
}
}
}
-Client
package step1.client;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
import java.net.UnknownHostException;
import common.IP;
public class NetClient1 implements IP {
public void go() throws UnknownHostException, IOException {
//Socket socket=new Socket("127.0.0.1",5432);// 내 컴퓨터의 ip로 테스트
String inst=IP.INST;
Socket socket=new Socket(inst,5432);// 강사 컴퓨터의 ip로 테스트
System.out.println("NetClient1 서버에 접속**");
// InputStreamReader로 8bit ByteStream을 16bit 단위의 CharacterStream으로 변환
BufferedReader br=new BufferedReader(new InputStreamReader(socket.getInputStream()));
System.out.println("서버가 보낸 메세지:"+br.readLine());
socket.close();
}
public static void main(String[] args) {
try {
new NetClient1().go();
} catch (IOException e) {
e.printStackTrace();
}
}
}
↓
* 클라이언트의 경우 강사님 IP주소는 지금 사용이 불가능해서 강제종료 후 IP 주소를 로컬로 바꿔준 뒤 다시 실행했다.
2. Network 2
* 클라이언트가 접속하면 데이터를 출력하고 접속 해제 후 다시 대기 * 다른 클라이언트가 접속하면 데이터를 출력하고 접속 해제 후 다시 대기 * * 다수의 클라이언트를 순차적으로 접수받아 출력서비스 하는 서버 * * while(true){ * Socket socket=serverSocket.accept(); * socket.getOutputStream(); * socket.close(); * } |
-Server
package step2.server;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
//NetServer2 서버를 실행하고 step1 NetClient1을 실행해서 테스트하면 된다.
public class NetServer2 {
ServerSocket serverSocket=null;
public void go() throws IOException {
try {
serverSocket = new ServerSocket(5432);
System.out.println("**NetServer2 서버 실행**");
while (true) {
Socket socket = serverSocket.accept(); // 대기하다가 클라이언트 접속하면 실행, 소켓(직원전화기)을 반환
String clientIp = socket.getInetAddress().toString();
System.out.println(clientIp + "ip 클라이언트가 접속");
PrintWriter pw = new PrintWriter(socket.getOutputStream(), true);
pw.println(clientIp + "님 환영합니다. 저만 집에 가고 싶나요?");
socket.close();
}
} finally {
if (serverSocket != null)
serverSocket.close();
}
}
public static void main(String[] args) {
try {
new NetServer2().go();
} catch (IOException e) {
e.printStackTrace();
}
}
}
↓
3. Network 3
- Server는 client 접속을 대기하다 접속하면 클라이언트가 보낸 메세지를 입력받아 서버 자신의 화면에 그 메세지를 출력하는 작업을 클라이언트 접속시마다 반복한다.
- Client는 NetServer3에 접속해 메세지를 출력하고 종료하는 클라이언트 프로그램
-Server
package step3.server;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
public class NetServer3 {
public void go() throws IOException {
ServerSocket serverSocket = null;
try {
serverSocket = new ServerSocket(5432);
System.out.println("**NetServer3 서버 실행**");
while (true) {
Socket socket = serverSocket.accept();
String clientInfo = socket.getInetAddress().toString();
System.out.println(clientInfo + " 클라이언트가 접속");
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
System.out.println(clientInfo + " 님의 메세지: " + br.readLine());
socket.close();
}
} finally {
if (serverSocket != null)
serverSocket.close();
}
}
public static void main(String[] args) {
try {
new NetServer3().go();
} catch (IOException e) {
e.printStackTrace();
}
}
}
-Client
package step3.client;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;
import common.IP;
public class NetClient3 implements IP {
public void go() throws UnknownHostException, IOException {
Socket socket=new Socket(IP.INST,5432);
System.out.println("**NetClient3 서버에 접속**");
PrintWriter pw=new PrintWriter(socket.getOutputStream(),true);
pw.println("An nyoung ha si ryup ni gga?");
System.out.println("**메세지 출력**");
//getOutputStream() PrintWriter : 서버로 메세지 출력
socket.close();
}
public static void main(String[] args) {
try {
new NetClient3().go();
} catch (IOException e) {
e.printStackTrace();
}
}
}
↓
// 오늘의 단축키
터미널에서 control+c => 프로세스 강제종료
//오늘의 숙제
복습+!!Overriding&Polymorphism 예습!!