어떤 자바 어플리케이션이든 메인 스레드는 반드시 존재
메인 작업 이외에 추가적인 병렬 작업의 수만큼 스레드를 생성하면 된다.
java.lang.Thread 클래스를 직접 객체화하거나, Thread 클래스를 상속하여 하위 클래스를 만들어 생성한다.
java.lang.Thread 클래스로부터 작업 스레드 객체를 직접 생성
= Runnable을 매개값으로 갖는 생성자를 호출
Thread thread = new Thread(Runnalbe target);
class Task implements Runnalbe{
public void run(){
스레드가 실행할 코드;
}
}
단, 이때까지 Runnable은 작업 내용을 가지고 있는 객체일 뿐 실제 스레드가 아니다.
Runnable 구현 객체를 생성한 후, 이것을 매개값으로 하여 Thread 생성자를 호출하면 비로소 작업 스레드 생성!
Runnable task = new Task(); // 객체만 만들어짐 (스레드가 생성된 건 X)
Thread thread = new Thread(task); // 이 시점에 스레드 생성 (실행되진 X)
thread.start() // 이 시점에 스레드 실행
ex) 비프음을 들려주며, '띵'을 문자열로 출력하기
1. main스레드만 이용main 스레드의 코드는 위에서부터 아래로 읽히므로, 첫번째 beep() 가 실행되는 스레드를 따라서 비프음이 5번 들린 후에, 이어서 "띵"이 5번 출력된다 (동시 진행 불가능)
2. 프린팅은 메인스레드 / 비프음은 작업스레드
🔻비프음을 낼 작업스레드를 따로 만들기 위한 클래스 (BeppTask.java)🔻메인 클래스 (BeepTask 객체를 생성해서, 스레드 생성)
![]()
🔻위와 같은 방법이지만, 클래스를 따로 생성하지 않고, 람다식 이용
Thread의 하위클래스로 작업스레드를 정의하면서 작업 내용을 포함시킴다.
: Thread 클래스를 상속한 후 run 메소드를 재정의(overriding)해서 스레드가 실행할 코드를 작성
pulbic class WorkerThread extends Thread{
@Override
public void run(){
//스레드가 실행할 코드
}
}
람다식으로 표현 가능하다 (위의 예제)
Thread thread = new Thread(){
public void run(){
// 스레드가 표현할 코드
}
}
🔻Thread를 상속받는 클래스 생성(BeeaThread.java)
🔻메인에서 BeepThread 호출
멀티스레드는 동시성 또는 병렬성으로 실행 된다.
동시성 : 멀티 작업을 위해 하나의 코어에서 멀티 스레드가 번갈아가며 실행하는 성질
병렬성 : 멀티 작업을 위해 멀티 코어에서 개별 스레드를 동시에 실행하는 성질
스레드의 개수가, 코어의 개수보다 많을 경우 스레드를 어떤 순서에 의해 동시성으로 실행할 것인가를 결정해야한다.
= 스레드 스케줄링
우선순위가 높은 스레드가 실행 상태를 더 많이 가지도록 스케줄링
개발자가 코드로 제어 가능 (개발자가 우선순위 코드를 부여 : thread.setPriority(index)
)
시간할당량을 정하여 하나의 스레드를 정해진 시간만큼 실행하고, 다른 스레드를 실행하는 방식
자바 가상 기계에 의해서 정해지므로 코드로 제어 불가능
👉이렇게 스레드가 사용중인 객체를 다른 스레드가 변경할 수 없도록 하기 위해서, 스레드 작업이 끝날 때까지 객체에 잠금을 걸어서 다른 스레드가 사용할 수 없도록 해야한다.
public void setMemory(int memory){
this.memory = memory;
try{
Thread.sleep(2000);
}catch(Exception e){}
}
에서
public synchronized void setMemory(int memroy){
this.memory = memory;
try{
Thread.sleep(2000);
}catch(Exception e){}
}
이렇게 바꿔줄 수 있다.
상태 | 열거상수 | 설명 |
---|---|---|
객체생성 | NEW | 스레드 객체 생성 / 아직 start() 메소드가 호출되지 않은 상태 |
실행대기 | RUNNABLE | 실행 상태로 언제든지 갈 수 있는 상태 |
일시정지 | WAITING | 다른 스레드가 통지할 때까지 기다리는 상태 |
TIME_WATINING | 주어진 시간 동안 기다리는 상태 | |
BLOCKED | 사용하고자 하는 객체의 락이 풀릴 때까지 기다리는 상태 | |
종료 | TERMINATED | 실행을 마친 상태 |
메소드 | 설명 |
---|---|
interrupt() | 일시정지 상태의 스레드에서 InterruptedExecption 예외를 발생시켜, 예외 처리 코드(catch)에서 실행 대기 상태로 가거나 종료 상태로 갈 수 있도록 한다. |
notify() | 동기화 블록 내에서 wait() 메소드에 의해 일시 정지 상태에 있는 스레드를 실행 대기 상태로 만든다. |
notifyAll() | |
sleep() | 주어진 시간동안 스레드를 일시 정지 상태로 만든다. 주어진 시간이 지나면 자동적으로 실행대기상태가 된다. |
join() | join() 메소드를 호출한 스레드는 일시정지 상태가 된다. 실행 대기 상태로 가려면, join() 메소드를 멤버로 가지는 스레드가 종료되거나, 매개값으로 주어진 시간이 지나야한다. |
wait | 동기화(Synchronized) 블록 내에서 스레드를 일시 정지 상태로 만든다. 매개값으로 주어진 시간이 지나면 자동적으로 실행 대기 상태가 된다. 시간이 주어지지않으면, notify(), nofifyAll() 메소드에 의해 실행 대기 상태로 갈 수 있다. |
public class SleepExample {
public static void main(String[] args) {
Toolkit toolkit = Toolkit.getDefaultToolkit();
for(int i=0; i<10; i++){
toolkit.beep();
try{
Thread.sleep(3000);
}catch (InterruptedException e){}
}
}
}
try{
Thread.sleep(3000);
}catch (InterruptedException e){}
: 메인 스레드를 3초동안 일시 정지 상태로 보내고, 3초가 지나면 다시 실행 준비 상태가 돌아오도록 함
yield() 메소드를 호출한 스레드는 실행 대기 상태로 돌아가고, 동일한 우선순위 또는 높은 우선순위를 갖는 다른 스레드가 실행 기회를 가질수 있도록 함
//ThreadA.java
public class ThreadA extends Thread {
public boolean stop = false; //종료 플래그
public boolean work = true; //작업 진행 여부
public void run() {
while (!stop) {
if (work) {
System.out.println("ThreadA 작업 내용");
} else {
Thread.yield();
}
}
System.out.println("ThreadA 종료");
}
}
//ThreadB.java
public class ThreadB extends Thread {
public boolean stop = false; //종료 플래그
public boolean work = true; //작업 진행 여부
public void run() {
while (!stop) {
if (work) {
System.out.println("ThreadB 작업 내용");
} else {
Thread.yield();
}
}
System.out.println("ThreadB 종료");
}
}
//YieldExample.java
public class YieldExample {
public static void main(String[] args) {
ThreadA threadA = new ThreadA();
ThreadB threadB = new ThreadB();
threadA.start();
threadB.start();
try{Thread.sleep(3000);} catch (InterruptedException e){}
threadA.work =false;
try{Thread.sleep(3000);} catch (InterruptedException e){}
threadA.work = true;
try{Thread.sleep(3000);} catch (InterruptedException e){}
threadA.stop =true;
threadB.stop =true;
}
}
스레드는 다른 스레드와 독립적으로 실행하는 것이 기본!
다만, 계산 작업을 하는 스레드가 모든 계산 작업을 마쳤을 때, 계산 결과값을 받아 이용해야하는 경우 등 다른 스레드가 종료될때까지 기다렸다가 실행해야 하는 경우가 발생할 수도 있음.
//SumThread.java
public class SumThread extends Thread {
private long sum;
public long getSum() {return sum;}
public void setSum(long sum) {this.sum = sum;}
public void run() {
for (int i = 1; i <= 100; i++) {
sum += 1;
}
}
}
//JoinExample.java
public class JoinExample {
public static void main(String[] args) {
SumThread sumThread = new SumThread();
sumThread.start();
try {
sumThread.join(); // sumThread가 종료할 때 까지 메인 스레드를 일시정지
} catch (InterruptedException e) {
}
System.out.println("1~100의 합:" + sumThread.getSum());
}
}
👉동기화 메소드 또는 블록에서만 호출 가능한 Object 메소드
//ThreadA,B의 공유객체 WorkObject
public class WorkObejct {
public synchronized void methodA(){
System.out.println("ThreadA의 methodA() 작업 실행");
notify(); //일시 정지 상태에 있는 ThreadB를 실행 대기 상태로 만듦
try{
wait(); // ThreadA를 일시정지 상태로 만듬
}catch(InterruptedException e){}
}
public synchronized void methodB(){
System.out.println("ThreadB의 methodB() 작업 실행");
notify(); //일시 정지 상태에 있는 ThreadA를 실행 대기 상태로 만듦
try{
wait(); //공유 객체의 methodA()를 10번 반복 호출
}catch(InterruptedException e){}
}
}
//ThreadA
public class ThreadA extends Thread{
private WorkObejct workObejct;
public ThreadA(WorkObejct workObejct){
this.workObejct = workObejct; // 공유 객체를 매개값으로 받아 필드에 저장
}
@Override
public void run(){
for(int i=0; i<1; i++){
workObejct.methodA(); //공유 객체의 methodA()를 10번 반복 호출
}
}
}
//ThreadB
package ThisIsJava.chap12.section6;
public class ThreadB extends Thread{
private WorkObejct workObejct;
public ThreadB(WorkObejct workObejct){
this.workObejct = workObejct; // 공유 객체를 매개값으로 받아 필드에 저장
}
@Override
public void run(){
for(int i=0; i<1; i++){
workObejct.methodB(); //공유 객체의 methodA()를 10번 반복 호출
}
}
}
//스레드 실행 메소드
public class WaitNotifyExample {
public static void main(String[] args) {
WorkObejct shareObject = new WorkObejct(); //공육객체생성
ThreadA threadA = new ThreadA(shareObject);
ThreadB threadB = new ThreadB(shareObject); //ThreadA,B 생성
threadA.start();
threadB.start();
}
}
👉 실행 중인 스레드를 즉시 종료할 필요가 있을 때
interrupt()메소드는 스레드가 일시 정지 상태에 있을 때 InterruptedException 예외를 발생시키는 역할을 한다. 자체로 정지의 의미를 가지는 게 아니기 때문에, 예외를 발생 시킨 뒤, catch문에서 조절할 수 있음