프로그래머스 자바 중급 강의를 학습 목적으로 정리하여 포스팅합니다.
1. 쓰레드란?
동시에 여러가지 작업을 동시에 수행할 수 있게 하는 것
프로세스란 현재 실행되고 있는 프로그램을 말한다.
자바 프로그램은 JVM에 의해 실행되므로 JVM도 프로그램 중 하나이다.
운영체제 입장에서 보면 자바도 하나의 프로세스로 실행을 하는 것이다.
워드프로세서가 하나의 프로세스라면, 하나의 프로세스 안에서도 여러 개의 흐름이 동작할 수 있다.
이것을 Thread(쓰레드)라고 한다.
자바가 여러 개의 작업을 동시에 하게 만들고 싶다면 Thread (쓰레드)를 알아야 한다.
2. 쓰레드 만들기 (extend Thread)
Thread 클래스를 상속받는 방법과 Runnable 인터페이스를 구현하는 방법이 있다.
1. Thread 클래스를 상속받는 방법
Thread를 상속받아서 쓰레드를 생성하는 방법
public class MyThread1 extends Thread {
String str;
public MyThread1(String str){
this.str = str;
}
@Override
public void run(){
for(int i = 0; i < 10; i ++){
System.out.print(str);
try {
//컴퓨터가 너무 빨라 결과 확인을 위해
//Thread.sleep() 메서드를 이용해서 조금씩
//쉬었다가 출력할 수 있게한다.
Thread.sleep((int)(Math.random() * 1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
- java.lang.Thread 클래스를 상속받는다. 그리고 Thread가 가지고 있는 run() 메소드를 오버라이딩한다.
- 10번 반복하면서 str을 출력한다.
Thread 클래스를 상속받은 MyThread1을 사용하는 클래스
public class ThreadExam1 {
public static void main(String[] args) {
// MyThread인스턴스를 2개 만듭니다.
MyThread1 t1 = new MyThread1("*");
MyThread1 t2 = new MyThread1("-");
t1.start();
t2.start();
System.out.print("!!!!!");
}
}
- Thread를 상속받았으므로 MyThread1은 Thread이다.
- 쓰레드를 생성하고, Thread 클래스가 가지고 있는 start() 메소드를 호출한다.
3. 쓰레드 만들기 (implements Runnable)
2. Runnable 인터페이스를 구현해서 쓰레드를 만드는 방법
자바는 단일 상속만 가능하다.
이미 무언가를 상속받고 있는 상태에서 쓰레드를 적용하고 싶을 때 사용하는 방법이다.
Runnable 인터페이스가 가지고 있는 run() 메소드를 구현한다.
public class MyThread2 implements Runnable {
String str;
public MyThread2(String str){
this.str = str;
}
@Override
public void run(){
for(int i = 0; i < 10; i ++){
System.out.print(str);
try {
Thread.sleep((int)(Math.random() * 1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
Runnable 인터페이스를 구현한 MyThread2 사용하는 방법
public class ThreadExam2 {
public static void main(String[] args) {
MyThread2 r1 = new MyThread2("*");
MyThread2 r2 = new MyThread2("-");
Thread t1 = new Thread(r1);
Thread t2 = new Thread(r2);
t1.start();
t2.start();
System.out.print("!!!!!");
}
}
- MyThread2는 Thread를 상속받지 않았기 때문에 Thread가 아니다.
- Thread를 생성하고, 해당 생성자에 MyThread2를 넣어서 Thread를 생성한다.
- Thread 클래스가 가진 start() 메소드를 호출한다.
4. 쓰레드와 공유객체
하나의 객체를 여러 개의 Thread가 사용한다는 것을 의미
MusicBox라는 클래스는 3개의 메소드를 가지고 있다.
각각의 메소드는 1초 이하의 시간 동안 10번 반복하면서 어떤 음악을 출력한다.
이러한 MusicBox를 사용하는 MusicPlayer를 3명 만든다.
공유객체 MusicBox
public class MusicBox {
//신나는 음악!!! 이란 메시지가 1초이하로 쉬면서 10번 반복출력
public void playMusicA(){
for(int i = 0; i < 10; i ++){
System.out.println("신나는 음악!!!");
try {
Thread.sleep((int)(Math.random() * 1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//슬픈 음악!!!이란 메시지가 1초이하로 쉬면서 10번 반복출력
public void playMusicB(){
for(int i = 0; i < 10; i ++){
System.out.println("슬픈 음악!!!");
try {
Thread.sleep((int)(Math.random() * 1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//카페 음악!!! 이란 메시지가 1초이하로 쉬면서 10번 반복출력
public void playMusicC(){
for(int i = 0; i < 10; i ++){
System.out.println("카페 음악!!!");
try {
Thread.sleep((int)(Math.random() * 1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
MusicBox를 가지는 Thread 객체 MusicPlayer
public class MusicPlayer extends Thread{
int type;
MusicBox musicBox;
// 생성자로 부터 musicBox와 정수를 하나 받아들여서 필드를 초기화
public MusicPlayer(int type, MusicBox musicBox){
this.type = type;
this.musicBox = musicBox;
}
// type이 무엇이냐에 따라서
// musicBox가 가지고 있는 메소드가 다르게 호출
public void run(){
switch(type){
case 1 : musicBox.playMusicA(); break;
case 2 : musicBox.playMusicB(); break;
case 3 : musicBox.playMusicC(); break;
}
}
}
MusicBox와 MusicPlayer를 이용하는 MusicBoxExam1 클래스
public class MusicBoxExam1 {
public static void main(String[] args) {
// MusicBox 인스턴스
MusicBox box = new MusicBox();
MusicPlayer kim = new MusicPlayer(1, box);
MusicPlayer lee = new MusicPlayer(2, box);
MusicPlayer kang = new MusicPlayer(3, box);
// MusicPlayer쓰레드를 실행합니다.
kim.start();
lee.start();
kang.start();
}
}
5. 동기화 메소드와 동기화 블록
공유객체가 가진 메소드를 동시에 호출되지 않도록 하는 방법
- 메소드 앞에 synchronized를 붙힌다.
- 여러 개의 Thread들이 공유객체의 메소드를 사용할 때 메소드에 synchronized가 붙어있을 경우 먼저 호출한 메소드가 객체의 사용권(Monitoring Lock)을 얻는다.
public synchronized void playMusicA(){
for(int i = 0; i < 10; i ++){
System.out.println("신나는 음악!!!");
try {
Thread.sleep((int)(Math.random() * 1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
- 메소드 앞에 synchronized를 붙혀서 실행해보면 메소드 하나가 모두 실행된 후에 다음 메소드가 실행된다.
- 해당 객체의 사용권은 메소드 실행이 종료되거나 wait() 과 같은 메소드를 만나기 전까지 유지된다.
- 다른 쓰레드들은 객체의 사용권을 놓을 때까지 대기한다.
- synchronized를 붙히지 않은 메소드는 다른 쓰레드들이 synchronized 메소드를 실행하면서 객체의 사용권을 획득했다 하더라도 그것과 상관없이 실행된다.
public void playMusicB(){
for(int i = 0; i < 10; i ++){
synchronized(this){
System.out.println("슬픈 음악!!!");
}
try {
Thread.sleep((int)(Math.random() * 1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
- synchronized를 메소드에 붙혀서 사용할 경우, 메소드의 코드가 길어지면 마지막에 대기하는 쓰레드가 너무 오래 기다리는 것을 막기 위해서 메소드에 synchronized를 붙이지 않고 문제가 있을 것 같은 부분만 synchronized블록을 사용한다.
6. 쓰레드와 상태제어
쓰레드가 3개가 있다면 JVM은 시간을 잘게 쪼갠 후에 한번은 쓰레드1을, 한번은 쓰레드2를, 한번은 쓰레드3을 실행한다. 이것이 빠르게 실행되다보니 쓰레드가 모두 동작하는 것처럼 보이는 것이다.
- 쓰레드는 실행가능상태인 Runnable과 실행상태인 Running상태로 나뉜다.
- 실행되는 쓰레드 안에서 Thread.sleep() 이나 Object가 가지고 있는 wait() 메소드가 호출이 되면 쓰레드는 블록상태가 된다.
- Thread.sleep() 은 특정시간이 지나면 자신 스스로 블록상태에서 빠져나와 Runnable이나 Running 상태가 된다.
- Obejct가 가지고 있는 wait() 메소드는 다른 쓰레드가 notify() 나 notifyAll() 메소드를 호출하기 전에는 블록상태에서 해제되지 않는다.
- wait() 메소드는 호출이 되면 객체의 사용권을 놓게 된다. 그래서 대기 중인 다른 메소드가 실행된다.
- 쓰레드의 run 메소드가 종료되면 쓰레드는 종료된다. 즉 Dead 상태가 된다.
- Thread의 yield 메소드가 호출되면 해당 쓰레드는 다른 쓰레드에게 자원을 양보하게 된다.
- Thread가 가지고 있는 join메소드를 호출하게 되면 해당 쓰레드가 종료될 때까지 대기하게 된다.
7. 쓰레드와 상태제어 (join)
join() 메소드는 쓰레드가 멈출 때까지 기다리게 한다
0.5초씩 쉬면서 숫자를 출력하는 MyThread5
public class MyThread5 extends Thread{
public void run(){
for(int i = 0; i < 5; i++){
System.out.println("MyThread5 : "+ i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
해당 쓰레드를 실행하고 종료될 때까지 기다린 후, 내용을 출력하는 JoinExam 클래스
public class JoinExam {
public static void main(String[] args) {
MyThread5 thread = new MyThread5();
// Thread 시작
thread.start();
System.out.println("Thread가 종료될때까지 기다립니다.");
try {
// 해당 쓰레드가 멈출때까지 멈춤
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread가 종료되었습니다.");
}
}
실행결과
/*
Thread가 종료될때까지 기다립니다.
MyThread5 : 0
MyThread5 : 1
MyThread5 : 2
MyThread5 : 3
MyThread5 : 4
Thread가 종료되었습니다.
*/
8. 쓰레드와 상태제어 (wait, notify)
wait와 notify는 동기화된 블록 안에서 사용해야 한다.
wait를 만나게 되면 해당 쓰레드는 해당 객체의 모니터링락에 대한 권한을 가지고 있다면 모니터링 락의 권한을 놓고 대기한다.
notify를 만나게 되면 대기하고 있는 쓰레드가 깨어난다.
Thread를 상속받는 ThreadB 클래스
public class ThreadB extends Thread{
/*
해당 쓰레드가 실행되면 자기 자신의 모니터링 락을 획득
5번 반복하면서 0.5초씩 쉬면서 total에 값을 누적
그후에 notify()메소드를 호출하여 wait하고 있는 쓰레드를 깨움
*/
int total;
@Override
public void run(){
synchronized(this){
for(int i=0; i<5 ; i++){
System.out.println(i + "를 더합니다.");
total += i;
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
notify();
}
}
}
ThreadB를 사용하며 wait하는 클래스
public class ThreadA {
public static void main(String[] args){
// 앞에서 만든 쓰레드 B를 만든 후 start
// 해당 쓰레드가 실행되면, 해당 쓰레드는 run메소드 안에서 자신의 모니터링 락을 획득
ThreadB b = new ThreadB();
b.start();
// b에 대하여 동기화 블럭을 설정
// 만약 main쓰레드가 아래의 블록을 위의 Thread보다 먼저 실행되었다면 wait를 하게 되면서 모니터링 락을 놓고 대기
synchronized(b){
try{
// b.wait()메소드를 호출.
// 메인쓰레드는 정지
// ThreadB가 5번 값을 더한 후 notify를 호출하게 되면 wait에서 깨어남
System.out.println("b가 완료될때까지 기다립니다.");
b.wait();
}catch(InterruptedException e){
e.printStackTrace();
}
//깨어난 후 결과를 출력
System.out.println("Total is: " + b.total);
}
}
}
실행결과
/*
b가 완료될때까지 기다립니다.
0를 더합니다.
1를 더합니다.
2를 더합니다.
3를 더합니다.
4를 더합니다.
*/
9. 데몬 쓰레드
데몬(Daemon)이란 보통 리눅스와 같은 유닉스 계열의 운영체제에서 백그라운드로 동작하는 프로그램
데몬 쓰레드란 자바에서 데몬과 유사하게 동작하는 쓰레드
- 데몬쓰레드를 만드는 방법은 쓰레드에 데몬 설정을 하면 된다. 이런 쓰레드는 자바 프로그램을 만들 때 백그라운드에서 특별한 작업을 처리하게 하는 용도로 만든다.
- 데몬쓰레드는 일반 쓰레드(main 등)가 모두 종료되면 강제적으로 종료된다.
// Runnable을 구현하는 DaemonThread클래스를 작성
public class DaemonThread implements Runnable {
// 무한루프안에서 0.5초씩 쉬면서 데몬쓰레드가 실행중입니다를 출력하도록 run()메소드를 작성
@Override
public void run() {
while (true) {
System.out.println("데몬 쓰레드가 실행중입니다.");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
break; //Exception발생시 while 문 빠찌도록
}
}
}
public static void main(String[] args) {
// Runnable을 구현하는 DaemonThread를 실행하기 위하여 Thread 생성
Thread th = new Thread(new DaemonThread());
// 데몬쓰레드로 설정
th.setDaemon(true);
// 쓰레드를 실행
th.start();
// 메인 쓰레드가 1초뒤에 종료되도록 설정.
// 데몬쓰레드는 다른 쓰레드가 모두 종료되면 자동종료.
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("메인 쓰레드가 종료됩니다. ");
}
}
'JAVA > [프로그래머스] 자바 강의' 카테고리의 다른 글
[프로그래머스] 자바 중급 강의 part8. 람다 (0) | 2023.02.16 |
---|---|
[프로그래머스] 자바 중급 강의 part6. 어노테이션 (0) | 2023.02.08 |
[프로그래머스] 자바 중급 강의 part5. IO (0) | 2023.02.07 |
[프로그래머스] 자바 중급 강의 part4. 날짜와 시간 (1) | 2023.02.01 |
[프로그래머스] 자바 중급 강의 part3. java.util 패키지 (1) | 2023.02.01 |