자바 쓰레드(Thread)

웅평·2023년 12월 14일

쓰레드

프로그램(ex) 계산기)을 실행하고 실행되고 있는 상태를 프로세스라고 표현한다
프로세스의 자원을 이용해서 작업을 수햇하는 것을 쓰레드 라고한다
하나의 프로세스는 복수의 쓰레드를 가질 수 있다.
따로 처리하지 않으면 일반적으로 하나의 프로세스는 하나의 쓰레드를 갖는다

  • 동시에 여러 작업을 하기위해서 쓰레드를 사용
  • 쓰레드를 쓰기 위해서 쓰레드 클래스 상속해야한다
  • run이라는 메소드를 정의해주면 된다

쓰레드 작성

class 클래스이름 extends Thread{
	public void run(){
		동시다발로 동작시키고자 하는 내용
	}

Thread를 확장하여 새로운 클래스를 만들고
run메소드를 오버라이딩하여
동시다발로 동작시키고자 하는 내용을 써 준다.

public class CleanThread extends Thread{
    public void run() {
        System.out.println("직원 청소 시작 Thread");
        for (int i = 2; i <= 10; i += 2) {
            System.out.println(i + "번방 청소중 Thread");
        }
        System.out.println("직원 청소 끝 Thread");

    }
}
public class _01_Thread {
    public static void main(String[] args) {
        // 쓰레드 생성
        CleanThread cleanThread = new CleanThread();
        // run 메소드를 직접 호출해서 Thread안에있는 run 메소드를 수행하는것 뿐이다
        // cleanThread.run();
        cleanThread.start();
        cleanByBoss();
    }

    public static void cleanByBoss() {
        System.out.println("사장 청소 시작");
        for (int i = 1; i <= 10; i += 2) {
            System.out.println(i + "번방 사장 청소중");
        }
        System.out.println("사장 청소 끝");
    }
}
  • 쓰레드를 사용하려면 run이 아니라 start를 써야한다

    출력문이 차례대로 나오지 않고 섞었다. - 쓰레드를 사용했기때문에 동시에 여러일을 처리함

Runnable

