본문 바로가기

Java

스레드 thread

프로세스와 스레드

  • 프로세스 : 실행중인 프로그램, 프로그램 하나가 실행되는 단위
  • 스레드 : 하나의 프로세스 안에서 실질적인 작업 처리하는 하나의 단위
  • 프로그램을 수행하는데 필요한 데이터, 메모리등의 자원과 스레드로 구성
  • 프로세스의 자원을 이용해서 실제로 작업을 수행하는 것이 스레드
  • 둘 이상의 스레드를 가진 프로세스를 멀티스레드 프로세스라고 함
  • CPU가 아주 짧은 시간 동안 여러 작업 번갈 아 수행으로 동시 작업 처럼 보임
  • 서버프로그래밍(서블릿)의 겨우 여러개의 스레드를 생성 사용자의 요청 일대일 대응
  • 싱글스레드로 작성 시 프로세스 생성 비용이 많이 듬(시간, 메모리 공간등)
  • 단점으로 여러 스레드가 같은 프로세스의 자원을 공유 함으로 동기화, 교착상태 등 고려 해야함
  • 서블릿의 겨우 WAS(Web Application Server) 관리 해줌
  • 멀티태스킹 : 여러개의 프로세스 동시 실행

스레드의 생성 및 실행

Thread 클래스 상속받아서 스레드 생성

  • 해당 클래스 자체가 스레드가되기 때문에 스레드 생성자를 호출 하여 바로 생성 가능
  • start() 호출 -> 오버라이딩한 run() 자동 호출
  • main() 스레드 와 run() 스레드 2개가 생김
class ThreadExtend extends Thread {
    public void run() {
        System.out.println("Thread 상속받는 방법");
        for (int i=0; i<50; i++) {
            System.out.println("ThreadExtend:"+i);
        }
    }
}

Runnable 인터페이스 구현으로 스레드 정의

  • 다중 상속이 안되므로 이경우가 더 많이 쓰임
  • Runnable 인터페이스에는 run() 만 정의 되어 있음
  • 스레드 객체가 아니므로 스레드 객체에 생성자로 전달 해서 사용
class RunnableImple implements Runnable {
    public void run() {
        System.out.println("Runnable 구현하는 방법");
        for (int i=0; i<50; i++) {
            System.out.println("RunnableImple:"+i);
        }
    }
}

스레스 스타트 start()

  • run() 메서드로 정의 하고 start()로 실행
package chapter17;

public class ThreadEx {

    public static void main(String[] args) {

        // Thread 상속받는 방법
        ThreadExtend t1 = new ThreadExtend();

        // Runnable 구현하는 방법
        Runnable r = new RunnableImple();
        // Thread 생성자의 매개변수로 전달
        Thread t2 = new Thread(r);

        t1.start();
        t2.start();

    }

Runnable 인터페이스 익명 클래스 구현으로 스레드 정의

package chapter17;

public class ThreadEx2 {

    public static void main(String[] args) {

        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("t1 스레드 시작");
                for (int i=0; i<50; i++) {
                    System.out.println("t1 : "+i);
                }
            }
        });

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("t2 스레드 시작");
                for (int i=0; i<50; i++) {
                    System.out.println("t2 : "+i);
                }
            }
        });

        t1.start();
        t2.start();

    }

}

람다표현식으로 스레드 정의

package chapter17;

public class ThreadEx3 {

    public static void main(String[] args) {

        Thread t1 = new Thread(() -> {
            System.out.println("t1 스레드 시작");
            for (int i=0; i<50; i++) {
                System.out.println("t1 : "+i);
            }
        });

        Thread t2 = new Thread(() -> {
            System.out.println("t2 스레드 시작");
            for (int i=0; i<50; i++) {
                System.out.println("t2 : "+i);
            }
        });

        t1.start();
        t2.start();

    }

}

스레드 우선순위

  • 스레드가 여러개 실행할 때는 정확한 순서 제어가 힘듬
  • 스레드 스케줄러에 의해 cpu 사용이 결정됨
  • 정확하게 제어할 수는 없으나 우선순위가 높은 스레드가 CPU를 더 많이 점유
  • 스레드가 수행하는 작업의 중요도에 따라 스레드의 우선순위 지정하여 특정 스레드가 더 많은 작업시간을 갖도록 함
  • setPriority(우선순위값) 메서드 사용
  • 우선순위값 1~7(윈도우는 10), 기본값 5
    • static int Min_PRIORITY : 최하 우선순위
    • static int MAX_PRIORITY : 최대 우선순위
    • static int Norm_PRIORITY : 중간 우선순위(디폴트)
