이전에는 Object type만 사용 - 자료구조의 내용물을 알기 어려움
=> 자료구조 내에 들어가는 자료의 type을 제한! = 제네릭
public class GenericEx {
public static void main(String[] args) {
// ArrayList list = new ArrayList(); //이전의 사용 - 기본자료형 사용 불가
ArrayList<String> list = new ArrayList(); //문자열만 넣고싶을때
list.add("AAA");
//list.add(new Integer(1));
//위에서 문자열로 type을 제한했기때문에 Integer에 사용불가표시뜬것 = 제네릭
//제네릭 = 타입제한
}
}
public class Sample1<E> { //클래스자체를 제네릭화
private E inner;
//E = type을 나중에 결정하고싶을때
public static void main(String[] args) {
Sample1<String> obj = new Sample1(); //E = String 문자열로 지정해준것
obj.inner = "AAA"; //문자열을 넣었을때 에러X
Sample1<Integer> obj2 = new Sample1(); //E = String 문자열로 지정해준것
obj2.inner = new Integer(10); //가능은한데 취소선왜지
//똑같은 객체를 그때그때 필요한 type에 맞춰서 사용가능
// = 제네릭의 진짜 용도 = 유연한 코드 생성 가능
//하지만 직접 제네릭 선언하는 경우는 거의 없음 - 알아만두기
}
}
보통 제네릭(Generic)은 클래스 내부에서 지정하는 것이 아닌 외부에서 사용자에 의해 지정되는 것을 의미
1. 특정 타입만 입력 가능하도록 제한
2. 아직 타입을 특정하지 않았을때 나중에 지정해줄 수 있도록 열어두는? 개념
하지만 2번은 잘 사용하지 않아서 보통 1번기능 타입제한자로 사용함
보통 HashMap을 만들때도 제네릭을 사용해서 특정 타입만 입력 가능하도록 제한
public class ListTest {
public static void main(String[] args) {
//기본자료형은 안되고 Object만 가능함
//이 list 내에는 NoteDTO type만 들어갈 수 있음
ArrayList<NoteDTO> list = new ArrayList<>();
//배열의 경우 추가할때 인덱스 번호 알아야만 추가가능
// ArrayList는 순서 따로 없어서 그냥 추가하면됨
NoteDTO obj = NoteDTO.builder().build();
list.add(obj);
//위의 두 코드를 줄여서 아래와 같이 표현
list.add(NoteDTO.builder().no(1).who("A").whom("B").build());
list.add(NoteDTO.builder().no(2).who("A").whom("B").build());
list.add(NoteDTO.builder().no(3).who("B").whom("A").build());
list.add(NoteDTO.builder().no(4).who("B").whom("A").build());
list.add(NoteDTO.builder().no(5).who("A").whom("B").build());
//몇개나 찼는지 알 수 있음 (배열의 length와 같은 역할)
System.out.println(list.size());
//특정 값만 뽑고싶을때 = filter사용 true인 값만 추출
//현재 쪽지프로그램에서는 특정 사용자에게 도착한 쪽지만 추출할 때 사용하면됨
List<NoteDTO> blist
= list.stream().filter(noteDTO -> noteDTO.getWhom().equals("B")).collect(Collectors.toList());
System.out.println(blist);
}
}
5
[NoteDTO(no=1, who=A, whom=B, content=null), NoteDTO(no=2, who=A, whom=B, content=null), NoteDTO(no=5, who=A, whom=B, content=null)]
ArrayList에는 remove 기능 있음
public class ListTest {
public static void main(String[] args) {
ArrayList<NoteDTO> list = new ArrayList<>();
list.add(NoteDTO.builder().no(1).who("A").whom("B").build());
list.add(NoteDTO.builder().no(2).who("A").whom("B").build());
list.add(NoteDTO.builder().no(3).who("B").whom("A").build());
list.add(NoteDTO.builder().no(4).who("B").whom("A").build());
list.add(NoteDTO.builder().no(5).who("A").whom("B").build());
System.out.println(list.size());
//삭제
//ArrayList에는 remove기능이 있음
list.remove(2); //3rd
System.out.println(list);
System.out.println(list.size());
}
}
remove와 인덱스 번호를 이용해서 list의 3번째 data 삭제
5
[NoteDTO(no=1, who=A, whom=B, content=null), NoteDTO(no=2, who=A, whom=B, content=null), NoteDTO(no=4, who=B, whom=A, content=null), NoteDTO(no=5, who=A, whom=B, content=null)]
4
평소에는 ArrayList가 더 잘 쓰이지만
삭제같은 경우엔 ArrayList보다는 LinkedList가 더 빨라서 많이 쓰임
public class Ex1 {
public static void main(String[] args) {
//자바의 모든 프로그램은 쓰레드를 통해 실행
//기본적으로 싱글쓰레드 쓰레드1개 = main
//쓰레드는 객체가 아님
//Thread.currentThread().getName() 현재 실행되고있는 쓰레드의 이름을 출력
//기본은 main Thread 이기때문에 루프A, B 둘 다 똑같이 출력된다
//자바에서는 쓰레드를 코드를 통해 새로 생성가능함
//Multi-Thread
//매번 CPU의 상황이 달라져서 실행순서가 달라진다 -> 막 섞여서 실행됨
//멀티쓰레드는 실행의 순서를 예측할 수 없다
//근데 거의 다 main이 먼저 실행됨
new Thread(() -> {
for (int i = 0; i < 10; i++) {
System.out.print(Thread.currentThread().getName() + "A" + i + " ");
}
}).start();
for (int i = 0; i < 10; i++) {
System.out.print(Thread.currentThread().getName() + "B" + i + " ");
}
}
}
public class Horse {
private String name; //말이름
private int pos; //말위치
//모든 말의 포지션은 0부터 시작 이름만 다름
public Horse(String name) {
this.name = name;
this.pos = 0;
}
public void gallop() {
for (int i = 0; i < 100; i++) {
int range = (int)(Math.random()*10) + 1;
this.pos += range; //말의 위치가 경력
System.out.println(this.name + ": " + this.pos);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//Thread.sleep - Thread를 쉰다 = CPU를 차지하는 것을 쉬겠다는 의미
}//end for
}
}
여러개의 thread가 서로 CPU를 점유하려고 경쟁하는 것
Thread.sleep 실행되는 동안 해당 thread는 CPU점유를 쉼
-> 다른 thread가 CPU점유
-> 서로 CPU를 점유하려고 경쟁하는 것 race condition
public class Horse extends Thread{
private String name; //말이름
private int pos; //말위치
//모든 말의 포지션은 0부터 시작 이름만 다름
public Horse(String name) {
this.name = name;
this.pos = 0;
}
@Override
public void run() {
this.gallop();
}
public void gallop() {
for (int i = 0; i < 10; i++) {
int range = (int)(Math.random()*10) + 1;
this.pos += range; //말의 위치가 경력
System.out.println(this.name + ": " + this.pos);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//Thread.sleep - Thread를 쉰다 = CPU를 차지하는 것을 쉬겠다는 의미
}//end for
}
public static void main(String[] args) {
Horse h1 = new Horse("가");
Horse h2 = new Horse("나");
Horse h3 = new Horse("다");
Horse h4 = new Horse("라");
h1.start();
h2.start();
h3.start();
h4.start();
}
}
for 문이 돌면서 1에서 10 사이의 랜덤숫자 하나가 나오고 그 숫자가 pos에 누적되어 말들의 위치를 나타내는 경마프로그램
각 말을 나타내는 Horse에 Thread.start()를 통해 시작하게 하고 thread.sleep(100)을 통해 0.1초의 휴식을 주어 그 시간동안 다른 쓰레드가 시작하도록 함
나: 4
라: 3
다: 7
가: 5
나: 14
가: 11
다: 12
라: 9
나: 20
다: 22
가: 20
라: 15
나: 30
다: 23
...
public void gallop() {
for (int i = 0; i < 10; i++) {
int range = (int)(Math.random()*10) + 1;
this.pos += range; //말의 위치가 경력
int count = this.pos/10;
for(int j = 0; j < count; j++) {
System.out.print("*");
}
System.out.println(this.name + ": " + this.pos);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}//end for
}
누적된 pos의 수에 /10 을 해서 그 숫자만큼 *이 찍히도록함
하지만 원하는 대로 출력이 안되고 출력되는 중간에 다른 쓰레드가 시작하여 쓰레드끼리 간섭현상이 일어나는 것을 볼 수 있음
*라: 15
가: 9
*나: 6
다: 15
****라: 19
*나: 13
다: 25
가: 18
*********나: 23
다: 35
라: 25
가: 24
***********나: 29
가: 25
다: 43
라: 31
*********라: 38
나: 36
*****가: 34
다: 53
*****************라: 45
...
하나의 thread 작업이 채 끝나기 전에 다른 thread가 실행되어 서로 간섭이 일어나 원하는 작업을 다 끝내지 못하게됨
public void gallop() {
for (int i = 0; i < 10; i++) {
int range = (int)(Math.random()*10) + 1;
this.pos += range; //말의 위치가 경력
//syschronize를 통한 동기화로 쓰레드사이 간섭이 일어나는 것을 방지
//synchronized 블럭으로 특정 코드를 임계영역으로 설정할 수 있음
synchronized(System.out) {
int count = this.pos/10;
for(int j = 0; j < count; j++) {
System.out.print("*");
}
System.out.println(this.name + ": " + this.pos);
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}//end for
}
가: 4
다: 4
라: 9
나: 1
*라: 15
가: 8
나: 3
*다: 14
가: 9
**라: 22
*다: 16
나: 5
**라: 27
*가: 17
**다: 25
*나: 10
***다: 34
*나: 17
***라: 36
**가: 20
**나: 21
**가: 27
...
synchronize를 사용하게되면 쓰레드 하나가 진행되는 동안 다른 쓰레드의 접근을 막아 원하는 작업이 완전하게 끝나지 못하는 현상을 방지할 수 있음
동기화는 특정 메소드에도 설정할 수 있지만 이렇게 특정 코드를 임계영역으로도 선언 가능함
public class OneToOneServer {
//bad code
public static void main(String[] args) throws Exception{
ServerSocket server = new ServerSocket(9999);
System.out.println("Ready...");
Socket client = server.accept();
DataInputStream din = new DataInputStream(client.getInputStream());
while(true) {
String clientMsg = din.readUTF();
System.out.println(clientMsg);
}
}
}
public class OneToOneClient {
//bad code
public static void main(String[] args) throws Exception{
Scanner keyScanner = new Scanner(System.in);
Socket socket = new Socket("192.168.50.4", 9999);
DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
while(true) {
System.out.println("INSERT YOUR MESSAGE");
String msg = keyScanner.nextLine();
dos.writeUTF(msg);
}
}
}
public class OneToOneServer {
//bad code
public static void main(String[] args) throws Exception{
ServerSocket server = new ServerSocket(9999);
System.out.println("Ready...");
Socket client = server.accept();
DataInputStream din = new DataInputStream(client.getInputStream());
DataOutputStream dos = new DataOutputStream(client.getOutputStream());
while(true) {
String clientMsg = din.readUTF();
System.out.println(clientMsg);
for (int i = 0; i < 5; i++) {
Thread.sleep(1000);
dos.writeUTF(clientMsg);
}//end for
}//end while
}
}
public class OneToOneClient {
//bad code
public static void main(String[] args) throws Exception{
Scanner keyScanner = new Scanner(System.in);
Socket socket = new Socket("192.168.50.4", 9999);
DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
DataInputStream din = new DataInputStream(socket.getInputStream());
new Thread(() -> {
while(true) {
String serverMsg;
try {
serverMsg = din.readUTF();
System.out.println("SERVER: " + serverMsg);
} catch (IOException e) {
e.printStackTrace();
}
}//end while
}).start();
//원래는 unreachable code -> Thread를 두개로 분기해서 같이 돌리면 동시에 돌릴수있음
while(true) {
System.out.println("INSERT YOUR MESSAGE");
String msg = keyScanner.nextLine();
dos.writeUTF(msg);
}//end while
}
}
원래 while(true) 문을 두개를 돌리면 아래에 있는 코드는 unreachable code로 도달할 수 없는 코드라 에러가 발생하지만 이렇게 new Thread를 이용해 thread를 분리시키면 동시에 thread가 진행될 수 있기때문에 에러가 발생하지않음
그래서 하나 진행되는 동안에 다른 문장 입력해도 다른 코드와 엉켜서 꼬이거나 하지않고 각각 잘 싱행되는 모습을 볼 수 있음
public class OneToOneServer {
//bad code
public static void main(String[] args) throws Exception{
Scanner keyScanner = new Scanner(System.in);
ServerSocket server = new ServerSocket(9999);
System.out.println("Ready...");
Socket client = server.accept();
DataInputStream din = new DataInputStream(client.getInputStream());
DataOutputStream dos = new DataOutputStream(client.getOutputStream());
new Thread(() -> {
while(true) {
String serverMsg = keyScanner.nextLine();
try {
dos.writeUTF(serverMsg);
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
while(true) {
String clientMsg = din.readUTF();
System.out.println(clientMsg);
}//end while
}
}
최종적으로 Server와 Client 양쪽 모두에 In, Out Loop 를 개별 Thread로 돌아가도록 구성해서 메세지를 주고받음에 끊김이 없도록 구성함
받기를 하는 동안에도 보내기가 가능하고 보내는 동안에도 받는 것이 가능해짐
멀티쓰레드에 관한 내용이 확실하게 이해되었습니다. 감사합니다!