41일차
14. 멀티 스레드
하나의 프로그램이 여러가지 일을 하고 싶을때 스레드를 만들어서 실행해야한다.
직접쓸일은 적다.
클래스를 상속받거나 runnable객체를 구현해야한다.
14.2 스레드 상태
14.2.1 thread.sleep()
스레드가 가지고 있는 static메소드이다.
파라미터로 밀리세컨드를 받는다. 현재 진행독 있는 스레들르 멈춤 상태로 만든다.
sleep하면 현재 진행중인 스레드가 잠깐 멈췃다가 실행된다.
public class C02Sleep {
public static void main(String[] args) {
System.out.println("메인 스레드 시작");
Thread t= new Thread(()-> {
System.out.println("스레드 시작");
try {
Thread.sleep(2000); //2초
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("스레드 종료");
});
t.start();
System.out.println("메인 스레드 종료");
}
}
14.2.2 join()
다른스레드의 종료를 기다리는 메소드이다.
다른스레드가 끝날때 까지 기다린다.
특정스레드가 끝난다음 나머지를 같이 진행하고 싶을때 사용한다.
public class C02Sleep {
public static void main(String[] args) {
System.out.println("메인 스레드 시작");
Thread t= new Thread(()-> {
System.out.println("스레드 시작");
try {
Thread.sleep(2000); //2초
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("스레드 종료");
});
t.start();
System.out.println("메인 스레드 종료");
}
}
14.3 스레드 이름
여러 스레드가 동시에 진행되는데 스레드에 이름을 붙이고 싶다.
currentThread() 메소드는 현재 진행되고 있는 스레드를 리턴한다.
스레드의 기본이름은 스레드-번호가 된다.
setName()메소드를 사용하면 스레드의 이름을 바꿀 수 있다
public class C01Name {
public static void main(String[] args) {
//현재 스레드 객체 얻기
Thread t1 = Thread.currentThread();
//스레드 이름 얻기
System.out.println(t1.getName()); //main
//스레드 이름 바꾸기
t1.setName("메인스레드");
System.out.println(t1.getName()); //메인스레드
}
}
스레드 생성자에 runnable객체를 넣고 이름도 같이 넣어서 생성할 수도 있다.
public class C04Name {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
Thread t = Thread.currentThread();
System.out.println(t.getName());
}, "작업스레드1");
t1.start();
}
}
14.4 스레드 동기화
동시에 실행되기 때문에 문제가 발생할 수 있다.
두 스레드가 잇을때 동시에 둘다 작업을 한다.
여러스레드가 어떤 객체 상태를 변경하는 것을 주의해야한다.
public class C01Concurrency {
static int field = 0;
public static void main(String[] args) {
Thread a = new Thread(() -> {
for (int i = 0; i < 100000; i++) {
field++;
}
}, "A Thread");
Thread b = new Thread(() -> {
for (int i = 0; i < 100000; i++) {
field++;
}
}, "B Thread");
a.start();
b.start();
try {
a.join();
b.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(field); //20만이 안됨
}
}
왜 이런일이 발생하나?
스레드a가 값을 넣다가 b가 가져가서 넣고
b가 가져가서 넣다가 a가 넣고 이런식으로 발생해서 그렇다.
그래서 동시에 일을 할때 내가 원하지 않는 일이 발생할 수 있다.
해결책1: 여러 스레드가 하나의 객체상태를 변경하는 코드를 작성하지 않는다.
해결책 2: 동기화블록
14.4.1 동기화 블록
업무를 하나의 블록으보 보자.
a스레드와 b스레드가 경쟁하고 있는데 그만큼을 진행할때 a가일할때 b가 일못하게 b가일할때a가 일못하게 하는것이다.
공통으로 획득할 수 있는 객체를 하나 만들어주면된다.
synchronized을 실행하려면 파라미터로 받는 객체를 획득해야한다.
synchronized작업 이후 획득한 객체를 반납해야한다.
획득 - 실행 - 반납의 순서로 진행되게 된다.
같은 객체를 넣어야된다.
monitor lock / intrinsic lock이라고 한다.
public class C02Concurrency {
static int field = 0;
public static void main(String[] args) {
Object o = new Object();
Thread a = new Thread(() -> {
for (int i = 0; i < 100000; i++) {
synchronized (o) {
field++;
}
}
});
Thread b = new Thread(() -> {
for (int i = 0; i < 100000; i++) {
synchronized (o) {
field++;
}
}
});
a.start();
b.start();
try {
a.join();
b.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(field);
}
}
14.4.2 동기화 메소드
메소드안에 내용이 synchronized 블록이 되어야되면된다.
객체가 하나이니 this 이 객체를 획득 실행 반납 하는 것이다.
그런데 모든내용이 동기화되어야한다면 메소드 자체가 동기화 메소드가 되면 된다.
public void increase() {
synchronized (this) {
item++;
}
}
public synchronized void increase() {
item++;
}
한 스레드가 동기화된 1번을 사용하고 잇으면 다른 스레드는 동기화1번뿐만아니라 2번도 접근하지 못한다.
스레드 1이 setMemory1을 접근하면 setMemory2의 동기화블록도 사용하지 못하게 된다.
작업이 끝나고 동기화가 풀리고 스레드2가 접근해서 setMemory2을 할 수 있다.
synchronized한다고 무조건 객체가 잠기는게 아니다.
lock을 무엇으로 하는지가 중요하다. 같은 객체를 획득 반납 획득반납하는 방식이어야 된다.
아래예제에서 synchronized메소드도 Calculator객체를 획득반납하고
동기화블록 synchronized(this){} this가 Calculator객체이기때문에 획득반납해서
되는 것이다. synchronized(o){} 같이 다른 객체를 lock으로 사용한다면 따로 값이 설정될 것이다.
public class Calculator {
private int memory;
public int getMemory() {
return memory;
}
public synchronized void setMemory1(int memory) {
this.memory = memory;
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ": " + this.memory);
}
public void setMemory2(int memory) {
synchronized (this) {
this.memory = memory;
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ": " + this.memory);
}
}
}
public class SychronizedExample {
public static void main(String[] args) {
Calculator calculator = new Calculator();
User1Thread user1Thread = new User1Thread();
user1Thread.setCalculator(calculator);
user1Thread.start();
User2Thread user2Thread = new User2Thread();
user2Thread.setCalculator(calculator);
user2Thread.start();
}
}
직접 스레드를 제어하려하지 말고 제공되있는 클래스들을 작성하는게 좋다.
14.4.3 ThreadSafe
thread safe integer operation java라고 검색해보자.
AtomicInteger를 써보라고 추천한다.
API를 확인해보면 INT를 atomically하게 관리한다고 되어있다.
이런식으로 멀티스레드 환경에서 대응하기 위한 api 들이 있다.
이런것들을 검색해서 사용해보자.
thread safe를 검색키워드로 해서 검색을 해보자.
public class C04ThreadSafe {
static int field = 0;
static AtomicInteger atomicField = new AtomicInteger(0);
public static void main(String[] args) {
Thread a = new Thread(() -> {
for (int i = 0; i < 100000; i++) {
field++;
atomicField.getAndIncrement(); //++같은 연산하는 메소드
}
});
Thread b = new Thread(() -> {
for (int i = 0; i < 100000; i++) {
field++;
atomicField.getAndIncrement();
}
});
a.start();
b.start();
try {
a.join();
b.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(field); //164174
System.out.println(atomicField); //200000
}
}
14.4.4 ThreadSafe - 2
멀티스레드 환경에서 ArrayList를 사용하면 에러가 생길 수 있다.
그래서 thread safe list java라고 검색해보자.
Collections.synchronizedList()를 사용하면 동기화된 리스트가 된다.
public class C05ThreadSafe {
static List<Integer> list = Collections.synchronizedList(new ArrayList<>());
public static void main(String[] args) {
Thread a = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
list.add(1000);
list.remove(0);
}
});
Thread b = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
list.add(2000);
list.remove(0);
}
});
a.start();
b.start();
try {
a.join();
b.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("종료: " + list.size()); // 0
}
}
15 동기화된 컬렉션
15.1 Vector
ArrayList는 synchronized되어 있지않아서 멀티스레드 환경에서 불안정하다.
그래서 Collections.synchronizedList()를 이용해서 동기화된 리스트로 만들어야 했다.
이외에도 Vector라는 클래스가 있다. Vector는 List를 구현하고 있는 클래스이다.
Vector는 동기화된 메소드로 구성되어 있다.
thread safe한게 필요하다면 Vector를 사용하는게 좋을 수 있다.
그런데 예전 버전의 List라서 Collections.synchronizedList()를 사용하는 것이 더 좋다.
ArrayList와 똑같은데 멀티스레드 환경에서 사용할 수 있다.
public class C06Vector {
static List<Integer> list = new Vector<>();
public static void main(String[] args) {
Thread a = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
list.add(9);
list.remove(0);
}
});
Thread b = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
list.add(9);
list.remove(0);
}
});
a.start();
b.start();
try {
a.join();
b.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("종료: " + list.size()); // 0
}
}
15.2 Hashtable
Hashtable도 synchronized되어 있어서 thread safe하다.
그러나 Hashtable또한 오래된 클래스라 Collections.synchronizedMap();을 사용하는 것이 좋다.
ConcurrentHashMap<>(); 도 있다.
public class HashtableExample {
public static void main(String[] args) {
// Hashtable 컬렉션 생성
Map<String, Integer> map = new Hashtable<>();
Thread threadA = new Thread(() -> {
for (int i = 1; i <= 1000; i++) {
map.put(String.valueOf(i), i);
}
});
Thread threadB = new Thread(() -> {
for (int i = 1001; i <= 2000; i++) {
map.put(String.valueOf(i), i);
}
});
threadA.start();
threadB.start();
try {
threadA.join();
threadB.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
int size = map.size();
System.out.println("총 엔트리 수:" + size); //2000
System.out.println();
}
}
19장 네트워크 입출력
네트워크를 통해서 데이터를 주고받는다.
18장에서 한 것을 네트워크를 통해서 한다.
프로그램끼리 데이터를 주고 받는다면 데이터가 프로그램기준 출력스트림 입력스트림을 기준으로
데이터를 주고 받았다.
네트워크를 사용하려면 A라는 컴퓨터에서 B라는 컴퓨터를 연결해야한다.
그래서
1.B컴퓨터가 어디에 있는지 알아야한다.
2.컴퓨터안에 여러 프로그램이 있는데 특정 프로그램의 위치를 알아야한다.
이 두가지를 알아야 데이터를 주고 받을 수 있다.
19.1 컴퓨터의 주소 알기
전세계 컴퓨터가 네트워크로 연결되어 있다.
컴퓨터마다 주소가 부여되어 있는데 이것을 알아야 주소를 알수 있다.
이것을 IP(Internext Protocol)주소라고 한다.
cmd에서 ipconfig를 치면 ip주소를 알 수 있다.
IPv4 주소로 컴퓨터의 주소를 알아야한다.
19.2 프로그램의 위치알기
하나의 컴퓨터안에 여러 프로그램이 실행된다.
이 프로그램을 구분하는 주소를 Port번호라고 한다.
같은 ip주소 내에서 프로그램마다 번호를 부여받는다.
ip주소를 통해 온다음 port번호를 통해 프로그램으로 찾아가게 되는 것이다.
프로그램마다 Port번호를 각각 가지고 있다.
그래서 사용하지 않는 Port번호를 설정해야 한다.
Port번호는 0
65535번까지 있다.
이미 사용하는 포트번호를 피하기 위해 이미 약속이 있다.
0
1023은 특정프로그램이 차지하고 있다. 웹서버 80번 ftp21번 등등
직접 프로그램을 만들어서 실행한다면 이것들을 사용하면 안된다.
1024~49151 은 회사에서 등록한다. 네트워크관리자가 사용하고 있다면 사용하면안된다.
19152~65535는 마음대로 사용해도 된다.충돌될 확률이 적다.
직접작성할일이 거의없고 잘돌아가는 프로그램을 사용하는 경우가 많다.
개념만 잘 알고 있는 것이 중요하다.
19.3 서버
ServerSocket객체가 필요하다.
리소스를 열었다면 꼭 닫아야하기때문에 try-with-resorce를 사용해보자.
ServerSocket serverSocket = new ServerSocket(port)
데이터를 주고받을려면 인풋스트림과 아웃스트림이 필요하다.
이 스트림을 얻으려면 소켓을 만들어야한다.
전기 선꼽는 콘센트 같은 모양이라고 생각하면된다. 이게 잇어야 선을 연결할 수 있는 것이다.
서버소켓객체로부터 accept()메소드를 실행하면 연결가능하다.
얘들도 닫을 목적으로 try-with-resorce를 사용하자.
소켓만들고 스트림연결하고 작성하고 flush하고 닫으면된다.
accept는 언제 실행되냐면 누군가가 이 컴퓨터에 접근하려면 시작이된다.
누군가 내컴퓨터에 연결해서 소켓을 만들기를 기다린다.
try (ServerSocket serverSocket = new ServerSocket(port)) {
try (Socket socket = serverSocket.accept();
OutputStream os = socket.getOutputStream();
OutputStreamWriter osw = new OutputStreamWriter(os);
BufferedWriter bw = new BufferedWriter(osw);) {
bw.write("Hello Client");
bw.flush();
}
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("프로그램 종료");
}
19.4 클라이언트
서버에 요청을 하는쪽은 클라이언트이다.
클라이언트도 서버의 주소와 포트번호를 통해 소켓을 생성해야한다.
이 소켓으로부터 스트림을 얻고 데이터를 주고받으면된다.
public class C02NetworkClient {
public static void main(String[] args) {
// ip주소
String ip = "172.30.1.60";
// port번호
int port = 50500;
try (Socket socket = new Socket(ip, port);
InputStream is = socket.getInputStream();
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr)) {
char[] buf = new char[1024];
while (true) {
int len = br.read(buf);
if (len == -1) {
break;
}
System.out.print(new String(buf, 0, len));
}
System.out.println("프로그램 종료");
} catch (IOException e) {
e.printStackTrace();
}
}
}
네이버에 들어간다면 ip주소와 포트번호를 얻어서 연결하게 된다.
네이버는 우리를 기준으로 한다면 1번 프로그램을 켜놓고 기다리고 있는 것이었다.
이런 프로그래밍을 앞으로 할 예정이다.
근데 우리는 한번도 네이버의 주소를 ip번호외 포트번호로 들어간적은 없다.
브라우저가 이것을 대신해준다. 네이버의 도메인주소를 입력하면 실제 주소를 찾는다.
Domain Name System에 저장되어 있다.
dns이 도메인의 이름을 네트워크 주소로 바꿔준다.
그래서 우리는 ip주소를 숫자로 알고 있을 필요가 없엇다.
이 도메인이름을 등록하고 유지하는 것이 유료이다. 경쟁이 치열해서 인기있는 ip주소는 비싸다.
서버에서 클라이언트로 줫으니 클라이언트가 서버에게 데이터를 줘보자.
반대로 서버가 받는 입장이 되어 보았다.
public class C03NetworkServer {
public static void main(String[] args) {
int port = 50500;
try (ServerSocket serverSocket = new ServerSocket(port)) {
System.out.println("연결 기다리는 중....");
try (Socket socket = serverSocket.accept();
InputStream is = socket.getInputStream();
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);) {
char[] buf = new char[1024];
while (true) {
int len = br.read(buf);
if (len == -1) {
break;
}
System.out.print(new String(buf, 0, len));
}
System.out.println();
}
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("프로그램 종료");
}
}
public class C04NetworkClient {
public static void main(String[] args) {
// ip주소
String ip = "172.30.1.60";
// port번호
int port = 50500;
try (Socket socket = new Socket(ip, port);
OutputStream os = socket.getOutputStream();
BufferedOutputStream bos = new BufferedOutputStream(os);
PrintStream ps = new PrintStream(bos)) {
ps.println("Hello server, i am Client");
ps.flush();
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("프로그램종료");
}
}
19.5 thread
연결을 기다리고 accpet하는 것은 여러번 일어날 수 있다.
그래서 무한반복을 해야한다.
연결이 수락되면 일 처리를 각각 처리를 해줘야하니 스레드에 넣어줘야한다.
public class C06Server {
public static void main(String[] args) {
int port = 50500;
try (ServerSocket serverSocket = new ServerSocket(port);) {
while (true) {
System.out.println("연결 기다리는중...");
Socket socket = serverSocket.accept();
Thread t = new Thread(() -> {
try (InputStream is = socket.getInputStream();
BufferedInputStream bos = new BufferedInputStream(is);
InputStreamReader isr = new InputStreamReader(bos);) {
char[] data = new char[1024];
System.out.println("입력 받는 중...");
while (true) {
int len = isr.read(data);
if (len == -1) {
break;
}
System.out.println(new String(data, 0, len));
}
socket.close();
} catch (Exception e) {
e.printStackTrace();
}
});
t.start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
2023.03.24 후기
혼자하면서 어려웠던 부분이 확확지나갔다. 더 좋은 경험을 했으면 좋겠다.
'국비 > Java' 카테고리의 다른 글
| 2023.03.23 40일차 Java (0) | 2023.03.23 |
|---|---|
| 2023.03.22 39일차 Java (0) | 2023.03.22 |
| 2023.03.21 38일차 Java (0) | 2023.03.21 |
| 2023.03.20 37일차 Java (0) | 2023.03.20 |
| 2023.03.17 36일차 Java (0) | 2023.03.19 |