쓰레드
프로세스 내에서 실제 작업을 수행. 모든 프로세스는 최소한 하나의 쓰레드를 가지고 있다.
멀티 태스킹(멀티 프로세싱) : 동시에 여러 프로세스를 실행
멀티 쓰레딩 : 하나의 프로세스 내에 동시에 여러 쓰레드를 실행
-프로세스를 생성하는 것보다 쓰레드를 생성하는 비용이 적다.
-같은 프로세스 내의 쓰레드들은 서로 자원을 공유한다.
멀티쓰레드 장단점
장점
-시스템 자원을 보다 효율적으로 사용할 수 있다.
-사용자에 대한 응답성(responseness)이 향상된다.
-작업이 분리되어 코드가 간결해 진다.
단점
-동기화(synchronization)에 주의해야 한다.
-교착상태(dead-lock)가 발생하지 않도록 주의해야 한다.
-각 쓰레드가 효율적으로 고르게 실행될 수 있게 해야 한다.
구현
//상속
class MyThread extends Thread {
public void run() { // Thread 클래스의 run()을 오버라이딩
/* 작업내용 */
}
}
MyThread t1 = new MyThread();
t1.start();
//Runnable 인터페이스를 구현
class MyThread2 implements Runnable {
public void run() { // Runnable 인터페이스의 추상메서드 run()을 구현
/* 작업내용 */
}
}
Runnable r = new MyThread2();
Thread t2 = new Thread(r);
t2.start();
싱글쓰레드 vs 멀티쓰레드

병행과 병렬

blocking

