동시성이란? (Concurrency)
동시성 프로그래밍이란?
동시성에 대해서 자바 문서에서는 이렇게 설명하고 있다. 유저는 컴퓨터를 사용하면서 한 번에 한 가지 이상의 작업을 수행할 수 있다는 사실을 당연하게 여긴다. 그들은 워드 프로세서 작업을 하면서 파일을 다운로드 받거나, 프린트 인쇄 대기열을 관리하거나, 오디오 스트리밍을 할 수 있는다고 생각한다. 심지어 단일 응용 어플리케이션의 경우에도 한 번에 둘 이상의 작업을 해야할 때가 많다. 예를 들어서 스트리밍 오디오 애플리케이션은 네트워크에서 디지털 오디오를 동시에 읽고 압축을 풀고, 재생을 관리하고 디스플레이를 업데이트 해야합니다. 워드 프로세서 조차도 텍스트 서식을 변경하거나 디스플레이를 업데이트 하는 작업이 아무리 바쁘더라도 키보드 및 마우스 이벤트에 항상 응답을 할 준비가 되어있어야 합니다. 이러한 작업을 수행할 수 있는 소프트웨어를 동시성 소프트웨어라고 합니다. 자바 플랫폼은 동시성 프로그래밍을 지원하도록 처음부터 설계되었습니다. 자바 버전 5.0부터 자바는 높은 수준의 동시성 API를 포함하고 있습니다.1
프로세스와 쓰레드
동시성 프로그래밍에서는 두가지의 기본 실행 단위가 있다. 자바에서는 동시성 프로그래밍은 주로 쓰레드와 관련이 있지만, 프로세스 역시 중요하다. 컴퓨터 시스템에서 일반적으로 많은 프로세스와 스레드가 있다. 이는 단일 코어 시스템에서도 마찬가지이다.
프로세스
- 프로세스는 자체적으로 포함된 실행 환경이 있다. 일반적으로 프로세스에서는 개인적인 리소스 공간이 존재하고 특히 각각의 프로세스에는 자체적인 메모리 공간을 가집니다.
- 대부분의 자바 가상 머신 구현은 단일 프로세스로 실행된다.
Java
는ProcessBuilder
를 이용하여 추가적인 프로세스를 생성할 수 있습니다.
쓰레드
-
쓰레드는 때때로 경량 프로세스라고 불리우며, 프로세스와 쓰레드 모두 실행 환경을 제공하지만 새로운 쓰레드를 만드는데 필요한 리소스가 새 프로세를 만드는 것보다 적은 비용이 든다.
-
쓰레드는 프로세스 내에 존재하며 모든 프로세스에는 최소한 한 개의 쓰레드가 있다. 쓰레드는 프로세스의 메모리, 열린 파일 및 자원을 공유합니다.
-
이러한 자원 공유는 쓰레드 간의 소통에 효율적이지만 잠재적으로 문제를 일으킬 수 있습니다. 2
자바에서 쓰레드를 사용하는 방법
자바에서 쓰레드를 사용할 수 있는 방법에는 크게 3가지가 있다.
1. Thread를 상속 받는 방법
public class Main {
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
System.out.println("Hello");
}
static class MyThread extends Thread {
@Override
public void run() {
System.out.println("Thread: " + Thread.currentThread().getName());
}
}
}
2. Runnable을 구현하는 방법
public class Main {
public static void main(String[] args) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Thread: " + Thread.currentThread().getName());
}
});
thread.start();
System.out.println("Hello: " + Thread.currentThread().getName());
}
}
3. 람다를 사용하는 방법
public class Main {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
System.out.println("Thread: " + Thread.currentThread().getName());
});
thread.start();
System.out.println("Hello: " + Thread.currentThread().getName());
}
}
쓰레드의 주요 기능
sleep
public class Main {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
System.out.println("Thread: " + Thread.currentThread().getName());
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
thread.start();
System.out.println("Hello: " + Thread.currentThread().getName());
}
}
Thread.sleep
은 스레드가 지정된 기간 동안 실행을 일시 중단 하도록 한다.InterruptedException
은 스레드가 슬립된 상태에서 다른 스레드가 현재 스레드를 인터럽트 시킬 떄 발생된다.- 위의 예제에서는 쓰레드가 1초동안 슬립 되었다가 다시 실행된다.
interrupt
public class Main {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
while (true) {
System.out.println("Thread: " + Thread.currentThread().getName());
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
System.out.println("interrupt!");
return;
}
}
});
thread.start();
System.out.println("Hello: " + Thread.currentThread().getName());
Thread.sleep(3000L);
thread.interrupt();
}
}
interrupt()
메서드를 호출 했을 때InterruptedException
예외가 발생하고 예외 처리 구문이 실행된다.- 스레드가 인터럽트 되었을 때, 처리하는 방식은 프로그래머가 결정한다.
join
public class Main {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
System.out.println("Thread: " + Thread.currentThread().getName());
try {
Thread.sleep(3000L);
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
});
thread.start();
System.out.println("Hello: " + Thread.currentThread().getName());
thread.join();
System.out.println(thread + " is finished");
}
}
join()
을 사용하면 스레드가 다른 스레드가 끝날 때까지 기다려준다.sleep()
과 마찬가지로join()
은 인터럽트가 되면InterruptedException
을 호출하면서 종료 된다.