내일배움캠프 6일차 TIL _ 2주 5일차
2024. 4. 26. 14:18ㆍTIL Java
- 오늘 있었던 일
- 스파르타 자바를 이용한 공부
- 쓰레드, 람다, 스트림, Optional
쓰래드
더보기



프로세스와 쓰레드
- 프로세스
- OS로 부터 자원을 할당받는 작업 단위
- 작업중인 프로그렘을 말한다. 모든 프로그렘은 OS가 만들어준 프로세스에서 실행된다.
- 카톡, 크롬,Java프로그렘 모두 프로세스로 실행됨
- 프로그램 2개 = 프로세스 2개
- 프로세스 구조
- OS에서 자원을 할당 할때 Code와 Data 그리고 메모리 영역(Stack, Heap) 을 같이준다.
- Code
- 자바의 메서드같은 코드를 의미함
- Date
- 프로그램 실행중 저장할 수 있는 공간을 의미
- 전역변수, 정적 변수(static), 배열 등 초기화된 데이터를 저장하는 공간
- Mamory
- Stack : 지역변수, 매개변수 리턴 변수를 저장하는 공간
- Heap : 프로그램이 동적으로 필요한 변수를 저장하는 공간 (new(), mallock())
- Code
- 쓰레드
- 프로세스가 할당받은 자원을 이용하는 실행의 단위로
- 프로세스 내에서 일하는 일꾼이라 생각하면 편하다
- 쓰레드의 생성
- 프로세스가 실행 요청이 들어오면 쓰레드(일꾼)을 만들어 명령처리함
- 프로세스 안에 여러 쓰레드(일꾼)이 있고 실행을 위한 공간이 있다.
- Stack : 개인 Mamory 공간
- Heap : 공용 Mamory 공간쓰레드의 자원
- Java 쓰레드
- Java 프로그램을 실행하면 앞서 배운 JVM 프로세스 위에서 실행됩니다.
- Java 프로그램 쓰레드는 Java Main 쓰레드부터 실행되며 JVM에 의해 실행됩니다.
- 일단 쓰레드랑 동일하다, 그져 JVM 프로세스 안에서 실행되는 쓰레드를 말함
- 쓰레드의 생성
멀티 쓰레드
- 멀티 쓰레드
- Java는 main 메서드를 실행 하면서 시작 된다 = main 쓰레드를 시작시킨다.
- 즉, Java는 멀티 쓰레드를 지원한다.
- 싱글 쓰레드
- Java 프로그렘 에서는 main 메서드를 실행 한 것을 싱글 쓰레드라함
- 다른 쓰래드 없이 main 메서드 = 메인 쓰레드만 만들었기에 모두 싱글 쓰레드로 실행됨
- JVM의 메인 쓰레드가 종료되면, JVM도 같이 종료됩니다.
- 멀티 쓰레드
- Java 프로그램은 메인 쓰레드 말고도 여러 개의 쓰래드를 만들 수 있습니다.
- 멀티 쓰래드는 병렬처리 된다
- 장점
- 여러 쓰레드를 이용해 동시에 작업을 해서 성능을 향상시킬 수 있음
- 자원을 효율적으로 사용이 가능함
- 응답과 작업 쓰래드를 분리해 빠른 응답이 가능함(비동기화)
- 단점
- 동기화 문제 발생 (충돌)
- 교착상태(데드락) 발생
- 서로 상대방이 필요한 자원을 가진체로 기다리는 상태로 무한 반복됨
Thread 와 Runnable
- 자바에서 쓰래드(Thread) 구현
public class TestRunnable implements Runnable {
@Override
public void run() {
// 쓰레드 수행작업
}
}
...
Runnable run = new TestRunnable();
Thread thread = new Thread(run); // 쓰레드 생성
thread.start(); // 쓰레드 실행
- 여기서 run() 메서드가 쓰래드를 실행할 작업이다
- thread.start();로 실행 시킨다.
- Runnable 인터페이스로 구현
public class TestRunnable implements Runnable {
@Override
public void run() {
// 쓰레드 수행작업
}
}
...
Runnable run = new TestRunnable();
Thread thread = new Thread(run); // 쓰레드 생성
thread.start(); // 쓰레드 실행
- 위에 쓰레드처럼 Runnable 도 run() 메서드가 쓰래드를 실행시킨다.
- Runnable 왜 이걸 쓸까?
- 클래스와 인터페이스 차이, Java의 클레스는 다중상속을 지원하지 않지만 인터페이스는 한다.
- 확장성을 늘리기 위해 인터페이스인 Runnable 을 쓴다.
람다식
public class Main {
public static void main(String[] args) {
Runnable task = () -> {
int sum = 0;
for (int i = 0; i < 50; i++) {
sum += i;
System.out.println(sum);
}
System.out.println(Thread.currentThread().getName() + " 최종 합 : " + sum);
};
Thread thread1 = new Thread(task);
thread1.setName("thread1");
Thread thread2 = new Thread(task);
thread2.setName("thread2");
thread1.start();
thread2.start();
}
}
- 인터페이스 람다명= () -> { 실행코드} 로 작동한다.
- Thread 쓰레드명= new Thread( 람다명 ); 쓰래드 생성
- 쓰레드명 .start (" 쓰레드명 "); 쓰래드 작동
데몬 쓰레드
public class Main {
public static void main(String[] args) {
Runnable demon = () -> { };
Thread thread = new Thread(demon);
thread.setDaemon(true); // true로 설정시 데몬스레드로 실행됨
thread.start();
}
}
- 데몬 쓰레드
- 보이지 않는 곳(background) 에서 실행되는 낮은 우선순위를 가진 쓰레드를 말합니다.
- 대표적인 데몬 쓰레드로는 메모리 영역을 정리해 주는 가비지 컬렉터가 있다.
- 데몬 쓰레드는 우선순위가 낮고 다른 쓰레드 들이 먼저 모두 종료되면 실행 도중이여도 종료된다.
- 데몬 쓰레드의 반대로는 사용자 쓰레드가 있는데 이는 기본적으로 사용하는 쓰레드를 말한다.
쓰레드 우선순위와 쓰레드 그룹
- 쓰레드는 생성될 때 우선순위가 정해집니다.
- 우선순위는 JVM이 정한 대로 (최대 10 /보통 5 / 최소 1 )의 우선순위로 나눈다.
- 쓰레드명.setPriority(우선순위) 메서드로 설정할 수 있다.
- 쓰레드명.getPriority() 로 확인도 할 수 있다.
- 우선순위는 JVM이 정한 대로 (최대 10 /보통 5 / 최소 1 )의 우선순위로 나눈다.
- 쓰레드 구룹
- 쓰레드들을 그룹으로 묶어서 다룰 수 있다.
- 기본적으로 System 하위인 main에 포함되어 되어있다.
- 그룹 생성
// ThreadGroup 클래스로 객체를 만듭니다.
ThreadGroup group1 = new ThreadGroup("Group1");
// Thread 객체 생성시 첫번째 매개변수로 넣어줍니다.
// Thread(ThreadGroup group, Runnable target, String name)
Thread thread1 = new Thread(group1, task, "Thread 1");
// Thread에 ThreadGroup 이 할당된것을 확인할 수 있습니다.
System.out.println("Group of thread1 : " + thread1.getThreadGroup().getName());
- 쓰레드 그룹으로 묶어서 쓰레드 관리
// ThreadGroup 클래스로 객체를 만듭니다.
ThreadGroup group1 = new ThreadGroup("Group1");
// Thread 객체 생성시 첫번째 매개변수로 넣어줍니다.
// Thread(ThreadGroup group, Runnable target, String name)
Thread thread1 = new Thread(group1, task, "Thread 1");
Thread thread2 = new Thread(group1, task, "Thread 2");
// interrupt()는 일시정지 상태인 쓰레드를 실행대기 상태로 만듭니다.
group1.interrupt();
쓰레드 상태와 제어
- 쓰레드의 상태

