Thread 클래스와 Runnable 인터페이스
1) Thread
Thread : 프로그램 실행의 가장 작은 단위
이를 사용하는 방법으로는 Thread 클래스를 상속받아 사용하기 , Runnable 인터페이스를 Thread 생성자의 매개변수로 넣어 주는 방법이 있음
-> sleep 메소드 : 현재 스레드 멈추기 , 자원을 놓아주지는 않고 제어권을 넘겨주므로 데드락이 발생할 수 있음
-> interrupt 메소드 : 다른 스레드를 깨워서 interruptException 발생시킴 , interrupt가 발생한 스레드는 예외를 catch 하여 다른 작업 가능
-> join 메소드 : 다른 스레드 작업이 끝날 때까지 기다리게 함 , 스레드 순서 제어시 사용
//Thread 상속 받아 구현
class exam extends Thread{
public void run() {
System.out.println("RUNNING~~");
}
}
public class Main {
public static void main(String[] args) {
Thread ex = new exam();
ex.start();
}
}
//Runnalbe 인터페이스를 구현하여 인자로 전달
class exam02 implements Runnable {
public void run() {
System.out.println("RUNNING");
}
}
public class Main {
public static void main(String[] args) {
Runnable ex = new exam02();
Thread exthread = new Thread(ex);
exthread.start();
}
}
2) Runnable
Runnable : 자바에서 다중 스레드를 구현하기 위한 인터페이스
Runnable 인터페이스를 구현한 객체는 실행 가능한 코드이며 스레드에 의해 실행 가능함
-> run 메소드 : 이를 오버라이드하여 실행 가능한 코드를 정의할 수 있음
이를 사용하면 스레드 클래스와 작업 클래스를 분리 가능, 코드 재사용성과 유연성 향상 가능
@FunctionalInterface
public interface Runnable{
public abstract void run();
}
스레드의 상태
스레드의 상태를 알 수 있도록 하는 메소드 : getState()
객체 생성 : NEW -> 스레드 객체가 생성 , 아직 start() 메소드가 호출되지 않은 상태
실행 대기 : RUNNABLE -> 실행 상태로 언제든지 갈 수 있는 상태
일시 정지 : WAITING -> 다른 스레드가 통지할 때까지 기다리는 상태
TIMED_WAITING -> 주어진 시간동안 기다리는 상태
BLOCKED -> 사용하고자 하는 객체의 락이 풀릴 때까지 기다리는 상태
종료 : TERMINATED -> 실행을 마친 상태
스레드의 우선순위
모든 Thread는 우선순위를 가짐
우선순위가 높은 스레드일 수록 더 많은 리소스 사용하기 위해 시도 -> 즉 우선순위가 높은 스레드는 실행 기회를 많이 갖음 ( BUT 순위가 높다고 자원을 모두 가져가거나 항상 먼저 실행되는 것은 아님)
우선순위가 낮은 스레드 -> cpu 자원을 적게 얻으려고 시도
기본 우선순위는 5임
public static final int MAX_PRIORITY | 가장 높은 순위 , 상수 10 |
public static final int NORM_PRIORITY | 일반적인 순위 , 상수 5 |
public static final int MIN_PRIORITY | 가장 낮은 순위 , 상수 1 |
ex) 우선 순위 스케줄링 실습 코드
class myThread extends Thread{
int num;
public myThread(int num) {
this.num = num;
}
@Override
public void run() {
System.out.println(this.num+" thread start");
for(int i=0;i<10000;i++){
for(int j=0;j<10000;j++){
for(int k=0;k<10000;k++){
}
}
}
System.out.println(this.num+" thread end");
}
}
public class Main {
public static void main(String[] args) {
for(int i=1;i<=5;i++){
myThread thread = new myThread(i);
if(i == 5){
thread.setPriority(Thread.MAX_PRIORITY);
}else{
thread.setPriority(Thread.MIN_PRIORITY);
}
thread.start();
}
}
}
실행 결과 )
위 실행 결과를 보면 5번 스레드의 우선순위를 MAX_PRIORITY를 두고 나머지 스레드는 MIN_PRIORITY로 설정했다.
가장 우선 순위가 높은 스레드가 실행 상태를 더 많이 가져 제일 먼저 종료되는 것을 볼 수 있다.
Main 스레드
자바 프로그램 시작과 동시에 하나의 스레드가 즉시 동작하게 되는 것을 Main 스레드라고 한다.
[속성]
- 메인 스레드로부터 다른 자식 스레드가 발생
- 다양한 종료 작업을 수행하기 때문에 대체로 마지막으로 실행 종료되는 스레드여야 함
동기화
1) Synchronized란?
이는 동기화라는 뜻으로 하나의 프로세스가 진행될 때 다른 프로세스가 간섭하지 못하게 해당 프로세스만을 진행하고 수행하도록 하는 것을 말한다.
- 동기화 하지 않은 코드
class counter {
private int count =0;
public void increment() {
count++;
}
public int getCount() {
return count;
}
}
public class Main {
public static void main(String[] args) {
counter c = new counter();
Thread t1 = new Thread(()-> {
for (int i =0;i<10000;i++){
c.increment();
}
});
Thread t2 = new Thread(() -> {
for(int i =0;i<10000;i++){
c.increment();
}
});
t1.start();
t2.start();
try{
t1.join();
t2.join();
} catch(InterruptedException e ){
e.printStackTrace();
}
System.out.println("fina count: "+c.getCount());
}
}
위 코드는 동기화를 하지 않은 코드로 실행 시켰을 때 increment() 메소드에서 경쟁 상태가 발생할 수 있다.
여러 스레드가 동시에 count++를 수행하면 원자성이 깨지고 최종 값이 예상보다 작아질 수 있다.
- 동기화 한 코드
class counter {
private int count =0;
public synchronized void increment(){
count++;
}
public synchronized int getCount() {
return count;
}
}
public class Main {
public static void main(String[] args) {
counter c = new counter();
Thread t1 = new Thread(()-> {
for (int i =0; i< 10000;i++){
c.increment();
}
});
Thread t2 = new Thread(() -> {
for (int i =0;i<10000;i++){
c.increment();
}
});
t1.start();
t2.start();
try{
t1.join();
t2.join();
}catch(InterruptedException e) {
e.printStackTrace();
}
System.out.println("final count : "+c.getCount());
}
}
위 코드를 실행 시키면 두 개의 스레드가 increment()를 호출 할 떄 한 번에 하나의 스레드만 접근 가능하도록 되어 final count의 값이 항상 20000이 나오는 것을 볼 수 있다.
데드락
두 개 이상의 스레드가 서로 상대방의 리소스를 기다리면서 무한 대기에 빠지는 상태를 말한다.
즉 서로가 서로를 기다리면서 실행이 멈춰버리는 상황을 말한다.
[발생 조건]
1. 상호 배제 (mutual exclusion)
하나의 리소스는 한 번에 한 개의 스레드만 사용할 수 있다.
2. 점유 및 대기
스레드가 이미 하나의 리소스를 점유한 상태에서 추가 리소스를 요청하며 대기한다.
3. 비선점
다른 스레드가 점유한 리소스를 강제로 빼앗을 수 없다.
4. 순환 대기
두 개 이상의 스레드가 서로의 리소스를 기다리면서 순환 형태의 대기 상태가 된다.
ex)
public class Main {
public static Object object1 = new Object();
public static Object object2 = new Object();
public static void main(String[] args) {
FirstThread thread1 = new FirstThread();
SecondThread thread2 = new SecondThread();
thread1.start();
thread2.start();
}
private static class FirstThread extends Thread{
@Override
public void run() {
synchronized (object1){
System.out.println("First Thread has object1's lock");
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("First Thread want to have object2's lock. so wait");
synchronized (object2){
System.out.println("First Thread has object2's lock too");
}
}
}
}
private static class SecondThread extends Thread{
@Override
public void run() {
synchronized (object2){
System.out.println("Second Thread has object2's lock");
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Second Thread want to have object1's lock, so wait");
synchronized (object1){
System.out.println("Second Thread has object1's lock too");
}
}
}
}
}
[코드 참조]
https://math-coding.tistory.com/175
[해결 방법]
1. 락은 순서를 항상 일정하게 유지
모든 스레드가 lock1을 먼저 획득하고 그 다음 lock2 를 획득하도록 순서 강제하기
2. 타임 아웃
스레드가 락을 획득하기 위해 기다리는 시간을 정해놓기
타임 아웃 시간이 다 되도록 락을 획득하지 못하면 이 스레드는 락을 포기한다
작업 처리하기 위해 락을 오래 잡고 있어도 lock 타임아웃에 걸려 포기하게 한다.
-> java.util.concurrency를 사용함
'🦁아기사자' 카테고리의 다른 글
[Java] 애노테이션 (0) | 2025.03.27 |
---|---|
[Java] Enum (0) | 2025.03.26 |
[Java] 인터페이스 (0) | 2025.03.20 |
[Java] 패키지 (0) | 2025.03.19 |
[Java] 상속 (0) | 2025.03.18 |