setPriority() : 쓰레드의 우선순위를 설정하는 메서드
Thread thread1 = new Thread(task1);
thread1.setPriority(8);
getPriority() : 쓰레드의 우선순위를 반환하는 메서드
int threadPriority = thread1.getPriority();
System.out.println("threadPriority = " + threadPriority);
예시) ThreadGroup 클래스로 객체를 만들어서 Thread 객체 생성시 첫번째 매개변수로 넣어 생성
public class Main {
public static void main(String[] args) {
// 람다식으로 Thread 수행
Runnable task = () -> {
// Interrupt가 들어오기 전까지 계속 수행
while (!Thread.currentThread().isInterrupted()) {
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName());
} catch (InterruptedException e) {
break;
}
}
System.out.println(Thread.currentThread().getName() + " Interrupted");
};
// 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");
// Thread에 ThreadGroup 이 할당된것을 확인
System.out.println("Group of thread1 : " + thread1.getThreadGroup().getName());
System.out.println("Group of thread2 : " + thread2.getThreadGroup().getName());
thread1.start();
thread2.start();
try {
// 현재 쓰레드를 지정된 시간(5000ms)동안 멈추게 함
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// interrupt()는 일시정지 상태인 쓰레드를 실행대기 상태로 변경
group1.interrupt();
}
}

run() 메서드를 수행run() 메서드가 종료되면 실행이 멈추게 된다.

쓰레드도 상태가 존재하고 이를 제어를 할 수 있다.