- 쓰레드는 실행과 대기를 반복하며 특정 매서드를 수행한다.
- 특정 메서드가 종료시 같이 종료하게 된다
- ex) 가족들(로직)이 고기집(종료)로 가는데(실행) 택시(쓰레드)를 따로(멀티) 타거나 하나(싱글)로 타는것

- 음악을 듣다 일시정지를 하는 것과 마찬가지로 쓰레드도 일시정지 상태를 만들 수 있습니다.
- 쓰레드가 다시 실행 상태로 넘어가기 위해서는 우선 일시정지 상태에서 실행 대기 상태로 넘어가야 합니다.
쓰레드의 상태를 정리
- 객체생성 ( NEW )
- 쓰레드 객체 생성, 아직 start() 메서드 호출 전의 상태
- 실행대기 ( RUNNABLE)
- 실행 상태로 언제든지 갈 수 있는 상태
- 일시정지 ( WAITING )
- 다른 쓰레드가 통지(notify) 할 때까지 기다리는 상태
- 일시정지 ( TIMED_WAITING )
- 주어진 시간 동안 기다리는 상태
- 일시정지 ( BLOCKED )
- 사용하고자 하는 객체의 Lock이 풀릴 때까지 기다리는 상태
- 종료 ( TERMINATED )
- 쓰레드의 작업이 종료된 상태
쓰레드 제어