package chapter17;

public class ThreadEx4 {

    public static void main(String[] args) {

        Thread t1 = new Thread(() -> {
            System.out.println("t1 스레드 시작");
            for (int i=0; i<50; i++) {
                System.out.println("t1 : "+i);
            }
        });

        Thread t2 = new Thread(() -> {
            System.out.println("t2 스레드 시작");
            for (int i=0; i<50; i++) {
                System.out.println("t2 : "+i);
            }
        });

        // 우선순위 지정
        t1.setPriority(MAX_PRIORITY ); // t1이 먼저 종료됨
        t2.setPriority(3);

        t1.start();
        t2.start();

    }

}

쓰레드 상태 변화 및 제어

  • run, blocked, dead 세 가지 상태 중 하나의 상태로 머무름
  • crate(쓰레드 생성) -> start() -> runnable(실행 가능상태) -> run() -> running(실행된 상태)
  • running -> sleep(), wait(), yield() -> blocked(실행 중지,대기 상태) -> notify() -> runnable(실행 가능상태)
  • running -> dead (작업 완료, 메모리 삭제)

sleep(n)

  • 실행중인 스레드를 원하는 시간만큼 멈추기, 1/1000 * n 초
package chapter17;

public class ThreadEx7 {

    public static void main(String[] args) {

        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i=0; i<10; i++) {
                    System.out.println("t1:"+i);
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {}
                }
                System.out.println("스레드 실행 종료");
            }
        });


        t1.start();

    }

}

join() 메서드

  • 다른 스레드의 실행이 완료될때까지 기다리는 메서드
class Sum extends Thread {
    int sum = 0;
    @Override
    public void run() {
        for (int i=1; i<=100; i++) {
            sum += i;
        }
    }
}

2개의 스레드 처리

package chapter17;

public class ThreadEx8 {

    public static void main(String[] args) {

        Sum t1 = new Sum();
        Sum t2 = new Sum();

        t1.start();
        t2.start();
        try {
            t1.join(); // t1 스레드가 종료될때까지 대기
            t2.join(); // t2 스레드가 종료될때까지 대기
        } catch (InterruptedException e) {

        }
        System.out.println("두 스레드의 sum 합계 = "+(t1.sum+t2.sum));

    }

}

yield() 메서드

  • 다른 스레드에게 실행을 양보하는 메서드
package chapter17;

public class ThreadEx9 {

    public static void main(String[] args) {

        YieldThread t1 = new YieldThread();
        YieldThread t2 = new YieldThread();

        t1.start();
        t2.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {}

        t1.isContinue = false; // t1 양보

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {}

        t1.isContinue = true; // t1 다시 실행, 주석달면 t2만 실행되고 끝남

        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {}

        // 스레드 종료
        t1.isBreak = true;
        t2.isBreak = true;

    }

}

class YieldThread extends Thread {
    boolean isBreak = false;
    boolean isContinue = true;

    @Override
    public void run() {
        while(!isBreak) {
            if (isContinue) {
                System.out.println(getName()+" 실행 중");
            }else {
                Thread.yield();
            }
        }
        System.out.println(getName()+" 종료");
    }
}

스레드 동기화

  • 하나의 객체를 여러 스레드가 동시에 사용시 의도치 않은 값의 변경등이 발생
  • 하나의 스레드 작업이 끝날 때까지 해당 객체가 변경되지 못하도록 막는것
  • 여러 개의 스레드가 한 개의 자원을 사용하고자 할 때 해당 스레드만 제외하고 나머지는 접근을 못하도록 막는 것
  • ex) 예매 시스템 등
  • syncronized 키워드 사용
// 플레이어1 스레드
class Player1 extends Thread {
    private SmartPhoneGame game;

    public void setSmartPhoneGame(SmartPhoneGame game) {
        this.setName("Player1"); // 스레드 이름 지정
        this.game = game;
    }

    @Override
    public void run() {
        game.increaseLevel();
    }
}
// 플레이어2 스레드
class Player2 extends Thread {
    private SmartPhoneGame game;

    public void setSmartPhoneGame(SmartPhoneGame game) {
        this.setName("Player2"); // 스레드 이름 지정
        this.game = game;
    }

    @Override
    public void run() {
        game.increaseLevel();
    }
}

동기화가 되지 않은 경우

// 스마트폰게임 클래스
class SmartPhoneGame {
    private int level; // 레벨

    public int getLevel() {
        return this.level;
    }