  • 쓰레드와 비슷
  • 인터페이스로 상속
  • 둘의 차이 쓰레드는 쓰레드 클래스를 상속, Runnable은 인터페이스를 구현
  • 상속은 하나만 상속가능, 복수 상속이 안되기 때문에 쓰레드 대신 Runnable 를 사용
  • Runnable 인터페스를 구현한 객체는 바로 start를 호출할 수 없고 Thread 객체로 포장한 후 start를 호출 해야한다
public class CleanRunnable implements Runnable{
    @Override
    public void run() {
        System.out.println("직원 청소 시작 Runnable");
        for (int i = 2; i <= 10; i += 2) {
            System.out.println(i + "번방 청소중 Runnable");
            try {
                // 지정한 시간만큼 정지한다
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        System.out.println("직원 청소 끝 Runnable");
    }
    
    
public class _02_Runnable {
    public static void main(String[] args) {
        CleanRunnable cleanRunnable = new CleanRunnable();
        Thread thread = new Thread(cleanRunnable);
        thread.start();
        cleanByBoss();
    }

    public static void cleanByBoss() {
        System.out.println("사장 청소 시작");
        for (int i = 1; i <= 10; i += 2) {
            System.out.println(i + "번방 사장 청소중");
        }
        System.out.println("사장 청소 끝");
    }
}

thread를 상속할 때와 비슷한 기능을 한다

Join

  • join은 이 코드가 끝날때까지 기다렸다가 다음으로 넘어간다
public class _03_Join {
    public static void main(String[] args) {
        CleanRunnable cleanRunnable = new CleanRunnable();
        Thread thread = new Thread(cleanRunnable);
        thread.start();

        try {
            // join은 이 코드가 끝날때까지 기다렸다가 다음으로 넘어간다
            thread.join(2500); // 2.5초 대기
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        cleanByBoss();
    }

    public static void cleanByBoss() {
        System.out.println("사장 청소 시작");
        for (int i = 1; i <= 10; i += 2) {
            System.out.println(i + "번방 사장 청소중");
            try {
                // 지정한 시간만큼 정지한다
                Thread.sleep(1000); // 1초 대기
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        System.out.println("사장 청소 끝");
    }
}

결과

멀티 스레드

만약, 두개 이상의 객체를 가능하면 공평하게(동시다발) 실행 되게끔 즉, 한번에 여러개의 메소드가 동시에 실행되는 것을 "멀티 쓰레드"라고 하고 이것을 위하여 자바에서는 Thread 클래스와 Runnable 인터페이스를 이용한다

  • 한번에 여러개의 메소드가 동시다발로 동작하는 것을 말한다.

자바에서는 멀티쓰레드 프로그래밍을 위하여 Thread라는 클래스와 Runnable이라는
인터페이스를 이용한다.

Thread를 가동시키면가능하면 공평하게 실행이 되게끔 스케줄링 해준다.
그러나, 만약 응급성이 있는 객체라면 가능하면 다른 객체들 보다 더 빨리 작업을 수행하도록
우선순위를 설정 할 수 있다

setPriority(newPriority);

  • newPriority : 1~10
  • MAX_PRIORITY : 10
  • MIN_PRIORITY : 1
  • NORM_PRIORITY : 5
class Person extends Thread{
	String name;
	public Person(String name) {
		this.name = name;
	}
	
	public void run() {
		for(int i=1; i<=10; i++) {
			System.out.println(name+"가(이) 밥을 먹어요~");
			try {
				Thread.sleep(100);
			}catch (Exception e) {
				// TODO: handle exception
			}
		}
	}
    
public class ThreadTest {
	public static void main(String[] args) {
		Person p1 = new Person("이문관");
		Person p2 = new Person("김지훈");
		Person p3 = new Person("김범진");
		
		//최고 우선순위를 설정
//		p1.setPriority(10);
		p1.setPriority(Thread.MAX_PRIORITY);
		
		p1.start();
		p2.start();
		p3.start();

	}

}

우선순위를 설정했더라도 항상 먼저 끝내는 것이 아니다


임계영역(Critical Section)

  • 두개 이상의 쓰레드가 어떠한 자원을 공유하고 있을 때 한번에 하나의 쓰레드에게만
    접근을 허용해야하는 영역을 "임계영역"이라고 한다
  • 자바에서는 그러한 영역에 접근을 하는 메소드 이름 왼쪽에(앞에) "synchronized" 키워드를 붙이면 자동으로 "임계영역"으로 설정된다
  • 즉 한번에 하나의 쓰레드에게만 접근을 허용하게 된다
  • 한번에 하나라도 먼저 쓰레드가 접근하면 락을 걸어서 다른 쓰레드가 접근을 못하도록 한번에 하나의 쓰레드만 접근하도록 해준다
public class Room {
    public int number = 1;

    public void clean(String name) {
        System.out.println(name + " : " + number + "번방 청소 중");
        number++;
    }
}

public class _05_Synchronization {
    public static void main(String[] args) {
        // 동기화
        // 여러 쓰레드에서 하나의 변수에 접근해서 업데이트하면 중복될 수 있다
        // 다른 쓰레드에서 오류가나도 다른 쓰레드도 문제가 있는게 아니다
        Room room = new Room();

        Runnable cleaner1 = new Runnable() {
            @Override
            public void run() {
                System.out.println("직원1 청소 시작");
                for (int i = 1; i <= 5; i++) {
                    room.clean("직원1");

                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
                System.out.println("직원1 청소 끝");
            }
        };

        Runnable cleaner2 = () -> {
            System.out.println("직원2 청소 시작");
            for (int i = 1; i <= 5; i++) {
                room.clean("직원2");

                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            System.out.println("직원2 청소 끝");
        };

        Thread cleaner1Thread = new Thread(cleaner1);
        Thread cleaner2Thread = new Thread(cleaner2);
        cleaner1Thread.start();
        cleaner2Thread.start();
    }
}

1번방부터 10번방 까지 차례대로 청소하는 메시지를 출력하고 싶은데 멀티 쓰레드를 사용하니까
동시에 하나의 clean메소드에 접근해서 일부 방번호가 겹치게된다

이러한 문제를 해결하기 위해서 동기화가 필요하다

public class Room {
    public int number = 1;

    // synchronized 를 추가하면 동기화
    synchronized public void clean(String name) {
        System.out.println(name + " : " + number + "번방 청소 중");
        number++;
    }
}

synchronized를 붙이면 이미 누군가 이 메소드를 사용중이라면 다른 사람은 이 메소드가 끝날때까지 접근이 불가능하다

쓰레드사이의 통신

  • 두개 이상의 쓰레드를 가동시키면 가능하면 공평하게 실행된다 반드시 1 : 1로 동작하게 하려면 쓰레드사이에 통신을 이용할 수 있다
  • 임계영역에 힌번에 하나의 쓰레드만 접근할 수 있으므로 다른 쓰레드가 일을 마칠때까지 대기하고 있다가 그 쓰레드가 일이 끝나면 대기중인 쓰레드를 깨워주고 하기 위해서는 쓰레드 사이에 통신이 필요하다
  • 쓰레드의 통신을 위하여 Object클래스의 wait과 notify를 이용한다

wait

  • 다른 쓰레드가 동작을 마칠떄까지 내가 기다리는 메소드
    notify
  • 내가 동작을 마친 후 대기중인 다른 쓰레드를 깨워주는 메소드

참고
나도코딩 유튜브

0개의 댓글