→ 현재 쓰레드를 지정된 시간동안 멈추게 한다.
Thread.sleep(ms); ms(밀리초) 단위로 설정 가능interrupt() 를 만날 경우,InterruptedException이 발생할 수 있다.예시
try {
Thread.sleep(2000); // 2초
} catch (InterruptedException e) {
e.printStackTrace();
}
→ 일시정지 상태인 쓰레드를 실행대기 상태로 만듦
sleep() 실행 중 interrupt()가 실행되면 예외가 발생!Thread.currentThread().isInterrupted() 로 interrupted 상태를 체크해서 처리하면 오류를 방지할 수 있다.예시
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;
}
}
System.out.println("task : " + Thread.currentThread().getName());
};
Thread thread = new Thread(task, "Thread"); // NEW
thread.start(); // NEW → RUNNABLE
thread.interrupt(); // RUNNABLE → 일시정지
System.out.println("thread.isInterrupted() = " + thread.isInterrupted());
}
}
!Thread.currentThread().isInterrupted() 로 interrupted 상태를 체크해서 처리하면 오류를 방지할 수 있다.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;
}
}
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.sleep(ms); ms(밀리초) 단위로 설정interrupt() 를 만날 경우InterruptedException이 발생할 수 있다예시
Thread thread = new Thread(task, "thread"); // NEW
thread.start(); // NEW → RUNNABLE
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
→ 남은 시간을 다음 쓰레드에게 양보하고, 쓰레드 자신은 실행대기 상태로 변경하는 명령어
예시) thread1과 thread2가 같이 1초에 한번씩 출력되다가
5초뒤에 thread1에서 InterruptedException이 발생하면서 Thread.yield(); 이 실행되어
thread1은 실행대기 상태로 변경되면서 남은 시간은 thread2에게 리소스를 양보
public class Main {
public static void main(String[] args) {
Runnable task = () -> {
try {
for (int i = 0; i < 10; i++) {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName());
// 현재 Thread 이름을 출력
}
} catch (InterruptedException e) {
Thread.yield(); // 예외처리가 될 경우 다음 쓰레드에게 양보
// e.printStackTrace();
}
};
Thread thread1 = new Thread(task, "thread1");
Thread thread2 = new Thread(task, "thread2");
thread1.start();
thread2.start();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread1.interrupt();
}
}
→ 쓰레드가 진행중인 작업을 다른 쓰레드가 침범하지 못하도록 막는 것을 '쓰레드 동기화'(Synchronization)라고 함
방법 1. 메서드 전체를 임계영역으로 지정
public synchronized void asyncSum() {
...침범을 막아야하는 코드...
}
방법 2. 특정 영역을 임계영역으로 지정
synchronized(해당 객체의 참조변수) {
...침범을 막아야하는 코드...
}
예시
→ 과를 순서대로 잘 먹는 것을 확인
→ 만약 Syschronized 를 설정하지 않았을 경우 : 남은 사과의 수가 뒤죽박죽 출력될뿐만 아니라 없는 사과를 먹는 경우도 발생
public class Main {
public static void main(String[] args) {
AppleStore appleStore = new AppleStore();
Runnable task = () -> {
while (appleStore.getStoredApple() > 0) {
appleStore.eatApple();
System.out.println("남은 사과의 수 = " + appleStore.getStoredApple());
}
};
// 3개의 thread를 한꺼번에 만들어서 start 수행
// 생성(NEW)과 동시에 start(NEW → RUNNABLE)
for (int i = 0; i < 3; i++) {
new Thread(task).start();
}
}
}
class AppleStore {
private int storedApple = 10;
public int getStoredApple() {
return storedApple;
}
// synchronized 처리 하기 전
public void eatApple() {
// A라는 thread가 사과를 먹었은 후, 사과가 없는데 B와 C Thread는 사과를 먹는 척을 하게 됨
if (storedApple > 0) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
storedApple -= 1;
}
}
// synchronized 처리 한 후
public void eatApple() {
// 순서대로 사과를 먹도록 싱크를 맞춤
synchronized (this) {
if(storedApple > 0) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
storedApple -= 1;
}
}
}
}
→ 실행중인 쓰레드가 Lock을 반납하고 대기하게 하는 명령어
예시
public class Main {
public static String[] itemList = {
"MacBook", "IPhone", "AirPods", "iMac", "Mac mini"
};
public static AppleStore appleStore = new AppleStore();
public static final int MAX_ITEM = 5;
public static void main(String[] args) {
// 가게 점원
Runnable StoreClerk = () -> {
while (true) {
int randomItem = (int) (Math.random() * MAX_ITEM);
appleStore.restock(itemList[randomItem]);
try {
Thread.sleep(50);
} catch (InterruptedException ignored) {
}
}
};
// 고객
Runnable Customer = () -> {
while (true) {
try {
Thread.sleep(77);
} catch (InterruptedException ignored) {
}
int randomItem = (int) (Math.random() * MAX_ITEM);
appleStore.sale(itemList[randomItem]);
System.out.println(Thread.currentThread().getName() + " Purchase Item " + itemList[randomItem]);
}
};
new Thread(StoreClerk, "StoreClerk").start();
new Thread(Customer, "Customer1").start();
new Thread(Customer, "Customer2").start();
}
}
class AppleStore {
private List<String> inventory = new ArrayList<>();
public void restock(String item) {
synchronized (this) {
while (inventory.size() >= Main.MAX_ITEM) {
System.out.println(Thread.currentThread().getName() + " Waiting!");
try {
wait(); // 재고가 꽉 차있어서 재입고하지 않고 기다리는 중!
Thread.sleep(333);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 재입고
inventory.add(item);
notify(); // 재입고 되었음을 고객에게 알려주기
System.out.println("Inventory 현황: " + inventory.toString());
}
}
public synchronized void sale(String itemName) {
while (inventory.size() == 0) {
System.out.println(Thread.currentThread().getName() + " Waiting!");
try {
wait(); // 재고가 없기 때문에 고객 대기중
Thread.sleep(333);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
while (true) {
// 고객이 주문한 제품이 있는지 확인
for (int i = 0; i < inventory.size(); i++) {
if (itemName.equals(inventory.get(i))) {
inventory.remove(itemName);
notify(); // 제품 하나 팔렸으니 재입고 하라고 알려주기
return; // 메서드 종료
}
}
// 고객이 찾는 제품이 없을 경우
try {
System.out.println(Thread.currentThread().getName() + " Waiting!");
wait();
Thread.sleep(333);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
→ 기존의 쓰레드 동기화(synchronized) 방식의 제약을 해결하기 위해 사용된 방법
ReentrantReadWriteLock에 낙관적인 Lock의 기능을 추가낙관적인 Lock : 데이터를 변경하기 전에는 락을 걸지 않고, 변경할 때 락을 거는 것예시
// ReentrantLock 예시
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();
}
}
}
- methodA는 **lock1**을 가지고, methodB는 **lock2**를 가집니다.
- methodB에서 **methodA**를 호출하고 있으므로, **methodB**에서 **lock2**를 가진 상태에서 methodA를 호출하면 **lock1**을 가지려고 할 것입니다.
- 그러나 이때, methodA에서 이미 lock1을 가지고 있으므로 lock2를 기다리는 상태가 되어 데드락이 발생할 가능성이 있습니다.
- 하지만 ReentrantLock을 사용하면, 같은 스레드가 이미 락을 가지고 있더라도 락을 유지하며 계속 실행할 수 있기 때문에 데드락이 발생하지 않습니다.
- 즉, ReentrantLock을 사용하면 코드의 유연성을 높일 수 있습니다.
→ 각 쓰레드마다 상태를 정의하여 대기줄(waiting pool)에서 각 쓰레드를 구분하는 방법
예시) Condition 을 만들어서 대기줄(waiting pool)을 사용
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(); // 임계영역 끝
}
}