프로세스 내부의 세부적 실행단위로 사전적 의미로는 실을 의미한다.
Process란 현재 실행중인 프로그램을 말한다.
예를 들어보자.
동영상 플레이어가 실행 중이면 프로세스이고 그 동영상 플레이어 프로세스 내부의 세부적 실행단위인 영상 , 음향 , 자막 과 같은 것이 쓰레드이다.
이들이 동시에 실행되는 것을 멀티 스레딩(Multi Threading)이라고 한다 .
또다른 예로는 여러개의 실(thread)이 모여 옷(process)를 구성한다.
쓰레드를 생성하는 방법은 1번과 2번이 있다.
쓰레드를 상속(extends)받는 방법과 Runnable 인터페이스를 구현(implements)하는 것이다.
스레드를 실행가능(Runnable) 상태로 보낸다. 이후 JVM이 스케줄링을 해서 실행(Running) 상태로 보낸다.
스레드의 실행 내용을 정의한다.
JVM이 스케줄링을 하면 실행된다.
run() 메서드 실행이 완료되면 스레드는 종료된다
class VideoWorker{
public void video() {
for(int i=0;i<10;i++)
System.out.println("영상을 제공하다");
}
}
public class TestThread2 {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName()+" 스레드 시작");
VideoWorker worker=new VideoWorker();
worker.video();
System.out.println(Thread.currentThread().getName()+" 스레드 종료");
}
}
main 스레드 시작
영상을 제공하다
영상을 제공하다
영상을 제공하다
영상을 제공하다
영상을 제공하다
영상을 제공하다
영상을 제공하다
영상을 제공하다
영상을 제공하다
영상을 제공하다
main 스레드 종료
이 예제는 main thread 즉 단일 스레드 환경에서의 실행 결과를 확인할 수 있는 예제이다.
메인 메소드 내에 worker.go() 메소드만 있기때문에
1. main 실행
2. go메서드 실행
3. go 메서드 종료
4. main종료
이렇게 해서 결국 main스레드 하나만 존재 하는 단일 스레드 환경이다.
class VideoWorker implements Runnable{
@Override
public void run() { // JVM에 의해 스케줄링 되면 run 메서드가 실행
video();
}
public void video() {
for(int i=0;i<10;i++)
System.out.println(Thread.currentThread().getName()+"스레드가 영상을 제공하다");
}
}
public class TestThread3 {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName()+" 스레드 시작");
VideoWorker videoWorker=new VideoWorker();
Thread thread=new Thread(videoWorker,"영상일꾼");
thread.start();//스레드를 실행가능상태로 보낸다
System.out.println(Thread.currentThread().getName()+" 스레드 종료");
}
}
main 스레드 시작
main 스레드 종료
영상일꾼스레드가 영상을 제공하다
영상일꾼스레드가 영상을 제공하다
영상일꾼스레드가 영상을 제공하다
영상일꾼스레드가 영상을 제공하다
영상일꾼스레드가 영상을 제공하다
영상일꾼스레드가 영상을 제공하다
영상일꾼스레드가 영상을 제공하다
영상일꾼스레드가 영상을 제공하다
영상일꾼스레드가 영상을 제공하다
영상일꾼스레드가 영상을 제공하다
예제의 결과는 다음과 같다.
위의 단일 스레드 예제와 비교해 결과를 확인할 수 있는 예제로, 멀티 스레드 예제이다.
위의 단일 쓰레드 예제는 단일 쓰레드 환경이므로 VideoWorker의 video 메서드가 반드시 모두 수행된 후에 main 이 종료되었다.
현재 예제는 멀티 쓰레딩 환경이다.
VideoWorker Thread를 main thread 가 start, 즉 실행가능상태로 보낸 후 자신은 종료되고, 이후 스케줄링을 받은 VideoWorker Thread가 run, 즉 실행을 하고 종료된다.
public class TestThread4 {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName()+" 스레드 시작");
Thread audioThread=new Thread(new AudioWorker(),"오디오 일꾼");
audioThread.start();//오디오 스레드를 실행 가능 상태로 보낸다
Thread videoThread=new Thread(new VideoWorker(),"비디오 일꾼");
videoThread.start();//비디스 스레드를 Runnable 상태로 보낸다
System.out.println(Thread.currentThread().getName()+" 스레드 종료");
}
}
public class VideoWorker implements Runnable{
@Override
public void run() {
try {
video();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void video() throws InterruptedException {
for(int i=0;i<10;i++) {
System.out.println(Thread.currentThread().getName()+"스레드가 영상을 재생하다");
Thread.sleep(1000);//2초간 스레드가 실행을 중지한 후 다시 재개
}
}
}
public class AudioWorker implements Runnable{
@Override
public void run() {
try {
audio();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void audio() throws InterruptedException {
for(int i=0;i<10;i++) {
System.out.println(Thread.currentThread().getName()+"스레드가 음향을 재생하다");
Thread.sleep(2000);//2초간 스레드가 실행을 중지한 후 다시 재개
}
}
}
main 스레드 시작
오디오 일꾼스레드가 음향을 재생하다
main 스레드 종료
비디오 일꾼스레드가 영상을 재생하다
비디오 일꾼스레드가 영상을 재생하다
오디오 일꾼스레드가 음향을 재생하다
비디오 일꾼스레드가 영상을 재생하다
비디오 일꾼스레드가 영상을 재생하다
오디오 일꾼스레드가 음향을 재생하다
비디오 일꾼스레드가 영상을 재생하다
비디오 일꾼스레드가 영상을 재생하다
오디오 일꾼스레드가 음향을 재생하다
비디오 일꾼스레드가 영상을 재생하다
비디오 일꾼스레드가 영상을 재생하다
오디오 일꾼스레드가 음향을 재생하다
비디오 일꾼스레드가 영상을 재생하다
비디오 일꾼스레드가 영상을 재생하다
오디오 일꾼스레드가 음향을 재생하다
오디오 일꾼스레드가 음향을 재생하다
오디오 일꾼스레드가 음향을 재생하다
오디오 일꾼스레드가 음향을 재생하다
오디오 일꾼스레드가 음향을 재생하다
멀티 쓰레드 환경에서의 예제이다.
1. 메인 쓰레드는 오디오 쓰레드를 start(실행 가능 상태로 만든 후 종료)
2. 이후 스케줄링을 받은 오디오 쓰레드가 run(실행)
3. 메인 쓰레드가 비디오 쓰레드를 start(실행 가능 상태로 만든 후 종료)
4. 이후 스케줄링을 받은 비디오 쓰레드가 run(실행)
메인 쓰레드는 쓰레드를 생성하고 start 시키기만 하고 자기할 일을 종료 한다.
메인쓰레드 내에서 코드를 수행하기때문에 "main 스레드 종료" 라는 출력문이 먼저 나오게된것이고, 이후 그제서야 JVM 스케줄러가 쓰레드를 run하기때문에 오디오 쓰레드와 비디오 쓰레드를 각자 실행해 번갈아 가며 수행된 결과이다.
만약 main 내의 코드가 저것보다 좀 더 길게되면 메인이 종료되기 전에 jvm이 상위의 스레드를 먼저 실행시킬 수도 있다. 그래서 이 때 쓰레드의 run() 메소드의 문장이 실행되기도 한다.
public class TestThread6 {
public static void main(String[] args) {
String threadName=Thread.currentThread().getName();
System.out.println(threadName+"스레드 시작"); //메인스레드가 서버의 접수처 대표전화 역할을 한다.
ServerWorker sw=new ServerWorker();
for(int i=1;i<=3;i++) {
Thread thread=new Thread(sw,i+"번 일꾼");
thread.start();
}
System.out.println(threadName+"스레드 종료");
}
}
public class ServerWorker implements Runnable{
@Override
public void run() {
try {
chattingService();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void chattingService() throws InterruptedException {
String threadName=Thread.currentThread().getName();
for(int i=0;i<10;i++) {
System.out.println(threadName+" 스레드가 고객에게 채팅서비스를 하다 "+i);
Thread.sleep(1000);
}
}
}
main스레드 시작
main스레드 종료
1번 일꾼 스레드가 고객에게 채팅서비스를 하다 0
3번 일꾼 스레드가 고객에게 채팅서비스를 하다 0
2번 일꾼 스레드가 고객에게 채팅서비스를 하다 0
3번 일꾼 스레드가 고객에게 채팅서비스를 하다 1
1번 일꾼 스레드가 고객에게 채팅서비스를 하다 1
2번 일꾼 스레드가 고객에게 채팅서비스를 하다 1
1번 일꾼 스레드가 고객에게 채팅서비스를 하다 2
3번 일꾼 스레드가 고객에게 채팅서비스를 하다 2
2번 일꾼 스레드가 고객에게 채팅서비스를 하다 2
1번 일꾼 스레드가 고객에게 채팅서비스를 하다 3
3번 일꾼 스레드가 고객에게 채팅서비스를 하다 3
2번 일꾼 스레드가 고객에게 채팅서비스를 하다 3
3번 일꾼 스레드가 고객에게 채팅서비스를 하다 4
2번 일꾼 스레드가 고객에게 채팅서비스를 하다 4
1번 일꾼 스레드가 고객에게 채팅서비스를 하다 4
1번 일꾼 스레드가 고객에게 채팅서비스를 하다 5
3번 일꾼 스레드가 고객에게 채팅서비스를 하다 5
2번 일꾼 스레드가 고객에게 채팅서비스를 하다 5
1번 일꾼 스레드가 고객에게 채팅서비스를 하다 6
3번 일꾼 스레드가 고객에게 채팅서비스를 하다 6
2번 일꾼 스레드가 고객에게 채팅서비스를 하다 6
1번 일꾼 스레드가 고객에게 채팅서비스를 하다 7
3번 일꾼 스레드가 고객에게 채팅서비스를 하다 7
2번 일꾼 스레드가 고객에게 채팅서비스를 하다 7
1번 일꾼 스레드가 고객에게 채팅서비스를 하다 8
2번 일꾼 스레드가 고객에게 채팅서비스를 하다 8
3번 일꾼 스레드가 고객에게 채팅서비스를 하다 8
1번 일꾼 스레드가 고객에게 채팅서비스를 하다 9
2번 일꾼 스레드가 고객에게 채팅서비스를 하다 9
3번 일꾼 스레드가 고객에게 채팅서비스를 하다 9
멀티 쓰레드의 세번째 예제이다.
멀티 쓰레드에서는 유선상 고객 문의 센터라는 개념에 적용할 수 있다.
고객이 전화를 걸어서 대표 접수처가 전화를 받는다.
그 후 고객은 자신의 용무에 맞게 분류되어 또 다른 접수처에게 다시 연결된다.
멀티 쓰레드의 개념에 적용해보자. 이 예제에는 두가지 쓰레드가 존재할 것이다.
여기서 첫번째 쓰레드는 대표 전화 즉, 접수처 역할을 하여 고객의 전화를 받아 담당 직원을 연결해주는 쓰레드이고 메인 쓰레드라고 생각하면된다.
두번째 쓰레드는 실제 고객에게 서비스하는 쓰레드, 두가지가 존재하는 것이다.
클래스 구성은 위의 두 종류를 정의하였고, 두번째 실제 고객에서의 서비스는 스레드는 접속한 고객 당 하나씩 생성되어야 한다는 것이 중요하다.
String threadName=Thread.currentThread().getName();
System.out.println(threadName+"스레드 시작");
ServerWorker sw=new ServerWorker();
메인쓰레드가 시작되었고, 서버의 접수처 대표전화 역할을 한다.
for(int i=1;i<=3;i++) {
Thread thread=new Thread(sw,i+"번 일꾼");
thread.start();
}
반복문으로 3개의 쓰레드가 생성되었고, 메인쓰레드는 두번째 쓰레드에게 일을 넘긴것이다.
즉 start() 로 인해 실행가능 상태를 만들고 메인 쓰레드는 종료되었다.
for(int i=0;i<10;i++) {
System.out.println(threadName+" 스레드가 고객에게 채팅서비스를 하다 "+i);
Thread.sleep(1000);
}
메인 쓰레드에 의해서 독립적인 작업을 수행하기 위한 쓰레드 3개가 생성되었었고,
이후에 run() 되어 실행된 내용이다.
3개의 쓰레드들이 독립적으로 각자 10번씩 메소드가 실행된것이다.
메인 쓰레드를 실행할때마다 결과는 다를것이다.
스케줄러는 start()되어 실행대기중인 쓰레드들의 우선순위를 고려하여 실행 순서와 실행 시간을 결정한다. 각 쓰레드들은 작성된 스케줄에 따라 자신의 순서가 되면 작업을 수행하기때문에 1번 일꾼, 2번 일꾼, 3번 일꾼이 정해지지않은 순서대로 메소드를 수행한 것이다.
Daemon Thread 처리 : 자신을 생성한 스레드가 종료될 때 함께 종료되는 스레드
- 예) Word 스레드에서 Backup 스레드를 생성하고 start
- 두 스레드가 작업 진행
- Word 스레드가 종료되면 Backup 스레도도 함께 종료되도록 처리해야 한다.
- => Backup 스레드를 Daemon 스레드로 처리 : setDaemon(true)
메인쓰레드, word 쓰레드, BackupWorker 쓰레드를 실행하는 예제이다.
public class TestThread7 {
public static void main(String[] args) {
Word word=new Word();
Thread wordThread=new Thread(word);
wordThread.start();
}
}
public class Word implements Runnable{
@Override
public void run() {
System.out.println("**워드문서스레드 작업시작**");
BackupWorker backupWorker=new BackupWorker();
Thread backupThread=new Thread(backupWorker);
backupThread.setDaemon(true);
backupThread.start();
try {
word();
backupWorker.backup();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("**워드문서스레드 작업종료**");
}
public void word() throws InterruptedException {
for(int i=0;i<20;i++) {
System.out.println("워드문서작업 "+i);
Thread.sleep(1000);
}
}
}
public class BackupWorker implements Runnable{
@Override
public void run() {
while(true) {
try {
Thread.sleep(3000);
backup();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void backup() throws InterruptedException {
System.out.println("백업스레드가 워드문서를 백업처리한다");
}
}
워드문서스레드 작업시작
워드문서작업 0
워드문서작업 1
워드문서작업 2
백업스레드가 워드문서를 백업처리한다
워드문서작업 3
워드문서작업 4
워드문서작업 5
백업스레드가 워드문서를 백업처리한다
워드문서작업 6
워드문서작업 7
워드문서작업 8
백업스레드가 워드문서를 백업처리한다
워드문서작업 9
워드문서작업 10
워드문서작업 11
백업스레드가 워드문서를 백업처리한다
워드문서작업 12
워드문서작업 13
워드문서작업 14
백업스레드가 워드문서를 백업처리한다
워드문서작업 15
워드문서작업 16
워드문서작업 17
백업스레드가 워드문서를 백업처리한다
워드문서작업 18
워드문서작업 19
백업스레드가 워드문서를 백업처리한다
워드문서스레드 작업종료
System.out.println("**워드문서스레드 작업시작**");
BackupWorker backupWorker=new BackupWorker();
Thread backupThread=new Thread(backupWorker);
출력문이 나오고 BackupWorker 쓰레드를 생성한다.
backupThread.setDaemon(true);
backupThread.start();
이 때 backup 작업을 담당하는 스레드를 Daemon Thread로 만든다.
자신을 생성한 스레드가 종료되면 함께 종료되는 스레드가 Daemon Thread이다.
try {
word();
backupWorker.backup();
} catch (InterruptedException e) {
e.printStackTrace();
}
워드문서 스레드 종료전에 백업을 하도록 메서드를 직접 호출한다.
word의 run()안에 backupWorker를 생성하고 데몬 쓰레드를 처리했다.
Word 스레드가 종료하면 backupWorker스레드도 종료되는데, 그러면 워드작업이 끝나고는 백업을 안 해주기때문에 워드작업을 마치면 한번 더 백업을 해주기 위해서 스레드가 아닌 메서드를 직접 호출해준것이다.
for(int i=0;i<20;i++) {
System.out.println("워드문서작업 "+i);
Thread.sleep(1000);
}
"워드문서작업"이라는 문구를 20번 1초 간격으로 출력한다.
이 때 word 메소드를 수행하며 backup() 메소드를 직접 호출하였기때문에 내용을 함께 보자.
public void run() {
while(true) {
try {
Thread.sleep(3000);
backup();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void backup() throws InterruptedException {
System.out.println("백업스레드가 워드문서를 백업처리한다");
}
word 쓰레드가 20번 1초 간격으로 "워드문서작업"을 출력할 때 BackupWorker는 3초 간격으로 "백업스레드가 워드문서를 백업처리한다"문장을 출력한다. 이 후 호출한 word 쓰레드가 종료되면 데몬 쓰레드로 설정된 BackupWorker는 종료된다.