프로세스 는 "실행중인 프로그램" 을 의미한다.프로세스 위에서 실행된다.code, Data 그리고 메모리 영역(Stack, Heap) 를 받는다.
Data 는 프로그램이 실행중 저장 가능한 초기화된 데이터의 저장공간이다.Memory 영역은
프로세스: OS 로부터 자원을 할당받는 작업의 단위
쓰레드: 프로세스가 할당받은 자원을 이용하는 실행의 단위
Data의 저장공간과 Memory 영역은 다른 의미이다.
스레드 란 프로세스 내에서 실제로 작업을 수행하는 주체를 의미한다. Runnable 인터페이스를 구현하는 방법Thread 클래스를 상속받는 방법두 방법 모두 run() 메서드에 작성하면 된다.
// 1.
public class TestThread extends Thread {
@Override
public void run() {
// 쓰레드 수행작업
}
}
// 2.
public class TestRunnable implements Runnable {
@Override
public void run() {
// 쓰레드 수행작업
}
}
TestThread thread = new TestThread(); // 쓰레드 생성
thread.start() // 쓰레드 실행
// 람다식 표현
public class Main {
public static void main(String[] args) {
Runnable task = () -> {...};
Thread thread1 = new Thread(task);
thread1.setName("thread1");
Thread thread2 = new Thread(task);
thread2.setName("thread2");
thread1.start();
thread2.start();
}
}
run() 쓰일 수행할 작업을 쓰면 된다.
java는 메인 쓰레드가 main() 메서드를 실행시키면도 시작이 된다. 교착상태(데드락)이 발생한다. background 에서 실행되는 낮은 우선순위를 가지는 쓰레드이다.
가비지 컬렉터(GC) 가 있다. thread.setDaemon(true); // true로 설정시 데몬스레드로 실행됨
foreground 에서 실행되는 높은 우선순위를 가진 쓰레드이다.
메인 쓰레드 가 있다. 사용자 쓰레드 이다. JVM 은 사용자 쓰레드의 작업이 긑나면 데몬 쓰레드도 자동을 종료한다.
쓰레드는 작업의 중요도에 따라 각 쓰레드의 우선순위를 부여할 수 있다.다.
5 이다. Thread thread1 = new Thread(task1);
thread1.setPriority(8);
int threadPriority = thread1.getPriority();
System.out.println("threadPriority = " + threadPriority);
하지만 우선순위가 높다고 반드시 쓰레드가 먼저 종료되는 것이 아니다.
확률이 높은것이지 실제로 코드로 결과를 보면 확연한 차이가 들어나지 않는다.
관련된 쓰레드들 끼리 묶어서 다룰 수 있다.
system 그룹이 생성 되어 포함된다.system 그룹 하위에 있는 main 그룹에 포함된다. main 쓰레드 하위에 포함된다. 그룹이 지정되지 않으면 자동으로 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());
run() 메서드를 수행한다.run() 메서드가 종료되면 실행이 멈춘다. 
| 상태 | Enum | 설명 |
|---|---|---|
| 객체생성 | NEW | 쓰레드 객체 생성, 아직 start() 메서드 호출 전의 상태 |
| 실행대기 | RUNNABLE | 실행 상태로 언제든지 갈 수 있는 상태 |
| 일시정지 | WAITING | 다른 쓰레드가 통지(notify) 할 때까지 기다리는 상태 |
| 일시정지 | TIMED_WAITING | 주어진 시간 동안 기다리는 상태 |
| 일시정지 | BLOCKED | 사용하고자 하는 객체의 Lock이 풀릴 때까지 기다리는 상태 |
| 종료 | TERMINATED | 쓰레드의 작업이 종료된 상태 |
쓰레드를 제어 가능한 Thread 클래스 안의 기능들을 사용할 수 있다.
Thread 는 static method 이기에 Thread.method 처럼 사용한다. try {
Thread.sleep(2000); // 2초
} catch (InterruptedException e) {
e.printStackTrace();
}
interrupt()를 만나면 InterruptedException이 발생한다. while (!Thread.currentThread().isInterrupted())
{...}
Thread thread = new Thread(task, "Thread");
thread.start();
thread.interrupt();
System.out.println("thread.isInterrupted() = "
+ thread.isInterrupted());
start() 된 후 동작하다 interrupt()를 만나 실행하면 interrupted 상태가 true가 됩니다.!Thread.currentThread().isInterrupted() 로 interrupted 상태를 체크해서 처리하면 오류를 방지할 수 있습니다.
정해진 시간동안 지정한 쓰레드가 작업하는 것을 기다린다.
만약 시간을 정하지 않으면 작업이 끝날 때까지 기다린다.
Thread thread = new Thread(task, "thread");
thread.start();
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
남은 시간을 다음 쓰레드에게 양보하고 쓰레드 자신은 실행 대기 상태가 된다.
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) {
Thread.yield();
}
};
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();
}
}
InterruptedException이 발생하면서 Thread.yield(); 이 실행되어 thread1은 실행대기 상태로 변경되면서 남은 시간은 thread2에게 리소스가 양보된다. 쓰레드 동기화(synchronization) 이라고 한다.임계영역 으로 설정한다.Lock 을 가진 단 하나의 쓰레드만 출입이가능하다. public synchronized void asyncSum() {
...침범을 막아야하는 코드...
}
////////////////////////////////////
synchronized(해당 객체의 참조변수) {
try{
} catch(Error e){
...
}
}
wait() 을 호출하여 쓰레드가 Lock을 반납하고 기다린다.wait()은 waiting pool 에서 대기한다.notify()를 호출하여 Lock을 얻을 수 있다.notify() 는 waiting pool 에 있는 임의의 쓰레드만 통지한다. synchronized 는 같은 메서드 내에서만 Lock 을 걸 수 있다는 제약이 있다. 때문에 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();
}
}
}
낙관적인 Lock 이란 데이터를 변경하기 전에 락을 걸지 않는 것이다. wait() & notify() 의 문제점인 waiting pool 안의 쓰레드를 구분하지 못하는데 그것의 해결책이 Condition 이다.
java.util.concurrent.locks 패키지에서 Condition 인터페이스를 제공한다.Condition 인터페이스는 ReentrantLock 클래스와 함께 사용됩니다. Condition 은 await() 와 signal()를 사용한다. 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(); // 임계영역 끝
}
}
이번시간에는 Thread 클래스의 활용법과 정확히 알지 못했던 개념들을 정리할 수 있는 시간이였다. 무엇보다 코드에 적용하는 연습을 해서 얻는게 많았던 시간이였다. 다음 시간에는 람다, 스트림, Optional 에 대해 알아본다.