스레드
wait()와 notify()로 스레드 이해하기
→ 이 방법을 권장하고 있다.
→ resume()은 suspend()는 내부에 동기화가 되어있지 않아 교착상태가 발생함.
→ wait()와 notify()는 순서대로 작업하게 되어있다.
wait()와 notify()를 이용한 프로그램
public class ThreadWaitTest {
public static void main(String[] args) throws InterruptedException {
// wait()와 notify()
// 고객 2명 스레드, 요리사 스레드 2개를 만들 예정.
// 고객 2명이 하는 일 - 음식먹기 , 요리사 하는 일 - 한번에 최대 6개 음식 만들기
// wait()는 따로 관리되는 wait pool이 있음, 그 곳에 들어가면 일시정지
// wait 되어지는 스레드가 notify를 해줌. (고객이 음식을 다먹어서 먹을 음식이 없으면 요리사에게 음식이 없다고 알림)
// table 클래스를 만들어 그 위에 음식 6개를 올려둘 수 있게 함.
Table table = new Table();
new Thread(new Cook(table), "COOK1").start(); // 0.001초 단위로 음식을 만든다
new Thread(new Customer(table, "donut"), "CUST1").start(); // 0.01초 단위로 음식을 먹는다
new Thread(new Customer(table, "burger"), "CUST2").start();
Thread.sleep(100); // 0.1초 뒤에
System.exit(0); // 프로그램 강제 종료
}
}
import java.util.ArrayList;
public class Table {
String[] dishNames = { "donut", "dount", "burger" };
final int MAX_FOOD = 6;
private ArrayList<String> dishes = new ArrayList<String>();
// 음식을 만드는 기능
public void add(String dish) {
if (dishes.size() >= MAX_FOOD) {
return;
}
dishes.add(dish);
System.out.println("Dishes : " + dishes.toString());
}
// true는 먹을 음식이 있다, false는 먹을 음식이 없다는 뜻
public boolean remove(String dishName) {
for (int i = 0; i < dishes.size(); i++) {
if (dishName.equals(dishes.get(i))) {
dishes.remove(i);
return true;
}
}
return false;
}
public int dishNum() {
return dishNames.length; // 배열의 크기
}
}
public class Cook implements Runnable{
// 요리사, 랜덤으로 음식을 만든다
private Table table;
public Cook(Table table) {
super();
this.table = table;
}
@Override
public void run() {
while(true) {
int idx = (int)(Math.random() * table.dishNum());
table.add(table.dishNames[idx]);
try {
Thread.sleep(1);
} catch (InterruptedException e) {
}
}
}
}
public class Customer implements Runnable {
private Table table;
private String food;
public Customer(Table table, String food) {
super();
this.table = table;
this.food = food;
}
@Override
public void run() {
while (true) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
}
String name = Thread.currentThread().getName();
if (eatFood()) {
System.out.println(name + " ate a " + food);
} else {
System.out.println(name + " failed to " + food);
}
}
}
boolean eatFood() {
return table.remove(food);
}
}
결과)
Exception in thread "COOK1" java.util.ConcurrentModificationException
at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1013)
at java.base/java.util.ArrayList$Itr.next(ArrayList.java:967)
at java.base/java.util.AbstractCollection.toString(AbstractCollection.java:456)
at Table.add(Table.java:17)
at Cook.run(Cook.java:16)
at java.base/java.lang.Thread.run(Thread.java:840)
= 오류가 난 이유 : 동시에 작업이 일어났기 때문이다
Exception이 발생, 이는 요리사(Cook) 쓰레드가 테이블에 음식을 놓는 도중에, 손님(Customer) 쓰레드가 음식을 가져가려했기 때문에 발생하는 예외
이런 예외가 발생하는 이유는 여러 쓰레드가 테이블(자원)을 공유하는데 동기화를 하지 않았기 때문이다.
또한 스레드마다 화면에 출력되는 순서도 뒤죽박죽이다. 이렇게 자원을 공유하는 경우 반드시 동기화가 필요하다.
이 밖에도 고객1과 고객2가 동시에 먹으려 하면 오류가 생긴다.
= 없는 위치에서 데이터를 삭제하려했다
= indexoutofboundsexception 오류
private ArrayList<String> dishes = new ArrayList<String>();
이 부분을 동시에 사용하려 하기 때문에 발생하는 오류.
실제 ArrayList를 쓰는 것은 add, remove
때문에 한번에 하나의 스레드만 데이터를 처리할 수 있도록 lock을 걸어야한다.
→ 해결방법 : synchronized로 동기화 처리를 한다.
요리사가 6개의 음식을 만들면 일시정지 해야 함.
wait()와 notify()를 통해 일시정지 시킨다.
반드시 동기화 치리 되어진 메소드 안에서만 사용할 수 있다.
synchronized 때문에 생긴 기아 현상
wait() 실행되면 락이 풀린다.
모든 문제를 해결한 코드)
public class ThreadWaitTest {
public static void main(String[] args) throws InterruptedException {
// wait()와 notify()
// 고객 2명 스레드, 요리사 스레드 2개를 만들 예정.
// 고객 2명이 하는 일 - 음식먹기 , 요리사 하는 일 - 한번에 최대 6개 음식 만들기
// wait()는 따로 관리되는 wait pool이 있음, 그 곳에 들어가면 일시정지
// wait 되어지는 스레드가 notify를 해줌. (고객이 음식을 다먹어서 먹을 음식이 없으면 요리사에게 음식이 없다고 알림)
// table 클래스를 만들어 그 위에 음식 6개를 올려둘 수 있게 함.
Table table = new Table();
new Thread(new Cook(table), "COOK1").start(); // 0.001초 단위로 음식을 만든다
new Thread(new Customer(table, "donut"), "CUST1").start(); // 0.01초 단위로 음식을 먹는다
new Thread(new Customer(table, "burger"), "CUST2").start();
Thread.sleep(2000); // 0.1초 -> 5초 -> 2초로 수정
System.exit(0); // 프로그램 강제 종료
}
}
import java.util.ArrayList;
public class Table {
String[] dishNames = { "donut", "dount", "burger" };
final int MAX_FOOD = 6;
private ArrayList<String> dishes = new ArrayList<String>();
// 음식을 만드는 기능
public synchronized void add(String dish) { // 해당 메소드를 동기화 처리
// 요리사가 6개의 요리를 다 만들고 나면 할 일이 없어짐
// 스레드의 일시 정지 -> wait() 사용
// 음식이 모자라면 다시 일 할 수 있도록 하기
while (dishes.size() >= MAX_FOOD) {
String name = Thread.currentThread().getName();
System.out.println(name + "is wating.");
try {
wait(); // 일시정지, COOK 스레드를 기다리게 한다.
Thread.sleep(500); // 0.5초 동안 sleep
} catch (InterruptedException e) {
}
}
dishes.add(dish);
notify(); // 기다리고 있는 CUST를 깨우기 위함.
System.out.println("Dishes : " + dishes.toString());
// if (dishes.size() >= MAX_FOOD) {
// return;
// }
//
// dishes.add(dish);
// System.out.println("Dishes : " + dishes.toString());
}
// true는 먹을 음식이 있다, false는 먹을 음식이 없다는 뜻
public void remove(String dishName) {
String name = Thread.currentThread().getName();
synchronized (this) { // 객체 자체를 동기화 처리, 범위가 넓다
// 누가 기다리고 있는지 확인
while (dishes.size() == 0) {
System.out.println(name + " is waiting.");
try {
wait();
Thread.sleep(500); // 0.5초 기다리고 있다.
} catch (InterruptedException e) {
}
}
while (true) {
for (int i = 0; i < dishes.size(); i++) {
if (dishName.equals(dishes.get(i))) {
dishes.remove(i);
notify(); // 잠자고 있는 COOK을 꺠우기 위함.
return;
}
}
try {
System.out.println(name + " is waiting.");
wait();
Thread.sleep(500); // 0.5초 기다리고 있다.
} catch (InterruptedException e) {
}
}
}
// return false;
}
public int dishNum() {
return dishNames.length; // 배열의 크기
}
}
public class Cook implements Runnable{
// 요리사, 랜덤으로 음식을 만든다
private Table table;
public Cook(Table table) {
super();
this.table = table;
}
@Override
public void run() {
while(true) {
int idx = (int)(Math.random() * table.dishNum());
table.add(table.dishNames[idx]);
try {
Thread.sleep(10); // 0.001초 -> 0.1초 -> 0.001초
} catch (InterruptedException e) {
}
}
}
}
public class Customer implements Runnable {
private Table table;
private String food;
public Customer(Table table, String food) {
super();
this.table = table;
this.food = food;
}
@Override
public void run() {
while (true) {
try {
Thread.sleep(100); // 고객이 먹는 속도 : 0.1초
} catch (InterruptedException e) {
}
String name = Thread.currentThread().getName();
table.remove(food);
System.out.println(name + " ate a " + food);
// if (eatFood()) {
// System.out.println(name + " ate a " + food);
// } else {
// System.out.println(name + " failed to " + food);
// }
}
}
// boolean eatFood() {
// return table.remove(food);
// }
}
ㅍ
결과)
(예시)
Dishes : [burger]
Dishes : [burger, burger]
Dishes : [burger, burger, donut]
Dishes : [burger, burger, donut, dount]
Dishes : [burger, burger, donut, dount, burger]
Dishes : [burger, burger, donut, dount, burger, dount]
COOK1 is wating.
CUST1 ate a donut
Dishes : [burger, burger, dount, burger, dount, dount]
CUST1 is waiting.
CUST2 ate a burger
CUST1 is waiting.
CUST2 ate a burger
Dishes : [dount, burger, dount, dount, dount]
CUST1 is waiting.
CUST2 ate a burger
Dishes : [dount, dount, dount, dount, dount]
람다식
= 이름없는 메소드라고 표현
함수형 인터페이스를 사용해야 사용 가능.
표현방식)
public class LambdaTest {
// 람다식(Lambda expression)
// 메소드를 하나의 식으로 표현한 것
// 이름 없는 메소드
// 람다는 하나의 객체다
// 기존의 식
int max(int x, int y) {
return x > y ? x : y;
}
// 람다식
(int x, int y) -> {return x > y ? x : y;}
(int x, int y) -> x > y ? x : y
(x, y) -> x > y ? x : y // 타입이 없어도 상관없다
(x) -> x * x;
x -> x * x; // 매개변수가 하나인 경우 괄호 생략 가능
}
함수형 인터페이스 → 람다식에 이름을 부여하는 역할.
public class LambdaTest {
// 람다식(Lambda expression)
// 메소드를 하나의 식으로 표현한 것
// 이름 없는 메소드
// 람다는 하나의 객체다
// 기존의 식
// int max(int x, int y) {
// return x > y ? x : y;
// }
// 람다식
// (int x, int y) -> {return x > y ? x : y;}
//
// (int x, int y) -> x > y ? x : y
//
// (x, y) -> x > y ? x : y // 타입이 없어도 상관없다
//
// (x) -> x * x;
//
// x -> x * x; // 매개변수가 하나인 경우 괄호 생략 가능
//
//(x, y) -> x > y ? x : y
// 함수형 인터페이스
// 오버라이딩 목적이 아니다
// 람다식에 이름을 부여하는 역할
// 반드시 하나의 추상메소드만 갖는다
@FunctionalInterface
interface MyFuntion {
// 추상메소드(람다식의 형식에 맞게 구성한다.)
// 추상 메소드의 선언부를 여기에 정의한다
public abstract int max(int x, int y);
}
MyFuntion f = (x, y) -> x > y ? x : y; // 객체
// 하나의 객체로 사용할 수 있다. 메소드 자체를 하나의 값으로 사용
f.max(10, 20) // 림디식의 호출방식
}
FunctionalInterface → 추상메서드가 딱 하나만 존재하는 인터페이스
익명클래스 : 인터페이스를 이용해서 객체를 생성하는 방식
public class LambdaTest2 {
static void execute(MyFunction f) {
f.run();
}
static MyFunction getMyFunction() {
MyFunction f = () -> System.out.println("f3.run()");
return f;
}
public static void main(String[] args) {
MyFunction f1 = () -> System.out.println("f1.run()");
// 익명클래스 : 인터페이스를 이용해서 객체를 생성하는 방식
MyFunction f2 = new MyFunction() { // 익명 클래스
// 부모타입으로 참조변수 선언
// 람다식과 비슷하다
@Override
public void run() {
System.out.println("f2.run()");
}
};
MyFunction f3 = getMyFunction();
f1.run(); // f1.run();
f2.run(); // f2.run();
f3.run(); // f3.run();
// 람다식의 주소값을 넘긴다
execute(f1); // f1.run();
execute(() -> System.out.println("run()")); // run()
}
}
@FunctionalInterface // 함수형 인터페이스
public interface MyFunction {
void run();
}
메소드를 하나의 데이터처럼 사용할 수 있다.
iterator 방식과 향상된 for문, 람다식을 이용한 출력
import java.security.AlgorithmConstraints;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import javax.lang.model.element.NestingKind;
import javax.security.auth.x500.X500Principal;
public class LambdaTest3 {
public static void main(String[] args) {
// TODO Auto-generated method stub
ArrayList<Integer> list = new ArrayList<Integer>();
for (int i = 0; i < 10; i++) {
list.add(i);
}
// iterator 방식
Iterator<Integer> iterList = list.iterator();
while (iterList.hasNext()) {
System.out.print(iterList.next()); // 0123456789
}
System.out.println();
// 향상된 for문
for (Integer i : list) {
System.out.print(i); // 0123456789
}
System.out.println();
// 함수형 인터페이스 = 람다식을 받겠다
// 함수형 인터페이스에는 추상메서드가 정의되어있다
// 람다식을 받은 후에는 accept 로 받아서 쓴다
list.forEach(i -> System.out.print(i)); // 0123456789
// removeIf 조건에 맞는 값을 삭제하겠다
// 2의 배수 또는 3의 배수를 삭제
list.removeIf(x -> x % 2 == 0 || x % 3 == 0);
System.out.println(list); // [1, 5, 7]
// replaceAll 값을 찾아서 바꿔준다
list.replaceAll(i -> i * 10);
System.out.println(list); // [10, 50, 70]
// Map
Map<String, String> map = new HashMap<String, String>();
map.put("1", "1");
map.put("2", "2");
map.put("3", "3");
map.put("4", "4");
// iterator 방식
Iterator<Map.Entry<String, String>> entries = map.entrySet().iterator();
while (entries.hasNext()) {
Map.Entry<String, String> entry = entries.next();
System.out.println("key : " + entry.getKey() + ", value :" + entry.getValue());
}
System.out.println();
// 향상된 for문
for (Map.Entry<String, String> entry : map.entrySet()) {
System.out.println("key : " + entry.getKey() + ", value :" + entry.getValue());
}
}
}
Map를 이용해 값을 불러오는 것도 잘 나왔다.
key : 1, value :1
key : 2, value :2
key : 3, value :3
key : 4, value :4
'2024_UIUX 국비 TIL' 카테고리의 다른 글
UIUX_국비과정 0530 [🍀 스트림] (0) | 2024.06.24 |
---|---|
UIUX_국비과정 0529 [람다와 스트림] (0) | 2024.06.24 |
UIUX _국비과정 0527 [스레드] (1) | 2024.06.24 |
UIUX _국비과정 0524 [JAVA _ Generic, 스레드의 개념] (0) | 2024.06.12 |
UIUX _국비과정 0523 [Map, 배열 정렬] (0) | 2024.06.11 |