sleep()
try {
Thread.sleep(2000); // 2초
} catch (InterruptedException e) {
e.printStackTrace();
}
- Thread.sleep(ms); ms(밀리초) 단위로 설정됩니다.
- 예외 처리를 해야 합니다.
- sleep 상태에 있는 동안 interrupt()를 만나면 다시 실행되기 때문에 InterruptedException이 발생할 수 있습니다.
- 임의의 쓰레드를 멈추게 한다 = 특정 쓰레드를 멈추게 하는 것은 불가능하다.
interrupt()
public class Main {
public static void main(String[] args) {
Runnable task = () -> {
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("task : " + Thread.currentThread().getName());
};
Thread thread = new Thread(task, "Thread");
thread.start();
thread.interrupt();
System.out.println("thread.isInterrupted() = " + thread.isInterrupted());
}
}
- Thread 클래스 내부에 interrupted 되었는지를 체크하는 boolean 변수가 존재한다.
- 쓰레드가 start() 된 후 동작하다 interrupt()를 만나 실행하면 interrupted 상태가 true가 됩니다
- isInterrupted() 메서드를 사용하여 상태 값을 확인할 수 있습니다.
- sleep() 실행 중 interrupt()가 실행되면 예외가 발생합니다.
public class Main {
public static void main(String[] args) {
Runnable task = () -> {
while (!Thread.currentThread().isInterrupted()) {//오류 방지
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName());
} catch (InterruptedException e) {
break;
}
}
};
}
}
- 해당 코드의 [// 오류 방지 ]가 있는 조건으로 interrupted 상태를 체크해서 처리하면 오류를 방지할 수 있습니다.
join()
Thread thread = new Thread(task, "thread");
thread.start();
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
- 정해진 시간 동안 지정한 쓰레드가 작업하는 것을 기다린다.
- 시간을 지정하지 않았을 때는 지정한 쓰레드의 작업이 끝날 때까지 모두 종료하지 않고 기다린다.
- sleep처럼 일시정지 이기 때문에 IntteruptedException 예외처리가 필요하다.
yield()
Runnable task = () -> {
try {
for (int i = 0; i < 10; i++) {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName());
}
} catch (InterruptedException e) {
Thread.yield();
}
};
thread1.interrupt();
- 남은 시간을 다음 쓰레드에게 양보하고 쓰레드 자신은 실행 대기 상태가 된다..
- 실행중인 쓰레드.interrupt(); 으로 InterruptedException이 발생하면서 Thread.yield(); 이 실행되 실행되던 쓰레드는 실행 대기 상태로 변경되면서 남은 시간은 다른 쓰레드에게 자원이 양보된다.
synchronized()
- 멀티 쓰레드는 한 프로세스의 자원을 공유해서 작업하기에 자원문제로 장애나 버그가 발생할 수 있다..
- 이러한 일을 방지하기 위해서 다른 쓰레드가 침범을 막는 것을 동기화(Synchronization)라고 한다.
- 동기화를 침범을 막아야 할 구역을 ‘임계 영역’으로 설정하면 된다.
- 임계 영역에는 Lock을 가진 단 하나의 쓰레드만 출입이 가능합니다.
- 즉, 임계 영역은 한 번에 한 쓰레드만 사용이 가능합니다.
- 실행할 메서드 또는 실행할 코드 묶음 앞에 synchronized를 붙여서 임계 영역을 지정하여 다른 쓰레드의 침범을 막을 수 있습니다. (침범을 막다. = Lock을 걸다.)
- 임계 영역 지정 하는법
//임계 영역 지정
public synchronized void asyncSum() {
...침범을 막아야하는 코드...
}
synchronized(해당 객체의 참조변수) {
...침범을 막아야하는 코드...
}
사용 예시 코드 ( 사과 먹기 )
public class Main {
public static void main(String[] args) {
AppleStore appleStore = new AppleStore();
Runnable task = () -> {
while (appleStore.getStoredApple() > 0) {// 사과가 0개 이상일때 반복하는데
appleStore.eatApple();/synchronized가 없을시 남은 사과의 수가 -1,-2까지 간다
System.out.println("남은 사과의 수 = " + appleStore.getStoredApple());
}
};
for (int i = 0; i < 3; i++) {
new Thread(task).start();// 3개의 쓰래드가 나온다.
}
}
}
class AppleStore {
private int storedApple = 10;
public int getStoredApple() {
return storedApple;
}
public void eatApple() {
synchronized (this) {//synchronized 문을 넣는 것으로 사과를 먹는데
if(storedApple > 0) {// -1,-2까지 가지않고 0에서 끝난다
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
storedApple -= 1;
}
}
}
}
wait()
- 실행 중이던 쓰레드를 대기실(waiting pool) 에서 notify()의 통지를 기다리게 하는 메서드다.
- 다만 조건이 만족되서 쓰레드가 wait()에 들어가고, notify()를 할 쓰레드도 wait()되어 버려서 교착 상태로 쓰레드들이 락이 걸려 아무것도 안하는 것을 주의해야한다
notify()
- 해당 객체의 대기실(waiting pool)에 있는 모든 쓰레드 중에서 임의의 쓰레드만 통지를 한다.
- 임의의 쓰레드만 통지를한다 = 특정 쓰레드를 깨울수는 없다
Lock
- synchronized에서 자동으로 Lock이 걸고 풀지만, 동일 메서드 내에서만 가능하기에 ReentrantLock을 이용함
- ReentrantLock
- 재진입 가능 Lock , 조건으로 풀고 나중에 다시 얻어 임계 영역으로 진입이 가능하다.
-
public class MyClass { private Object lock1 = new Object(); private Object lock2 = new Object(); public void methodA() { synchronized (lock1) { methodB(); } } public void methodB() { synchronized (lock2) { // do something methodA(); } } }
- ReentrantReadWriteLock
- 읽기를 위한 Lock과 쓰기를 위한 Lock을 따로 제공합니다
- 읽기에는 공유적이고, 쓰기에는 베타적인 Lock입니다
- 읽기 Lock이 걸려있으면 다른 쓰레드들도 읽기 Lock을 중복으로 걸고 읽기를 수행할 수 있습니다. (read-only)
- 읽기 Lock이 걸려있는 상태에서 쓰기 Lock을 거는 것은 허용되지 않습니다. (데이터 변경 방지)
- StampedLock
- ReentrantReadWriteLock에 낙관적인 기능을 추가했습니다.
- 낙관적인 Lock
- 데이터를 변경하기 전에 락을 걸지 않는 것을 말합니다. 낙관적인 락은 데이터 변경을 할 때 충돌이 일어날 가능성이 적은 상황에서 사용합니다.
- 쓰기 작업이 빈번하지 않은 경우, 낙관적인 락을 사용하면 읽기와 쓰기 작업 모두가 빠르게 처리됩니다
- 낙관적인 읽기 Lock은 쓰기 Lock에 의해 바로 해제 가능합니다.
- 무조건 읽기 Lock을 걸지 않고, 쓰기와 읽기가 충돌할 때만 쓰기 후 읽기 Lock을 겁니다.
- ReentrantLock
Condition
- wait() & notify()의 문제점을 해결해둔 것으로 특정 쓰레드를 깨울수 있다.
- ReentrantLock와 함께 사용됨에 따라 Condition을 사용하면 wait()과 notify()의 문제점을 보환 한다.
- await() = wait() & signal() = notify() 으로 쓰인다.
public class Main {
public static final int MAX_TASK = 5;
private ReentrantLock lock = new ReentrantLock();
// lock으로 condition 생성
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private ArrayList<String> tasks = new ArrayList<>();
// 작업 메서드
public void addMethod(String task) {
lock.lock(); // 임계영역 시작
try {
while(tasks.size() >= MAX_TASK) {
String name = Thread.currentThread().getName();
System.out.println(name+" is waiting.");
try {
condition1.await(); // wait(); condition1 쓰레드를 기다리게 합니다.
Thread.sleep(500);
} catch(InterruptedException e) {}
}
tasks.add(task);
condition2.signal(); // notify(); 기다리고 있는 condition2를 깨워줍니다.
System.out.println("Tasks:" + tasks.toString());
} finally {
lock.unlock(); // 임계영역 끝
}
}
}
- await() 으로 선언된 쓰래드를 메서드가 기다리게 하고 signal() 로 깨워는 형식
- 마지막에 lock.unlock() 으로 임계영역을 끝내서 쓰레드의 일이 완료할 수 있게 한것
모던 자바 알아보기 (람다, 스트림, Optional)
더보기
모던 자바
재미로 읽어보는 ‘병렬처리’
역사가 오래된 분야이자 작업 효율을 높여주어 최근 각광 받고 있는 주제 ‘병렬처리’에 대해 간단하게 소개해드리고자 합니다.
medium.com
람다와 스트림 문법 살펴보기
- 함수형 인터페이스
- 추상 메소드를 딱 하나만 가지고 있는 인터페이스
- @FunctionalInterface 어노테이션으로 검증할 수 있음
- 제네릭을 이용해 interface Predicate<T> 이렇게 만든다
람다식 사용법
- (클레스명 응답명) -> 응답명 .메소드나 조건))
- 함수를 값으로 전달하는데, 어딘가에 구현하지 않고 그냥 간단하게 구현해서 넘기고 싶으면 람다식을 이용하면 됩니다!
- 람다식은 “함수 값”으로 평가되며, 한 번만 사용되지만 간결하게 가능하다
- 람다식 더 알아보기
코딩교육 티씨피스쿨
4차산업혁명, 코딩교육, 소프트웨어교육, 코딩기초, SW코딩, 기초코딩부터 자바 파이썬 등
tcpschool.com
스트림
- 추상화된 자료구조와 자주 사용하는 프로그래밍 API
- 원본의 데이터를 변경하지 않습니다.
- 일회용입니다.
- 스트림 API 더 확인하기
List<Car> benzParkingLot =
// carsWantToPark의 스트림값을 받아와서
carsWantToPark.stream()
// 거기 구현되어 있는 filter()메서드를 사용합니다.
// filter메서드는 함수를 파라미터로 전달받습니다.
// 여기서 함수는 제조사가 벤츠면 true를 반환하는 함수네요.
// 필터 메서드는 이름처럼 false를 반환한 스트림의 원소들을 제거합니다.
.filter((Car car) -> car.getCompany().equals("Benz"))
// 이 결과도 반환을 받아서 다시 리스트로 묶어줍니다.
.toList();
Null은 10억 달러 짜리 실수다? Optional
- Null
- 아무것도 참조하지 않는다는 계념
- null 체크하기
- if (변수 != null) 이렇게 조건문으로 null값을 처리하해주면 좋다.
- 이런것을 발전 시킨 것이 Optional이다.
- null 알아보기
- Optional
-
Optional<String> carName = getCarNameFromDB(); // orElse() 를 통해 값을 받아옵니다, 파라미터로는 null인 경우 반환할 값을 적습니다. String realCarName = carName.orElse("NoCar"); // 위는 예시코드고 실제는 보통 아래와 같이 사용하겠죠? String carName = getCarNameFromDB().orElse("NoCar"); // orElseGet()이라는 메서드를 사용해서 값을 받아올 수 있습니다. // 파라미터로는 없는 경우 실행될 함수를 전달합니다. Car car = getCarNameFromDB().orElseGet(Car::new); // 값이 없으면, 그 아래 로직을 수행하는데 큰 장애가 되는경우 에러를 발생시킬수도 있습니다. Car car = getCarNameFromDB() .orElseThrow(() -> new CarNotFoundException("NO CAR!)
-
당일 회고
- 주말동안 한번 더 복습하자 글로는 정리했지만 아직 머리로는 정리가 부족하다
'TIL Java' 카테고리의 다른 글
내일배움캠프 8일차 WIL _ 2주 7일차 (1) | 2024.04.28 |
---|---|
내일배움캠프 7일차 WIL _ 2주 6일차 (0) | 2024.04.27 |
내일배움캠프 5일차 TIL _ 2주 4일차 (0) | 2024.04.25 |
내일배움캠프 4일차 TIL _ 2주 3일차 (0) | 2024.04.24 |
내일배움캠프 3일차 TIL _ 2주 2일차 (0) | 2024.04.23 |