본문 바로가기
☁︎KOSTA/☂︎KOSTA-JAVA

[KOSTA] Spring 기반 Cloud 서비스 구현 개발자 양성 (Day 21) - synchronized (동기화), Network, multi threading, Single thread, thread-safe

by 하_센세 2022. 8. 29.

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 예습!!