메인 쓰레드
로부터 실행되며 JVM에 의해 실행된다.main
메소드를 실행시키면 메인 쓰레드가 시작된다.데몬 스레드도 자동
으로 종료시켜 버린다.JVM 프로세스 + 메인 스레드
JVM 프로세스 + 메인 스레드 + 여러 개의 다른 스레드
Thread thread = new Thread();
tread.setDaemon(true);
우선순위의 숫자가 높을수록 먼저 작업할 가능성이 높다.
Java에서의 스레드 우순순위 범위는 OS가 아니라 JVM이다.
1~10의 값으로 설정해줄 수 있으며, 기본 값은 5(NROM_PRIORITY)이다.
Thread thread = new Thread();
tread.setPriotiry(8); // 먼저 실행될 가능성이 높음
Thread thread2 = new Thread();
tread2.setPriotiry(Thread.MIN_PRIORITY);
스레드들은 기본적으로 그룹에 포함되어 있다.
system
그룹이 생성되고 스레드들은 기본적으로 system
그룹에 포함된다.메인 스레드는 system 그룹 하위에 있는 main 그룹에 포함되어 있다.
모든 스레드는 반드시 하나의 그룹에 포함되어 있어야 한다.
- 그룹을 지정받지 못한 스레드는 자신을 생성한 부모 스레드의 그룹과 우선순위를 상속받게 된다.
ThreadGroup group1 = new ThreadGroup("Group1");
Thread thread1 = new Thread(group1, "thread1");
Thread thread2 = new Thread(group1, "thread2");
// 해당 스레드 그룹의 일시정지 상태인 스레드 실행 대기 상태로 만든다.
group1.interrupt();
Thread.sleep()
InterruptedException
예외 처리를 반드시 해줘야 한다.interrupt()
호출
하면, InterruptedException 예외가 발생
하며 실행대기 상태로 된다.isInterrupted()
메소드를 통해, 해당 스레드가 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) {
// interrupt가 호출되면, 무한 반복문을 탈출
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());
}
}
join()
시간을 지정하지 않는다면, 지정한 스레드의 작업이 끝날 때까지 기다린다.
public class Main {
public static void main(String[] args) {
Runnable task = () -> {
try {
Thread.sleep(5000); // 5초
} catch (InterruptedException e) {
e.printStackTrace();
}
};
Thread thread = new Thread(task, "thread");
thread.start();
long start = System.currentTimeMillis();
try {
thread.join(); // 메인 스레드는 5초간 기다리게 된다.
} catch (InterruptedException e) {
// interrupt()로 메인 스레드를 강제로 깨울 수도 있다.
// 그렇기에 예외 처리를 해줘야만 한다.
e.printStackTrace();
}
// thread 의 소요시간인 5000ms 동안 main 쓰레드가 기다렸기 때문에 5000이상이 출력됩니다.
System.out.println("소요시간 = " + (System.currentTimeMillis() - start));
}
}
Thread.yield()
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());
}
} catch (InterruptedException e) {
// interrupt를 통해 예외 처리가 되며
// 다른 스레드에게 리소스가 양보된다.
Thread.yield();
}
};
Thread thread1 = new Thread(task, "thread1");
Thread thread2 = new Thread(task, "thread2");
thread1.start();
thread2.start();
try {
Thread.sleep(5000); // 메인 스레드는 5초간 대기
} catch (InterruptedException e) {
e.printStackTrace();
}
// 스레드 1을 실행대기 상태로 변경되며 yield()를 호출
thread1.interrupt();
}
}
synchronized
Lock
을 가진 단 하나의 스레드만 출입이 가능하다.wait()
sleep()
랑을 달리, 단순히 기다리는것이 아니라 lock을 반납한다.notify()
호출을 받게 되면 lock을 얻어 진행할 수 있다.synchronized
블록 내에서만 호출이 가능notify(), notifyAll()
notify()
notifyAll()
모든 스레드
에게 통지한다.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();
}
}
}
}
Lock, Condition
ReentrantLock
재진입이 가능한 lock, 가장 일반적인 배타 lock
synchronized
블록과 비슷함
synchronized(lock){...}
---
lock.lock();
...
lock.unlock();
ReentrantReadWriteLock
StampedLock
ReentrantReadWriteLock
에 낙관적인 lock의 기능을 추가기존의 notify()
메소드는 어떤 스레드를 깨워야 할 지 특정하질 못했다.
Condition
을 사용한다면, 특정할 수 있다.
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(); // 임계영역 끝
}
}