지금까지 하나의 내용을 처리만 했는데 한번에 여러개를 처리하고 싶을땐 어떻게 해야할까요?
바로 오늘 배우는 스레드를 알게된다면 조금은 알게 될 것 같아요~
스레드를 배우기 전에 프로그램, 프로세스부터 알아볼까요?
<프로그램 vs 프로세스 vs 스레드>
프로그램
- 아직 실행되지 않은 상태
- 소스코드로 잘 짜여진 틀, 명령어의 집합
프로세스
- 실행된 프로그램
- 운영체제로부터 시스템 자원을 할당받는 작업의 단위
- JAVA는 운영체제가 바로 실행시켜주지 않고 JVM에 의해 실행되기 때문에 JVM으로부터 시스템 자원을 할당받는다.
(자바는 이식성이 좋기 때문에 운영체제에 맞게 유동적으로 돌아갈 수 있도록 해놓음)
스레드
- 프로세스 처리 경로
- 전적으로 JVM에 의해 스케줄링 된다.
<단일스레드와 멀티스레드>
- 단일 스레드
처리 경로를 한 개만 가지고 있기 때문에 직렬적이다.
동시에 많은 양을 처리하기 힘들기 때문에 상대적으로 비효율적이다.
하지만 하나의 작업에 문제가 발생하더라고 다른 작업에는 영향을 끼치지 않으며, 안정성이 보장된다.
설계도 멀티 스레드에 비해 쉽다.
- 멀티 스레드
하나의 프로세스를 동시에 처리하는 것처럼 보이지만 사실은 매우 짧은 단위로 분할해서 차례로 처리한다.
여러개의 처리 경로를 가질 수 있도록 하며, 동시 작업이 가능해진다.
설계하기 굉장히 어려우며, 하나의 쓰레드 문제 발생 시 모든 스레드에 문제가 발생하게 된다.
java 웹 개발 시 사용되는 웹서버가 대표적인 멀티 쓰레드이다.
멀티 스레드로 설계했다면, 처리량 증가, 효율성 증가, 처리비용 감소의 장점이 있기 때문에
단점을 감수하고 설계하는 편이다.
<교착상태>
멀티스레드의 골칫덩어리 - 교착상태(DeadLock)
멀티 스레드 중 스레드 간에 대기 상태가 종료되지 않아서 무한정 대기만 하는 비정산적인 상태
교착생태인지를 판단했다면 전체 스레드를 깨워주거나 하나의 스레드를 종료시켜주면 교착상태가 해결된다.
< 멀티 스레드 구현 방법>
1. Thread 클래스 상속
2. Runnable 인터페이스 지정
★Point! 여기서의 핵심은 1. run메소드의 재정의, 2. 상속은 하나만 받을 수 있는데 클래스로 상속을 받을 것인지!
// Thread 클래스 상속하여 멀티스레드 구현
public class Thread1 extends Thread{
String data;
public Thread1() {;}
public Thread1(String data) {
super();
this.data = data;
}
//Thread 클래스의 run메소드를 오버라이딩하여 기능을 넣어준다.
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(data);
// sleep 시간동안 인터럽트가 일어난다.
// 그 인터럽트에 대한 오류를 처리하기 위해 try - catch문을 사용한다.
try {
// sleep(마이크로초) : 1초는 1000으로 기준!
sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
// Runnable 인터페이스로 멀티스레드 구현
public class Thread2 implements Runnable{
// Runnable의 run메소드를 재정의하여 기능 구현
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName());
// 스레드의 sleep상태에서 인터럽트가 일어나기 때문에
// Thread로 구현했을때와같이 try - catch문을 사용하여 오류를 제어한다.
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
<동기화>
하나의 스레드가 실행 중일 때 다른 쓰레드가 동시에 같은 필드를 사용하지 못하게 되는것으로, 각 쓰레드를 제어할 수 있게 됩니다.
멀티스레드 구현 시 자원의 특정 부분만 하나씩 처리하고 싶은때 사용합니다. 선언방식은
1. synchronized(mutex){...}로 메소드처럼 만들어주어 일부 소스코드만 동기화를 걸어줍니다..
2. synchronized 키워드를 사용한 메소드에 걸어주어 메소드 전체에 동기화를 걸어줍니다.
public class ATM implements Runnable{
int money= 10000;
// Runnable의 run메소드 오버라이딩
@Override
public void run() {
for (int i = 0; i < 5; i++) {
withdraw(1000);
try {Thread.sleep(500);} catch (InterruptedException e) {;}
}
}
// 일부 소스만 동기화시킬때 사용하는 메소드
synchronized void use() {
// 일부 소스를 동기화 할때
// 이부분(임계영역)에 코드를 작성하여 동기화를 해준다.
}
// 일반 메소드에 동기화 키워드를 걸어 동기화를 걸어준다.
public synchronized void withdraw(int money) {
synchronized (this) {
this.money -= money;
}
System.out.println();
System.out.println(Thread.currentThread().getName() + "이(가) " + money + "원 출금");
System.out.println("현재 잔액 : " + this.money + "원");
}
}
// Gs25에서 엄마와 아들이 동시에 하나의 계좌에서 돈을 꺼내기!
public class Gs25 {
public static void main(String[] args) {
// ATM 객체 생성
ATM atm = new ATM();
// ATM의 객체를 공유한다.
Thread mom = new Thread(atm,"엄마");
Thread sun = new Thread(atm,"아들");
// thread의 run에서 사용하는 withdraw메소드에 싱크를 걸어주었기 때문에
// start를 걸어도 동시에 사용되지 않고, 하나씩 사용하게 된다.
mom.start();
sun.start();
}
}
+) thread는 main이 끝나도 따로 작동하기 때문에 안에서 끝나는 것이 아니면 프로그램이 끝날 때까지 꺼지지 않는다.
+) thread의 상태제어
1. join : 우선순위를 줄때는 join으로 실행한다.
Thread t1 = new Thread();
Thread t2 = new Thread();
t1.start();
t1.join();
t2.start();
// 라고 되어있으면 t1이 우선순위가 높기 때문에 t1을 먼저 실행완료 시키고 t2가 실행된다.
/*
* 주의사항!
* 모두를 start를 걸거 join을 건다면 작동속도가 빠르기 때문에
* join이 걸리기 전 thread가 완료 될수있다.
*
* 그러므로 join이 필요하다면
* thread start()를 사용후 바로 join()걸어주고 다른 스레드 작업을 해야한다.
*/
// 입력의 예시는 1 3 2 순으로 입력함을 알고 코드를 보시기 바랍니다.
// 스레드 기본 기능 선언
public class ThreadTask implements Runnable{
모든 TreadTask의 타입은 cnt를 공유한다.
public static int cnt;
// 기본생성자
// ;를 삽입한 이유는 일부러 비워 놓았다는 의미 (개발자들끼리 알아보기 위한 신호!)
public ThreadTask() {;}
public void printFirtst(Runnable first) {
first.run();
}
public void printSecond(Runnable second) {
second.run();
}
public void printThird(Runnable thrid) {
thrid.run();
}
// switch는 thread의 이름에 따라 작동 방법을 정한다.
// 들어온 순서의 thread이름은 1, 3, 2순서이고, cnt는 모두 공유하는 static 타입으로
// 1번 스레드 first, 2번 스레드 third, 3번 스레드 second라고 출력된다.
// run메소드 오버라이딩
@Override
public void run() {
String name = Thread.currentThread().getName();
switch(name) {
case "1":
// Runnable 인터페이스 람다식 사용
// printFirst(Runnable Type)에서 Runnable은 @FunctionalInterface이므로
// 하나의 추상메소드(run메소드)만 가지고 있으므로 람다식 사용이 가능하다.
// 고로 () -> ~~~ 이것으로 runnable의 run메소드를 오버라이딩 할수 있다.
printFirtst(()->System.out.println(++cnt +"번 쓰레드 first"));
break;
case "2":
printSecond(()->System.out.println(++cnt +"번 쓰레드 second"));
break;
case "3":
printThird(()->System.out.println(++cnt +"번 쓰레드 third"));
break;
}
}
}
// 실행 메소드
import java.util.Scanner;
public class ThreadMain {
public static void main(String[] args) {
//
Scanner sc = new Scanner(System.in);
int[] arInput = new int[3];
Thread[] arThread = new Thread[3];
ThreadTask tt = new ThreadTask();
for (int i = 0; i < arThread.length; i++) {
arThread[i] = new Thread(tt);
}
// 몇번스레드를 먼저 실행시킬지 순서를 정해준다.
// 예) 1 3 2
System.out.println("입력 : ");
for (int i = 0; i < arInput.length; i++) {
arInput[i]= sc.nextInt();
// 순서를 정해주고, 그 순서에 이름을 입력한 순서의 이름으로 지정해준다.
// arThread[0]의 이름은 1
// arThread[1]의 이름은 3
// arThread[2]의 이름은 2
arThread[i].setName(arInput[i] + "");
}
for (int i = 0; i < arThread.length; i++) {
// 이름이 1인 스레드, 3인 스레드, 2인 스레드 순으로 작동한다.
arThread[i].start();
// 시작 순서대로 join을 걸어준다.
// 동시에 start를 해도 join이 걸려있기 때문에
// 시작 순서대로 스레드가 작동한다.
try {arThread[i].join();} catch (InterruptedException e) {; }
}
}
}
2. wait : 스레드를 Runnable 상태에서 waiting 상태로 변경
※ Point! 모든 스레드가 wait걸리면 작동에 문제가 생기기 때문에 문제가 생긴다. (교착상태)
-> notify/notifyAll로 스레드를 모두 깨워준다.
3. notify / notifyAll : (특정/모든) 스레드를 깨운다
( 바로 실행이 아닌 실행중인 스레드가 있을수 있기 때문에 blocked 상태로 만들어주고 현재 작동중인 스레드가 끝나면 runnable상태로 변경된다.)
+) 스레드 종료 방법
1. 필드에 boolean타입의 변수를 선언하고 run안에 있는 반복문에 해당 변수가 true일때 break하도록 설계
2. sleep()을 사용한다면 일시정지한 상태에서 InterruptedException을 발생시켜줌으로써 예외처리로 종료
※ 스레드객체.interrupt()를 사용하면 일시정지 상태에서 예외 발생
3. wait(), join을 사용한 로직이라면, sleep()을 사용한 후 종료를 진행해야하고,
만약 위의 메소드를 사용하지 않는 로직이라면, sleep()을 사용하지 않아도 종료가 가능하다.
※ 스레드객체.interrypt()를 사용하면 Thread.interrupted()의 상태가 true로 변경된다.
'프로그래밍 공부 > Java' 카테고리의 다른 글
[lesson] Java 프로그래밍 언어 - JDBC (0) | 2021.08.22 |
---|---|
[lesson] Java 프로그래밍 언어 - 파일 입출력 (0) | 2021.08.11 |
[lesson] Java 프로그래밍 언어 - Collection (0) | 2021.08.04 |
[lesson] Java 프로그래밍 언어 - Object , Wrapper Class (0) | 2021.07.30 |
[lesson] Java 프로그래밍 언어 - API (0) | 2021.07.30 |