쓰레드 우선순위
-작업의 중요도에 따라 쓰레드의 우선순위를 다르게 하여 특정 쓰레드가 더 많은 작업시간을 갖게 할 수 있다.
void setPriortiy(int newPriority) // 쓰레드의 우선순위를 지정한 값으로 변경
int getPriority() // 쓰레드의 우선순위를 반환
쓰레드 그룹(Thread Group)
-서로 관련된 쓰레드를 그룹으로 묶어서 다루기 위한 것(보안상의 이유)
-모든 쓰레드는 반드시 하나의 쓰레드 그룹에 포함되어 있어야 한다.
-쓰레드 그룹을 지정하지 않고 생성한 쓰레드는 'main쓰레드 그룹'에 속한다
-자신을 생성한 쓰레드(부모 쓰레드)의 그룹 우선순위를 상속받는다.
데몬 쓰레드(Daemon Thread)
-일반 쓰레드(non-daemon thread)의 작업을 돕는 보조적인 역할을 수행
-일반 쓰레드가 모두 종료되면 자동적으로 종료된다.
-가비지 컬렉터, 자동저장, 화면자동갱신 등에 사용된다.
-무한루프와 조건문을 이용해서 실행 후 대기하다가 특정조건이 만족되면 작업을 수행하고 다시 대기하도록 작성
boolean isDaemon() // 데몬쓰레드 판단. 데몬 쓰레드면 true 반환
void setDaemon(boolean on) // 쓰레드를 데몬 쓰레드 또는 사용자 쓰레드로 변경. on을 true로 지정하면 데몬 쓰레드가 됨
*setDaemon(boolean on)은 반드시 start()를 호출하기 전에 실행되야 한다. 아니면 오류 발생
쓰레드 실행제어 메서드
sleep()
-현재 쓰레드를 지정된 시간동안 멈추게 한다.
-예외처리를 해야 한다.(InterruptedException이 발생하면 깨어남)
-특정 쓰레드를 지정해서 멈추게 하는 것은 불가능
interrupt()
-대기상태(WAITING)인 쓰레드를 실행대기(RUNNABLE) 상태로 만든다.
yield()
-남은 시간을 다음 쓰레드에게 양보하고, 자신(현재 쓰레드)은 실행대기한다.
-yield()와 interrupt()를 적절히 사용하면, 응답성과 효율을 높일 수 있다.
join()
-지정된 시간동안 특정 쓰레드가 작업하는 것을 기다린다.
-예외처리를 해야 한다.(InterruptedException이 발생하면 작업 재개)
쓰레드 동기화
synchronized
-한 번에 하나의 쓰레드만 객체에 접근할 수 있도록 객체에 락(lock)을 걸어서 데이터의 일관성을 유지하는 것
// 메서드에 lock
public synchronized void withdraw(int money){
if(balance >= money) {
try {
Thread.sleep(1000);
} catch(Exception e) {}
balance -= money;
}
}
//특정한 객체에 lock
public void withdraw(int money) {
synchronized(this) {
if(balance >= money) {
try {
Thread.sleep(1000);
} catch(Exception e) {}
balance -= money;
}
}
}
-동기화의 효율을 높이기 위해 wait(), notify()를 사용
-Object클래스에 정의되어 있으며, 동기화 블록 내에서만 사용 가능
wait()
객체의 lock을 풀고 쓰레드를 해당 객체의 waiting pool에 넣는다
notify()
waiting pool에서 대기중인 쓰레드 중 하나를 깨운다
notifyAll()
waiting pool에서 대기중인 모든 쓰레드를 깨운다
람다(Lambda)
람다식(Lambda Expression)
-함수(메서드)를 간단한 '식(Expression)' 으로 표현하는 방법
// 함수(메서드)
int max(int a, int b) {
return a > b ? a : b;
}
// 식 표현
(a, b) -> a > b ? a : b
*함수와 메서드의 차이는 근본적으로 동일하나 함수는 일반적인 용어이고 메서드는 객체지향개념 용어이다. 함수는 클래스에 독립적이나 메서드는 클래스에 종속적이다.
-메서드의 이름과 반환타입을 제거하고 '->'를 블록{} 앞에 추가한다.
-반환값이 있는 경우, 식이나 값만 적고 return문 생략 가능(끝에 ';' 안 붙임)
-매개변수의 타입이 추론 가능하면 생략가능(대부분의 경우 생략가능)
-매개변수가 하나인 경우, 괄호() 생략가능(타입이 없을 경우)
-블록 안의 문장이 하나뿐 일때, 괄호{} 생략가능(끝에 ';'안 붙임), 단 하나뿐인 문장이 return문이면 괄호{} 생략불가
함수형 인터페이스
람다식은 익명 객체
//람다식 -> 익명 객체
new Object() {
int max(int a, int b) {
return a > b ? a : b;
}
}
//람다식(익명 객체)을 다루기 위한 참조변수가 필요
Object obj = new Object() {
int max(int a, int b) {
return a > b ? a : b;
}
}
함수형 인터페이스 : 단 하나의 추상 메서드만 선언된 인터페이스
함수형 인터페이스 타입의 참조변수로 람다식을 참조할 수 있음(단, 함수형 인터페이스의 메서드와 람다식의 매개변수 개수와 반환타입이 일치해야 함)
MyFunction f = new MyFunction() {
public int max(int a, int b) {
return a > b ? a : b;
}
};
MyFunction f = (a, b) -> a > b ? a : b;
int value = f.max(3, 5); // 실제로는 람다식(익명 함수)이 호출됨
java.util.function 패키지
Supplier<T> { T get() } : 매개변수는 없고, 반환값만 있음
Consumer<T> { void accept(T t) } : 매개변수만 있고, 반환값이 없음
Function<T, R> { R apply(T t) } : 일반적인 함수. 하나의 매개변수를 받아서 결과를 반환
Predicate<T> { boolen test(T t) } : 조건식을 표현하는데 사용됨. 매개변수는 하나. 변환타입은 boolean
Supplier<Integer> f = () -> (int)(Math.random()*100) + 1;
Consumer<Integer> f = i -> System.out.print(i+", ");
Predicate<Integer> f = i -> i%2==0;
Function<Integer, Integer> f = i -> i/10*10;
스트림(Stream)
다양한 데이터 소스를 표준화된 방법으로 다루기 위한 것
List<Integer> list = Arrays.asList(1,2,3,4,5);
Stream<Integer> intStream = list.stream(); //컬렉션
Stream<String> strStream = stream.of(new String[]{"a", "b", "c"}); //배열
Stream<Integer> evenStream = Stream.iterate(0, n->n+2); //0,2,4,6, ...
Stream<Double> randomStream = Stream.generate(Math::random); //람다식
IntStream intStream = new Random().ints(5); //난수 스트림(크기가 5)
중간 연산 - 연산결과가 스트림인 연산. 반복적으로 적용가능
최종 연산 - 연산결과가 스트림이 아닌 연산. 스트림의 요소를 소모하므로 한번만 적용가능
stream.distinct().limit(5).sorted().forEach(System.out::println)
// 중간 연산 중간 연산 중간 연산 최종 연산
String[] strArr = { "dd", "aaa", "CC", "cc", "b" };
Stream<String> stream = stream.of(strArr); //문자열 배열이 소스인 스트림
Stream<String> fileteredStream = stream.filter(); //걸러내기(중간 연산)
Stream<String> distinctedStream = stream.distinct(); //중복제거(중간 연산)
Stream<String> sortedStream = stream.sort(); //정렬(중간 연산)
Stream<String> limitedStream = stream.limit(5); //스트림 자르기(중간 연산)
int total = stream.count(); //요소 개수 세기(최종 연산)
특징
스트림은 데이터 소스로부터 데이터를 읽기만 할 뿐 변경하지 않는다.
List<Integer> list = Arrays.asList(3,1,5,4,2);
List<Integer> sortedList = list.stream().sorted() // list를 정렬해서
.collect(Collectors.toList()); // 새로운 List에 저장
System.out.println(list); // [3, 1, 5, 4, 2]
System.out.println(sortedList); // [1, 2, 3, 4, 5]
스트림은 Iterator처럼 일회용이다.(필요하면 다시 스트림을 생성해야 함)
strStream.forEach(System.out::println); //모든 요소를 화면에 출력(최종연산)
int numOfStr = strStream.count(); //에러. 스트림이 이미 닫혔음
최종 연산 전까지 중간연산이 수행되지 않는다 - 지연된 연산
IntStream intStream = new Random().ints(1,46); //1~45범위의 무한 스트림
intStream.distinct().limit(6).sorted() //중간 연산
.forEach(i->System.out.print(i+",")); //최종 연산
스트림은 작업을 내부 반복으로 처리한다.
for(String str : strList)
System.out.println(str); // -> stream.forEach(System.out::println);
'Language > Java' 카테고리의 다른 글
| Java의 정석 정리 3 (0) | 2023.09.15 |
|---|---|
| Java의 정석 정리 2 (0) | 2023.09.10 |
| Java의 정석 정리 (0) | 2023.09.07 |
| 강의 내용 정리 2 (0) | 2023.03.08 |
| 강의 내용 정리 1 (0) | 2023.03.04 |