🦁멋쟁이사자처럼 백엔드 부트캠프 13기 🦁
TIL 회고 - [18]일차
🚀18일차에는 스레드에 대해 배우면서 단일스레드, 멀티스레드, 스레드의 다양한 메소드 등을 실습해보았다.
이번 실습에서는 println() 출력문이 중요한 것 같았다.
스레드의 수행 순서 및 수행 작업들을 눈으로 확인할 수 있어야했는데 직관적으로 구분할 수 있게 도와주었다.
가장 큰 깨달음은 채팅프로그램을 만들기 위해서 A사용자가 입력할 때 B사용자는 무작정 기다리고만 있으면 안된다.
동시에 채팅을 입력할 수 있어야하는데 이럴때 멀티스레딩을 하는 것이었다.
스레드를 통해 멀티버스와 같이 여러 개의 수행흐름을 만들 수 있다는 것또한 배울 수 있었다.
Thread 스레드
- 프로세스 내에서 실제로 작업을 수행하는 최소단위를 의미
- 스레드는 직접구현할 필요없이 잘 만들어진 API를 사용
public static void main(String[] args) {
System.out.println("스레드 WAIT 테스트");
Scanner sc = new Scanner(System.in);
String threadMsg = sc.next();
// 수행 흐름 -> 하나 (단일 스레드)
// 입력이 끝날때까지 기다린다.
System.out.println("입력 메시지 : " + threadMsg);
}
- 먼저 스레드의 흐름을 직관적으로 이해하기 위해
Scanner클래스로 사용자의 입력을 받을때까지 기다리는 경우를 실습해보았다. - 입력을 받을때까지 기다리고 입력이 끝나면 종료되는 것을 알 수 있다.
- 이처럼 수행흐름이 하나 인것을 "단일 스레드"라 한다.
- 단일 스레드일 경우에는 무전기처럼 상대방의 입력을 기다리고 있어야한다.
프로세스
- 실행 중인 프로그램의 인스턴스
- 프로세스는 독립적인 메모리 공간을 가짐 (코드/데이터/힙/스택)
- 다른 프로세스와는 격리됨 ➡️프로세스간 안전성 보장
- 프로세스는 각자 공간을 관리하기때문에 리소스 관리에 있어서 비효율적일 수 있음
멀티스레드
- 하나의 프로세스 내에서 여러 개의 스레드를 “동시에 실행” 하여 프로세스의 리소스를 공유
➡️멀티스레드에서 스택은 독립된 공간을 가지며 힙 메모리와 코드영역은 공유 - 멀티스레딩은 프로그램의 여러작업을 병렬적으로 처리하여 효율성, 응답성 향상에 도움
- 데이터 공유로 인한 동시성 문제 발생가능 ➡️동기화로 해결
❓프로세스와 멀티스레드의 차이점
1. 메모리 공유 :
- 멀티스레드는 여러스레드가 같은 메모리공간(힙)을 공유
- 프로세스는 완전히 독립적인 메모리 공간을 가짐
2. 통신 :
- 멀티스레드는 같은 프로세스 메모리를 공유하기때문에 "프로세스 보다" 스레드 간의 통신이 간단
3. 오버헤드 :
- 멀티스레드는 생성과 관리 측면에서 오버헤드가 적음
- 프로세스는 더 많은 시스템 리소스를 필요로하여 오버헤드가 많음
4. 안정성 :
- 멀티스레드 환경에서 한 스레드 오류가 전체 프로세스에 영향을 줄 수 있음
- 프로세스는 독립적인 프로세스를 가짐
- 스레드는 Runnable 인터페이스를 implements하여 구현하고 있는 것을 알 수 있다.
▶️실습 - Thread 사용 3가지 방법
▶️1. Thread클래스를 상속받는 방법
- Thread 클래스를 직접 상속받아 run()메소드를 오버라이딩
- 이 클래스 자체가 Thread가 된다.
// Thread를 상속받아 자체적으로 스레드 생성
public class MyThread extends Thread{
// 이 스레드의 main메소드 역할을 함
@Override
public void run() {
// 이 스레드가 할 일을 구현
System.out.println("스레드 시작");
int i = 0;
while(i++ < 10){
System.out.println("스레드 중... " + i);
}
System.out.println("스레드 종료");
}
}
public class MyThreadMain {
public static void main(String[] args) {
System.out.println("MyThread의 Main 시작");
// MyThread 생성
// 1번방법 - 일반적 방법
MyThread myThread = new MyThread();
// myThread.run(); // (X) run을 직접호출하지 않는다.
myThread.start(); // (O) 실제 수행흐름을 하나 만들어내는 일을하고, 준비가 되면 run()을 호출
int i = 0;
while(i++ < 10){
System.out.println("Main의 구현... " + i);
}
System.out.println("MyThread의 Main 종료");
}
}
- myThread.start()로 인해 main의 수행흐름에서 가지를 쳐서 새롭게 수행흐름을 만들어냄 (수행흐름이 2개)
- 두 개의 스레드가 동시에 실행되어 각자의 작업을 수행
- main의 MyThread호출 과 콘솔출력, MyThread의 start()로 인한 run()호출로 콘솔출력
- 이 순서는 실행때마다 매번 달라진다. ➡️💡스레드에 우선순위를 부여가능
▶️2. Runnable 인터페이스 구현 방법
- run()메소드 만을 오버라이딩 그 후 Runnable객체를 Thread객체에 전달하여 스레드 생성
- 이 클래스 자체가 Thread는 아니다. ➡️혼자서 동작할 수 없다.
- Runnable객체를 Thread객체에 전달하여 스레드를 생성, 실행하는 해야함
- 정리하자면 스레드를 상속받지 못할때 대안으로 제공되는 인터페이스
❓Runnable 인터페이스 방법이 등장한 이유
➡️extends Thread를 통한 “다중상속” 이 이루어지지 않을때 Runnable 인터페이스를 구현하는 방법으로 해결가능
public class MyRunnable implements Runnable{
@Override
public void run() {
// 이 스레드가 할 일을 구현
System.out.println("Runnable 스레드 시작");
int i = 0;
while(i++ < 10){
System.out.println("Runnable 스레드 중... " + i);
}
System.out.println("Runnable 스레드 종료");
}
}
// ThreadMain에 추가
// 2번 방법 - Runnable 인터페이스 구현 (implements) 후 Thread 객체에 전달
Runnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
thread.start();
// 이렇게 선언할 수도 있다.
Thread thread2 = new Thread(new MyRunnable());
- 서로 경쟁적으로 실행이 되고 있음을 확인
sleep
- 예외를 발생시키는 메소드이므로, try-catch구문으로 감싸주어야함
- 옵션 : mills( 밀리세컨드 )
= 1000으로 지정하면 1초
= 100으로 지정하면 0.1초 - 스레드에 지연시간을 걸어줄 수 있음
while(i++ < 5){
System.out.println("Runnable 스레드 중... " + i);
try{
Thread.sleep(1000);
}catch (InterruptedException e){
throw new RuntimeException(e);
}
}
- Thread.sleep(1000); 처럼 직접 접근 사용 (try-catch문 사용)
- 스레드들은 start()해도 스레드가 바로 수행되지않고, Runnable (실행가능한 상태)의
스레드 풀(Thread pool)에 기다리고 있다가 run()을 통해서 실제로 Running이 일어날때 수행 - Running이 sleep()이 되면 잠깐 Blocked으로 쉼
- 다시 notify() 를 만나기 전까지 기다리다가 실행가능한 상태인 Runnable로 돌아감
- ⚠️stop()으로 Dead 시키는 것을 권장하지 않음
- 💡yield() : 양보하는 메소드이다. > sleep()이나 stop()처럼 무조건 기다리진 않는다.
▶️3. 이름을 가진 Runnable 인터페이스 구현 방법
- Runnable이 여러 개 일 가능성을 고려하여 이름을 지정하는 실습
String name;
public NameRunnable(String name) {
this.name = name;
}
@Override
public void run() {
// 이 스레드가 할 일을 구현
System.out.println("[" + name + "] Runnable 스레드 시작");
int i = 0;
while(i++ < 5){
try{
Thread.sleep(1000);
}catch (InterruptedException e){
throw new RuntimeException(e);
}
}
System.out.println("[" + name + "] Runnable 스레드 종료");
}
// 이름을 가진 Runnable 인터페이스 사용
Thread nameThread = new Thread(new NameRunnable("Isak"));
nameThread.start();
- 필드로 String name을 만들어주고, String name에 대한 기본 생성자를 만들어서 접근 가능
멀티스레드 제어 필요성
- 다양한 작업을 동시에 수행할 수 있지만 여러 스레드 간의 경쟁에 따른 복잡한 문제 발생 가능
> 효과적으로 제어해야할 필요성 - 데이터의 일관성과 안정성 유지
- 여러 스레드가 동일 데이터에 접근,변경할 경우 데이터의 일관성과 안정성 손상
- 따라서 데이터의 동시 접근제어 및 동기화를 해야함
- 동시 접근 제어 : Thread클래스의 join() 메소드 활용
- 동기화 : synchronized 키워드를 메소드에 붙여 사용 - 데드락 방지
- 여러 스레드가 서로 소유한 리소스의 해제를 기다리면서 무한정 대기하는 상태
- 스레드 간 리소스 할당과 해제를 적절히 관리하여 데드락을 방지해야함 - 스레드 생명주기 관리
스레드 생성, 실행, 대기, 종료 등의 생명 주기를 적절히 관리 → 중지 및 재활용 - 성능최적화및 부하 관리
스레드를 너무 많이 생성하면 “컨텍스트 스위칭”으로 인한 오버헤드 증가 가능성 ➡️스레드 풀 관리 필요
❓스레드 풀 관리 Thread pool
➡️스레드가 하나의 일만 딱 한번 수행되고 소멸되면 비효율적이므로, 스레드 풀은 스레드를 재활용
작업을 마친 스레드는 바로 소멸시키지 않고 스레드 풀이 잠시 저장한 후 필요 시 꺼내서 사용
❓컨텍스트 스위칭 Context Switching
➡️컨텍스트는 “스레드의 상태”를 의미
컨텍스트 스위칭은 실행중이던 스레드가 다른 스레드로 교체되는 것을 의미
컨텍스트 스위칭은 “여러 스레드들을 동시에 실행시키거나, 공정한 CPU시간을 갖거나,
높은 우선순위의 스레드가 빠르게 처리”될 수 있도록 도와줌
▶️실습 - 멀티스레드 제어되기 전
// 멀티스레드 제어의 예제
public class ControlTread {
public static void main(String[] args) {
System.out.println("main - 시작");
System.out.println("연산을 수행해야합니다. -> calThread 스레드 생성");
Thread calThread = new CalThread();
calThread.start();
System.out.println("main - 계산과 동시에 할 일 진행중...");
System.out.println("main - calThread에서 계산을 끝냈습니다. 결과 : [?]");
}
}
// 계산해주는 스레드
class CalThread extends Thread {
@Override
public void run() {
System.out.println("계산 스레드 - 시작");
// 계산에 대한 자체적 시간을 부여
try {
System.out.println("계산 스레드 - 작동중...");
sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("계산 스레드 - 종료");
}
}
- 계산 스레드 - 종료 후에 main에서 계산을 끝냈다는 문구가 발생해야하는데 이미 계산을 끝냈다고 출력
- join()메소드 사용 : calThread가 일을 끝날때까지 기다리게 해주는 기능
▶️실습 - 멀티스레드 제어와 join() 메소드
try{
calThread.join(); // calThread가 계산이 끝날때까지 기다리게해줌
}catch(InterruptedException e){
throw new RuntimeException(e);
}
- 이렇게 calThread 생성과 “계산을 끝냈다”는 출력문 사이에 try-catch문으로 calThread.join()메소드를 수행
- 계산 스레드가 모두 종료 후에 main의 계산을끝냈다는 문구가 출력 확인
- main메소드가 join()메소드 이전에 for문을 통해 일하는 중임을 확인 가능
Daemon Thread 데몬 스레드
- 이 데몬스레드는 백그라운드 작업을 한다. 백그라운드 작업을 할 것들은 데몬 스레드로 생성
- 따라서 백그라운드에서 불필요한 데이터들을 처리해주는 "가비지 컬렉터"도 “데몬 스레드”에 해당
- 모든 작업이 종료되면 같이 종료되어야함
▶️실습 - 데몬 스레드와 멀티스레드 제어
// 일반 작업의 스레드
class TaskThread extends Thread{
// ... 일반 작업 메소드 ...
}
// 백그라운드에서 진행하는 작업의 스레드
class DemonThread extends Thread{
@Override
public void run() {
while(true) {
System.out.println("배경음악 재생 중...");
try {
sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
// ... Main
// 스레드들 생성
TaskThread task1 = new TaskThread("Isak의 작업");
TaskThread task2 = new TaskThread("Bowen의 작업");
TaskThread task3 = new TaskThread("Lewis의 작업");
DemonThread demonThread = new DemonThread();
// 스레드들 start()
task1.start();
task2.start();
task3.start();
demonThread.start();
System.out.println("모든 작업을 완료했습니다.");
- 이처럼 작업이 종료됨에도 배경음악이 계속 재생되고있다.
➡️문제 해결을 위해 데몬 스레드가 "자신이 데몬스레드임을 인지" 시켜야함
// 스레드들 start()
task1.start();
task2.start();
task3.start();
demonThread.setDaemon(true);
demonThread.start();
- setDaemon(true); : 기본값은 false이고, true로 바꿔주면 데몬스레드로 동작
동기화
- Synchronization
- 메소드나 블록을 “동기화”하여 한 시점에 하나의 스레드만 “해당 코드 영역에 접근할 수 있도록”함
- 공유 자원에 대한 동시 접근을 방지하여 데이터 일관성,안정성 보장
▶️실습 - 동기화 사용 이전
// 메소드가 수행하는 영역 파트
for(int i = 0; i < 5; i++){
System.out.println("동기화되지 않은 [메소드 A] 수행... " + ((i+1) * 20));
try{
Thread.sleep(500);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
// ... methodB, methodC...
// Thread 클래스 파트
@Override
public void run() {
// Rodriguez이면 methodA() 수행
}
// Main 메소드 파트
// 공유객체 robot -> main에서 플레이어들에게 인자로 공유객체 robot을 전달
Robot robot = new Robot();
// 메인에서는 각각 다른 플레이어 이름을 가지는 스레드를 여러개 생성
RobotPlayer playerA = new RobotPlayer("Rodriguez", robot);
RobotPlayer playerB = new RobotPlayer("Jake", robot);
RobotPlayer playerC = new RobotPlayer("Raul", robot);
playerA.start();
playerB.start();
playerC.start();
- 동기화되지 않았기때문에 동시에 이처럼 메소드를 수행하면 우선순위도 없고 오류가 발생할 가능성이 높다.
▶️실습 - 동기화 사용 이후
public synchronized void methodA(){
for(int i = 0; i < 5; i++){
System.out.println("동기화 [메소드 A] 수행... " + ((i+1) * 20));
try{
Thread.sleep(500);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
- 동기화 키워드 synchronized 를 사용한 메소드들은 기다리면서 수행하게 된다.
여기서 메소드들의 순서는 중요하지않다 ➡️ 또한 모든 기능들(메소드들)을 동기화시킬 필요는 없다.
🚀실습 - 동기화 사용 (커피숍)
- 친구 둘이 대화하면서 커피를 마시는 것을 구현
class Task {
static final int PERCENTAGE = 25;
static final int REPEAT_COUNT = 4;
public synchronized void talkA(){
for(int i = 0; i < REPEAT_COUNT; i++){
System.out.println("[Ben]이 말하는 중..." + ((i+1) * PERCENTAGE) + "%");
try{
Thread.sleep(500);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
// ... talkB(), drinkCoffee(), CoffeeShop클래스 파트 ...
Task task = new Task(); // 공유객체
// 스레드 생성
CoffeeShop friendA = new CoffeeShop("Ben", task);
CoffeeShop friendB = new CoffeeShop("Toni", task);
CoffeeShop drinking = new CoffeeShop("Coffee", task);
friendA.start();
friendB.start();
drinking.start();
- 공유객체를 가지는 talkA(), talkB()라는 메소드는 동기화되어있으며 “말하는 중…” 을 출력
- talkA()는 Ben이 수행하고, talkB()는 Toni가 수행하는데
실행할때마다 누가 먼저 말할지의 순서는 중요하지 않다. ➡️스레드는 자체적으로 순서를 보장하지 않음 - 동기화되었기때문에 한 명이 말하면 한 명은 기다린다.
- 커피를 마시는 것은 동기화되지 않았음으로 누군가 말할때 기다리지 않고 수행
스레드 생명주기 제어
- Object 클래스에 정의된 메소드로 스레드의 수행순서를 지정해줄 수 있다.
- 스레드 간 통신을 제어하는데 사용
- wait() : 스레드를 대기 상태로 만듦
- notify() : 대기중인 스레드를 깨움
- notifyAll() : 대기중인 모든 스레드를 깨움
🚀실습 - 택배 배송 : wait(), notify() 사용
private static final Object lock = new Object();
private static boolean deliverAvailable = false;
private static final int PERCENTAGE = 25;
private static final int REPEAT = 4;
// 배송업체 스레드 - 내부클래스 (static)
static class Delivery extends Thread{
@Override
public void run() {
synchronized (lock){ // lock의 소유권을 가짐
// 택배업체가 하는 일들 ...
deliverAvailable = true; // 배송준비 완료
lock.notify(); // 포장이 끝나면 소비자에게 배송출발을 알림
for(int i = 0; i < REPEAT; i++){
System.out.println("[택배업체] : 배송 중... " + ((i+1) * PERCENTAGE) + "%");
} // 배송완료
}
}
}
// 소비자 스레드 - 내부클래스 (static)
static class Consumer extends Thread{
@Override
public void run() {
synchronized (lock){
while(!deliverAvailable){
try{
System.out.println("[소비자] : <택배 주문 완료>\\n");
lock.wait(); // 택배 배송을 기다림
}catch(InterruptedException e){
e.printStackTrace();
}
} // 택배 받은 후
deliverAvailable = false; // 택배 받은 후 상태 업데이트
}
}
}
public static void main(String[] args) {
Delivery delivery = new Delivery();
Consumer consumer = new Consumer();
consumer.start(); // 소비자 스레드 시작
try{
Thread.sleep(500); // 택배업체 스레드 시작 전 딜레이 지정 0.5초
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
delivery.start(); // 택배업체 스레드 시작
}
- 내부 클래스가 외부 클래스의 멤버를 가져와 사용하는 경우가 아닌 경우에는
반드시 내부 클래스를 선언 시 static 키워드를 붙여주어야 함
🚀 실습 - 택배 배송 : wait(), notifyAll() 사용
private static final Object lock = new Object();
private static int deliverAvailable = 0; // 배송할 아이템 수
private static final int PERCENTAGE = 25;
private static final int REPEAT = 4;
// 배송업체 스레드
static class Delivery extends Thread{
@Override
public void run() {
synchronized (lock){ // lock의 소유권을 가짐
deliverAvailable += 5; // 배송준비 완료
lock.notifyAll(); // 포장이 끝나면 모든 소비자에게 배송출발을 알림
}
}
}
// 소비자 스레드
static class Consumer extends Thread{
// 이름 생성자 파트
@Override
public void run() {
synchronized (lock){
while(deliverAvailable <= 0){ // 배송할 아이템이 없을때
try{
lock.wait(); // 택배 배송을 기다림
}catch(InterruptedException e){
e.printStackTrace();
}
}
deliverAvailable--; // 택배 받은 후 상태 업데이트
}
}
}
- 배송할 아이템 수를 static 변수로 받고, 배송업체 스레드가 실행되면서 배송개수를 늘린다.
- notifyAll() : lock이 걸려있던 것을 풀고 모든 소비자에게 상품이 준비되어서 배송출발한다는 알림을 보냄
Delivery delivery = new Delivery();
Consumer consumer1 = new Consumer("Isak");
Consumer consumer2 = new Consumer("Baines");
Consumer consumer3 = new Consumer("Fekir");
consumer1.start(); // 소비자 스레드 시작
consumer2.start(); // 소비자 스레드 시작
consumer3.start(); // 소비자 스레드 시작
try{
Thread.sleep(500);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
delivery.start(); // 택배업체 스레드 시작
🚀실습 - 모험가 스킬 사용 : notify(), wait(), random() 사용
private static final List<Integer> skillSet = new ArrayList<>(); // 공유 자원
private static final int MAX_SKILL_COUNT = 5; // 스킬셋의 스킬 보유 최대 크기
private static final Object lock = new Object(); // 동기화를 위한 lock 객체
private static final Random random = new Random(); // 랜덤 객체 생성
private static int USE_SKILL_COUNT = 0; // 스킬을 사용한 횟수 제한
private static boolean flag = false;
// 스킬 셋 스레드
static class Skill extends Thread {
@Override
public void run() {
int value = 0;
while (true) {
synchronized (lock) {
while (skillSet.size() >= MAX_SKILL_COUNT) {
try {
System.out.println("{스킬셋} : 스킬 쿨타임... (모든 스킬이 준비되었습니다!)\\n");
lock.wait(); // 리스트가 가득 찬 경우 대기
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("{스킬셋} : 스킬 추가 [" + value + "]");
skillSet.add(value++); // 데이터 추가
lock.notify(); // 모험가에게 알림
}
try {
Thread.sleep(100); // 스킬 준비 속도 조절
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
- 스킬셋에 5개의 스킬을 채우는데, 모험가가 스킬을 사용하지 않은 상태에서 꽉 채워지면 "모든 스킬 준비" 출력
- lock.wait() : 이 경우에는 wait() 메소드를 통해 대기를 시킴
- skillSet.add(value++) : 일반적인 스킬셋은 스킬을 추가한다.
리스트의 add()메소드로 추가하게되며, 기다리고있던 모험가에게 notify()로 알림
// 모험가 스레드
static class Player extends Thread {
@Override
public void run() {
while (USE_SKILL_COUNT <= 5) {
synchronized (lock) {
while (skillSet.isEmpty()) {
try {
System.out.println("[모험가] 대기 중... (사용할 스킬이 없습니다!)");
lock.wait(); // 리스트가 비어 있는 경우 대기
} catch (InterruptedException e) {
e.printStackTrace();
}
}
int value = skillSet.remove(0); // 데이터 제거
System.out.println("[모험가] 스킬 사용 횟수 : " + value);
USE_SKILL_COUNT++; // 모험가의 스킬사용횟수를 갱신
lock.notify(); // 스킬시스템에 알림
}
try {
int delay = random.nextInt(1001); // 스킬 사용 속도 0~1000 사이의 랜덤 값 생성
Thread.sleep(delay);
System.out.println("[모험가] : [" + delay + "ms] 동안 대기 후 다시 실행\\n");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
- 모험가의 스킬 사용 횟수가 일정 횟수에 도달하면 모험가 접속 종료와 게임 종료를 하도록 구현
- 모험가의 스킬 사용이 스킬셋에 남아있는 스킬보다 많을 경우 "사용할 스킬 없다" 출력
- lock.wait() : 그 후 wait()로 스킬셋에 스킬이 채워질 수 있도록 스레드가 대기상태로 돌입
- skillSet.remove() : 일반적인 모험가는 스킬셋의 스킬을 사용했으므로 remove(0)메소드를 통해 첫번째 스킬을 제거
- USER_SKILL_COUNT++ : 그 후 스킬 사용 횟수를 갱신하고, 스킬시스템에 notify()로 알리게됨
- delay : 전체적으로 이러한 일을 할때 랜덤한 딜레이 시간으로 대기할 수 있도록 함
스레드 상태 확인 및 제어
- interrupt() : 스레드를 중단시키는 요청, 스레드에 인터럽트 플래그 설정
- isInterrupted(), interrupted() : 스레드가 인터럽트 되었는지 여부 확인
- 스레드를 안전하게 종료시킬 수 있도록 도와줌
▶️실습 - 스레드 종료 : Interrupt()
@Override
public void run() {
try{ // ... 구현부 ... }
catch (InterruptedException e) {
System.out.println("스레드가 중단되었습니다.");
return; // 스레드를 안전하게 종료함
}
}
// Main 파트
MyThread myThread = new MyThread();
myThread.start();
try{
Thread.sleep(5000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
myThread.interrupt(); // 스레드에게 인터럽트 신호를 보내서 안전하게 종료하도록 함
- stop() 메소드는 사용하지 않는 것을 권장 ➡️deprecated 된 메소드 (불안정한 메소드)
Thread클래스의 기타 메소드
- join() : 한 스레드가 다른 스레드의 종료를 기다리게함
- setPriority(), getPriority() : 스레드의 우선순위를 설정하거나 가져옴
getPriority() : 기본으로 5의 값을 가짐
Thread와 관련된 패키지
- 스레드의 생성, 관리, 실행, 동기화, 통신 등을 효율적으로 수행할 수 있도록 도와줌
- 가장 중요한 패키지로 java.util.concurrent가 있음
💡스레드에 대한 개념은 얼추 알고 있었지만 실제로 어떻게 사용해야하는지 어떤 코드에서 쓰이는지
어떻게 스레드가 작동하는지 확인해야할지 감이 안잡혔었다. 오늘 회고를 통해서 스레드, 멀티스레드, 생명주기 관리, 스레드 제어 부분을 실습해보니 점점 익숙해지는 것이 느껴졌다.
스레드를 활용해서 커피숍, 택배 배송과 같은 실습을 해보면서 그 흐름을 잘 이해하고자 노력하였다.🚀
'Recording > 멋쟁이사자처럼 BE 13기' 카테고리의 다른 글
[멋쟁이사자처럼 부트캠프 TIL 회고] BE 13기_20일차_''디자인패턴" (2) | 2024.12.30 |
---|---|
[멋쟁이사자처럼 부트캠프 TIL 회고] BE 13기_19일차_''객체지향원칙 OOP" (3) | 2024.12.27 |
[멋쟁이사자처럼 부트캠프 TIL 회고] BE 13기_17일차_''Java IO" (1) | 2024.12.24 |
[멋쟁이사자처럼 부트캠프 TIL 회고] BE 13기_16일차_''컬렉션 프레임워크" (0) | 2024.12.23 |
[멋쟁이사자처럼 부트캠프 TIL 회고] BE 13기_15일차_''JDBC와 DAO/DTO" (2) | 2024.12.20 |