    public void increaseLevel() {
        while (true) {
            this.level++; // 레벨 1씩 증가
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {}
            // 현재 스레드의 이름과 레벌 출력
            System.out.println(Thread.currentThread().getName()+" Level : " + this.level);

            // 레벨이 10의 배수가 되면 종료
            if (this.level % 10 == 0) break;
        }
    }


}

테스트

package chapter17;

public class ThreadEx10 {

    public static void main(String[] args) {

        // 게임 객체 생성
        SmartPhoneGame game = new SmartPhoneGame();

        // 플레이어1 객체 생성 후 스레드 실행
        Player1 p1 = new Player1();
        p1.setSmartPhoneGame(game);
        p1.start();

        // 플레이어2 객체 생성 후 스레드 실행
        Player2 p2 = new Player2();
        p2.setSmartPhoneGame(game);
        p2.start();

    }

}

동기화한 경우

  • 메서드에 synchronized 사용
// 스마트폰게임 클래스
class SmartPhoneGame {
    private int level; // 레벨

    public int getLevel() {
        return this.level;
    }


    public synchronized void increaseLevel() {
        while (true) {
            this.level++; // 레벨 1씩 증가
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {}
            // 현재 스레드의 이름과 레벌 출력
            System.out.println(Thread.currentThread().getName()+" Level : " + this.level);

            // 레벨이 10의 배수가 되면 종료
            if (this.level % 10 == 0) break;
        }
    }

스레드 간에 교대로 작업 (협업)

  • running -> wait() -> blocked(실행 중지,대기 상태) -> notify() -> runnable(실행 가능상태)
  • wait() : 실행 -> 대기 상태
  • notify(), notifyAll() : 대기 -> 실행 가능
// 스마트폰게임 클래스
class SmartPhoneGame {
    private int level; // 레벨

    public int getLevel() {
        return this.level;
    }


    public synchronized void increaseLevel() {
        while (true) {
            this.level++; // 레벨 1씩 증가
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {}
            // 현재 스레드의 이름과 레벌 출력
            System.out.println(Thread.currentThread().getName()+" Level : " + this.level);
            if (this.level == 5) {

                try { 
                    wait(); // 실행 -> 대기 상태
                    notifyAll(); // 대기 -> 실행 가능
                } catch (InterruptedException e) {}
                break;
            }


            // 레벨이 10의 배수가 되면 종료
            if (this.level % 10 == 0) break;
        }
    }


}

스레드 예제

  • 입금후 출금처리
// 엄마 스레드
class Mother extends Thread {
    Account account;

    Mother(Account account) {
        super("엄마");
        this.account = account;
    }

    @Override
    public void run() {
        while(true) {
            try {
                account.deposit();
                sleep((int)(Math.random()*2000));
            } catch (InterruptedException e) {
                break;
            }
        }
    }
}

//아들 스레드
class Son extends Thread {
    Account account;

    Son(Account account) {
        super("아들");
        this.account = account;
    }

    @Override
    public void run() {
        while(true) {
            try {
                account.withdraw();
                sleep((int)(Math.random()*300));
            } catch (InterruptedException e) {
                break;
            }
        }
    }
}
// 통장 클래스
class Account {
    int money;
    synchronized void withdraw() {
        while(money == 0) {
            try {
                wait();
            }catch(InterruptedException e) {
                break;
            }
        }
        notifyAll();
        if(money > 0) {
            System.out.println(Thread.currentThread().getName() + money + "원 출금");
            money = 0;
        }
    }
    synchronized void deposit() {
        while(money > 0) {
            try {
                wait();
            } catch(InterruptedException e) {
                break;
            }
        }
        // 랜덤 입금 1~5만원
        money = (int)((Math.random()*5)+1)*10000;
        notifyAll();
        System.out.println();
        System.out.println(Thread.currentThread().getName() + money + "원 입금");
    }
}
package chapter17;

public class ThreadEx14 {

    public static void main(String[] args) {

        // 통장 객체 생성
        Account acc = new Account();

        // 엄마스레드 객체 생성
        Mother mother = new Mother(acc);
        // 아들스레드 객체 생성
        Son son = new Son(acc);


        mother.start();
        son.start();

        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {}

        // 스레드 중지
        son.interrupt();
        mother.interrupt();

    }

}



'Java' 카테고리의 다른 글

네트워크 network  (0) 2022.03.07
입출력 Input / Output  (0) 2022.03.07
스트림 stream  (0) 2022.03.07
람다 lambda  (0) 2022.03.07
제네릭 generic  (0) 2022.03.07