Unicorns

All the things

ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Java Study] - 멀티 쓰레드(쓰레드 생성과 실행)
    Java 2021. 10. 12. 16:07

    자바의 멀티 쓰레드 프로그래밍을 알아보기 전에

    프로세스와 쓰레드의 개념을 살펴보면

    프로세스

    • 실행 중인 프로그램
    • 즉, 사용자가 작성한 프로그램이 운영체제에 의해 메모리 공간을 할당받아 실행중인 것을 말함
    • 자원(메모리,CPU..etc)과 쓰레드로 구성

     

    멀티 프로세스

     

    여러 개의 CPU를 사용하여 여러 프로세스를 동시에 수행하는 것

    이미지 출처 : https://www.geeksforgeeks.org/difference-between-multiprocessing-and-multithreading/

     

    쓰레드

    • 프로세스 내에서 실제 작업을 수행
    • 모든 프로세스는 최소한 하나의 쓰레드를 가지고 있다.

     

    멀티 쓰레드

     

    일반적으로 하나의 프로세서는 하나의 스레드를 가지고 작업을 수행한다.

    하지만 멀티 스레드는 하나의 프로세스 내에서 둘 이상의 스레드가 동시에 작업을 수행하는 것을 의미한다.

     

     

    이미지 출처 : https://www.geeksforgeeks.org/difference-between-multiprocessing-and-multithreading/

     

    동시성(Concurrency) 과 병렬성(Parallelism)

    동시성(Concurrency)

    멀티 작업을 위해 하나의 코어 에서 멀티 스레드가 번갈아가며 실행하는 성질

    싱글 코어 CPU를 이용한 멀티 스레드 작업은 병렬적으로 실행되는 것처럼 보이지만,

    사실은 번갈아가면서 실행하는 동시성 작업이다.  번갈아 실행하는 것이 워낙 빨라서 병렬성으로 보일 뿐이다.

     

    병렬성(Paralleism)

    멀티 작업을 위해 멀티 코어 에서 개별 스레드를 동시에 실행하는 성질

     

     

    멀티 쓰레드와 멀티 프로세스 비교

     

    멀티 스레드와 멀티 프로세스 모두 여러 흐름을 동시에 수행한다는 공통점을 가지고 있지만

    차이점은 멀티 프로세스는 각 프로세스가 독립적인 메모리를 가지고 별도로 실행되지만

    멀티 스레드는 각 스레드가 자신이 속한 프로세스의 메모리를 공유한다는 점이 다르다.

     

    자바 에서 쓰레드

    자바 프로그램에는 최소 한 개의 쓰레드가 있는데, 바로 main 쓰레드 이다.

    main 쓰레드는 JVM이 생성하는 것으로 자바 프로그램이 실행되면 작업을 수행하는 쓰레드다.

    쉽게 말해 main 메서드의 코드를 수행한다고 보면 된다. 

     

    쓰레드가 없다면 자바 애플리케이션 실행이 불가능하다.

     

     

     

    이제 main 쓰레드 외에 추가로 쓰레드를 생성하여 멀티 쓰레드 프로그래밍을 구현 해보자

     

    쓰레드의 생성과 실행

    쓰레드를 생성하는 방법은 2가지로

    Thread 클래스를 상속  &  Runnable 인터페이스를 구현  하는 방법이 있다.

     

     

    Thread 클래스를 상속

    public class HelloThread extends Thread{
        @Override
        public void run() { // 쓰레드가 수행할 작업을 작성
            System.out.println("hello");
        }
    }

    생성한 쓰레드 실행하기:

    public class HelloThread extends Thread{
        @Override
        public void run() {
            System.out.println("hello");
        }
    
        public static void main(String[] args) {
            HelloThread thread = new HelloThread();
            thread.start();
        }
    }

    Thread 클래스를 상속한 경우 객체 인스턴스를 생성 한 뒤 start() 메서드를 호출한다.

     

     

    Runnable 인터페이스 구현

     

    public class HelloRunnable implements Runnable {
        @Override
        public void run() { // 쓰레드가 수행할 작업을 작성
            System.out.println("hello");
        }
    }

    실행하기

     

    public class HelloRunnable implements Runnable {
        @Override
        public void run() {
            System.out.println("hello");
        }
    
        public static void main(String[] args) {
            HelloRunnable runnable = new HelloRunnable();
            Thread thread = new Thread(runnable);
            thread.start();
        }
    }

     

    Runnable 인터페이스를 구현한 경우 객체 인스턴스를 Thread 객체 생성자에 전달 한 뒤 start() 메서드를 호출한다.

     

     

    자바에서 쓰레드를 실행하기 위해서는 Runnable 인터페이스를 구현해야 한다.  

    Runnable 인터페이스는  추상 메서드 run()  하나만 있는 함수형 인터페이스로

    run() 메서드를 오버라이딩 하여 쓰레드를 실행할 수 있다.

     

    Thread 클래스는 java.lang 패키지안에 Runnable 인터페이스를 구현한 클래스이다.

    이 클래스안에는 당연히 오버라이딩한 run() 메서드가 있고 그 외에

    많은 속성들이 있는데

    그 중에 위에 코드에서 본 쓰레드를 실행하는 start() 메서드가 있다.

     

    차이점

     

    Thread 클래스

     쓰레드의 다양한 메서드 중 오버라이딩하고 싶은 것이 있을 때 사용

    그리고 자바에서는 다중 상속을 허용하지 않기 때문에 다른 클래스를 상속할 때는 Runnalble 인터페이스 사용

     

    Runnable 인터페이스

    run() 로 다른 메서드 오버라이딩 없이 실행만 하고 싶을 때 사용

    Thread 클래스를  상속하면 다른 클래스를 상속할 수 없기 때문에 상속할 클래스가 있을 때 사용

     

    여기서 의문점이 있다.

    run() 으로 실행하면 되는데 start()로 실행하는 특별한 이유가 있을까??

     

    결론 부터 얘기하면 start()를 사용해야 멀티 쓰레드 프로그래밍 을 할 수 있다.

     

    start() 메서드는 호출 시  새로운 쓰레드가 생성되고 

    생성된 쓰레드에서 run() 메서드가 실행된다.

    main 쓰레드가 실행되고 main() 메서드가 호출된다.

    main() 메서드가 start() 메서드를 호출한다.

    public class ThreadTest extends Thread {
        public static void main(String[] args) {
            ThreadTest test = new ThreadTest();
            test.start();
            for (int i = 0; i < 50; i++) {
                System.out.println(Thread.currentThread().getName()+" : "+i);
            }
        }
        @Override
        public void run() {
            for (int i = 0; i < 50; i++) {
                System.out.println(this.getName() + ": " + i);
            }
        }
    }

    start() 메서드가 호출되면 새로운 쓰레드가 생성되고

     

    새로운 쓰레드에서 run() 메서드가 실행된다.

     

    그리고 main 메서드 안에 있는 for문이 실행되면서

     

    동시에 main 쓰레드에 for문과 생성된 쓰레드의 for 문이 출력된다.

     

     

     

     

     run() 메서드를 직접 호출하면 새로운 쓰레드가 생성되지 않고 현재 스레드에서 호출한다.

     

     

    새로운 쓰레드가 생성되지 않고

    메인 쓰레드에서 run() 메서드를 호출한다.  이때 run() 은 Thread 클래스를 오버라이딩한 메서드일 뿐이다.

     

    public class ThreadTest extends Thread {
        public static void main(String[] args) {
            ThreadTest test = new ThreadTest();
            test.run();
            for (int i = 0; i < 50; i++) {
                System.out.println(Thread.currentThread().getName()+" : "+i);
            }
        }
        @Override
        public void run() {
            for (int i = 0; i < 50; i++) {
                System.out.println(this.getName() + ": " + i);
            }
        }
    }

     

    run() 에 있는 for문이 출력되고 난 뒤 아래에 있는 main 메서드가 출력된다.

     

     

    Thread.currentThread().getName() 으로 확인 해보자

    public class ThreadTest extends Thread {
        public static void main(String[] args) {
            ThreadTest test = new ThreadTest();
            test.run();
            for (int i = 0; i < 50; i++) {
                System.out.println(Thread.currentThread().getName()+" : "+i);
            }
        }
        @Override
        public void run() {
            System.out.println("print....."+Thread.currentThread().getName());
            for (int i = 0; i < 50; i++) {
                System.out.println(Thread.currentThread().getName() + ": " + i);
            }
        }
    }

    출력

    run() 을 바로 호출 했기 때문에 main 쓰레드에 있는 main 함수가 호출했다. 즉 싱글 쓰레드 이다.

     

     

    start() 로 호출하게 되면

    public class ThreadTest extends Thread {
        public static void main(String[] args) {
            ThreadTest test = new ThreadTest();
            test.start();
            for (int i = 0; i < 50; i++) {
                System.out.println(Thread.currentThread().getName()+" : "+i);
            }
        }
        @Override
        public void run() {
            System.out.println("print....."+Thread.currentThread().getName());
            for (int i = 0; i < 50; i++) {
                System.out.println(Thread.currentThread().getName() + ": " + i);
            }
        }
    }

    출력:

    메인쓰레드가 아닌 새롭게 생성된 쓰레드 임을 알 수 있다.

     

    이것을 그림으로 나타내면 아래와 같다.

     

     

     

     

    데몬 쓰레드

    java 에는 두가지 타입의 쓰레드가 있다.

    하나는 사용자(일반) 쓰레드이고 나머지 하나는 데몬쓰레드이다.

     

    위에서 Main 쓰레드가 생성한 쓰레드들은 사용자 쓰레드이다.

     

    그렇다면 데몬 쓰레드는 ??

    • 사용자 쓰레드의 작업을 돕는 보조적인 역할을 수행
    • 사용자 쓰레드가 모두 종료되면 자동적으로 종료된다.
    • 가비지 컬렉터 , 자동저장 , 화면 자동갱신 등에 사용된다.

     

    설정

     

    void setDaemon(boolean on)  : 쓰레드를 데몬 쓰레드 또는 사용자 쓰레드로 변경

    boolean isDaemon() - 쓰레드가 데몬 쓰레드 인지 확인한다.

    public class Application extends Thread {
        public static void main(String[] args) {
            Application application = new Application();
            application.setDaemon(true); // 데몬 쓰레드 설정
            application.start();
        }
        @Override
        public void run() {
           if (Thread.currentThread().isDaemon()) { // 데몬 쓰레드 인지 확인
               System.out.println("데몬 쓰레드 입니다");
           }else  {
               System.out.println("사용자 쓰레드 입니다.");
           }
        }
    }

    출력

    public class Application extends Thread {
        public static void main(String[] args) {
            Application application = new Application();
            application.setDaemon(false); // 사용자 쓰레드 설정
            application.start();
        }
        @Override
        public void run() {
           if (Thread.currentThread().isDaemon()) { // 데몬 쓰레드 인지 확인
               System.out.println("데몬 쓰레드 입니다");
           }else  {
               System.out.println("사용자 쓰레드 입니다.");
           }
        }
    }

    출력

     

    ※ 데몬 쓰레드 설정은 start() 메서드를 호출하기 전에 해야 한다. 

    start() 메서드를 실행하고  데몬 쓰레드를 설정하면 IllegalThreadStateException 예외가 발생한다.

     

    public class Application extends Thread {
        public static void main(String[] args) {
            Application application = new Application();
            application.start();
            application.setDaemon(false); //IllegalThreadStateException
        }
        @Override
        public void run() {
           if (Thread.currentThread().isDaemon()) { // 데몬 쓰레드 인지 확인
               System.out.println("데몬 쓰레드 입니다");
           }else  {
               System.out.println("사용자 쓰레드 입니다.");
           }
        }
    }

     

    그래서 당연히 main 쓰레드의 경우 ,

    자바 프로그램이 실행되자마자  main 쓰레드도 함께 실행 되기 때문에  main 쓰레드는 데몬 쓰레드로 설정 할 수 없다.

     

     

     

    데몬 쓰레드는 사용자가 쓰레드가 종료되면 자동으로 종료된다.

     

    메인 쓰레드는 for문 안에서 1초 간격으로 메인 쓰레드 실행 문장과 숫자를 출력하고

    데몬쓰레드는 2초 간격으로 "데몬 쓰레드 실행" 이라는 문장을 출력하는데

    while(true) 로 설정 했기 때문에 무한 루프에 빠질 수 있다.

     

    하지만 실행 해보면

    무한 루프에 빠지지 않고 메인 쓰레드의 for 문이 끝나자 데몬 쓰레드도 함께 종료되었다.

     

    즉, JVM은 사용자 쓰레드는 작업이 끝날 때 까지 기다려주지만, 데몬 쓰레드는 

    사용자 쓰레드의 작업이 끝나면 데몬 쓰레드가 작업이 끝날 때까지 기다려주지 않고

    즉시 종료 시켜버린다.

     

     

    쓰레드의 우선순위

    작업의 중요도에 따라 쓰레드의 우선순위를 다르게 하여 특정 쓰레드가 더 많은 작업시간을 갖게 할 수 있다.

     

    • 우선 순위는 숫자 1~10까지의 범위로 나타낼 수 있다.
    • 기본 값은 5이다.
    • 최소 값은 1 , 최대 값은 10이다. 

     

    다음 과 같이 세 개의 상수로 표현할 수 있다.

    public class ThreadTest extends Thread {
        public static void main(String[] args) {
            ThreadTest test = new ThreadTest();
            test.start();
            System.out.println(Thread.NORM_PRIORITY); // 5
            System.out.println(Thread.MIN_PRIORITY); // 1
            System.out.println(Thread.MAX_PRIORITY); // 10
    
        }
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getPriority());
        }
    }

     

    우선순위 설정

    setPriority() 메서드를 사용하여 우선순위를 임의로 지정할 수 있다.

    public class ThreadTest extends Thread {
        public static void main(String[] args) {
            Thread.currentThread().setPriority(9); // 우선순위 9로 설정
            System.out.println(Thread.currentThread().getPriority()); // 값 9
            ThreadTest test = new ThreadTest();
            test.start(); // 생성한 스레드 실행
        }
    
        @Override
        public void run() {
            System.out.println(this.getPriority()); // 값 : 9
        }
    }

    main 쓰레드의 우선순위를 9로 지정하였다.
    그리고 값을 출력해보니 생성된 쓰레드 역시 9로 지정되었다.

     

    메인 쓰레드 말고 생성된 쓰레드의 우선순위를 지정해보자

    public class ThreadTest extends Thread {
        public static void main(String[] args) {
            Thread.currentThread().setPriority(9); // 우선순위 9로 설정
            System.out.println(Thread.currentThread().getPriority()); // 값 9
            ThreadTest test = new ThreadTest();
            test.start(); // 생성한 스레드 실행
            test.setPriority(3); // 우선순위 3으로 설정
        }
    
        @Override
        public void run() {
            System.out.println(this.getPriority()); // 값 : 3
        }
    }

     

    우선 순위 범위(1~10)를 벗어나게 지정하면 IllegalArgumentException 이 발생한다.

     

    public class ThreadTest extends Thread {
        public static void main(String[] args) {
            ThreadTest test = new ThreadTest();
            test.start();
            test.setPriority(11); // java.lang.IllegalArgumentException 
    
        }
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getPriority());
        }
    }

     

    쓰레드의 상태

    상태 설명
    NEW 쓰레드가 생성되고 아직 start()가 호출되지 않은 상태
    RUNNABLE 실행 가능한 상태 , start() 호출 되면 RUNNABLE  상태가 되고 CPU가 가동 되길 기다린다.
    RUNNING 실행을 위해 CPU가 쓰레드에게 time slot 을 할당 , run() 호출
    BLOCKED 동기화 블럭에 의해서 일시정지된 상태(lock이 풀릴 때까지 기다리는 상태)
    WAITING 쓰레드의 작업이 종료되지는 않았지만 실행가능 하지 않은 일시정지 상태
    TIMED_WAITING 주어진 시간 동안 기다리는 상태
    TERMINATED 쓰레드의 작업이 종료된 상태

     

    쓰레드의 실행 제어

    • 쓰레드의 실행을 제어할 수 있는 메서드
    메서드 설명
    static void sleep(long millis)
    static void sleep(long millis , int nanos)
    지정된 시간(천분의 일초 단위)동안 쓰레드를 일시 정지시킨다. 지정한 시간이 지나고 나면, 자동적으로 다시 실행 대기 상태가 된다.
    void join()
    void join(long mills)
    void join(long millis , int nanos)
    지정된 시간동안 쓰레드가 실행되도록 한다. 지정된 시간이 지나거나 작업이 종료되면 join()을 호출한 쓰레드로 다시 돌아와 실행을 계속한다.
    void interrupt() sleep() 이나 join()에 의해 일시정지상태인 쓰레드를 깨워서 실행대기상태로 만든다. 해당 쓰레드에서는 InterruptedException 이 발생함으로써 일시정지 상태를 벗어나게 된다.
    void stop() 쓰레드를 즉시 종료시킨다.
    void suspend()  쓰레드를 일시정지 시킨다. resume()을 호출하면 다시 실행대기 상태가 된다.
    void resume() suspend() 에 의해 일시정지 상태에 있는 쓰레드를 다시 실행 대기 상태로 만든다.
    static void yield() 실행 중에 자신에게 주어진 실행시간을 다른 쓰레드에게 양보하고 자신은 실행대기 상태가 된다.

    위 메서드를 활용하여 보다 효율적은 프로그램을 작성할 수 있다.

     

     

     

    sleep()

     

    현재 쓰레드를 지정된 시간동안 멈추게 한다.

     

    sleep() 메서드는 예외 처리를 해주지 않으면 컴파일 에러가 발생한다.

    잠자고(일시 정지상태) 있는 데 사용자가 깨울 수 있기 때문에 컴파일러가 그것에 대한 예외처리를 해야한다고 알려주는 것이다.

    InterruptException이 발생하면 깨어난다.

    public class ThreadTest extends Thread {
        public static void main(String[] args) {
            ThreadTest test = new ThreadTest();
            test.start();
            for (int i=0; i<5; i++){
                System.out.println(Thread.currentThread().getName()+"실행");
            }
        }
        @Override
        public void run() {
            try {
                Thread.sleep(2000); // 2초가 일시 정지
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"실행");
        }
    }

    생성된 쓰레드는 2초간 일시정지 되고 작업을 수행하기 때문에

    메인쓰레드가 먼저 출력되고 2초 후에 생성된  쓰레드의 작업 결과가 출력된다.

     

     

     

     

    interrupt()

     

    대기 상태(WAITING)인 쓰레드를 실행대기 상태(RUNNABLE)로 만든다.

    void interrupt()  //쓰레드의 interrupted 상태를 false 에서 true로 변경
    boolean isInterrupted() //쓰레드의 interrupted 상태를 반환
    static boolean interrupted() //현재 쓰레드의 interrupted 상태를 알려주고, false로 초기화

     

    public class InterruptedTest extends Thread{
        public static void main(String[] args) {
            InterruptedTest newThread = new InterruptedTest();
            newThread.start();
            System.out.println(newThread.isInterrupted()); // false
            String input = JOptionPane.showInputDialog("아무 값이나 입력하세요");
            System.out.println("입력 값은 :" +input+"입니다.");
            newThread.interrupt(); // false -> true 변경
            System.out.println(newThread.isInterrupted()); // true
        }
    
        @Override
        public void run() {
            for (int i=10; i>=0; i--) {
                if (isInterrupted()){ // true면 break
                    break;
                }
                System.out.println("카운트 :" +i);
                try {
                    Thread.sleep(2000); // 2초간 일시정지
                } catch (InterruptedException e) {
                    interrupt();
                }
            }
            System.out.println("카운트가 종료되었습니다.");
        }
    }

     

    suspend() , resume() , stop()

     

    쓰레드의 실행을 일시정지, 재개, 완전정지 시킨다.

    void suspend() // 쓰레드를 일시정지 시킨다.
    void resume() // suspend()에 의해 일시정지된 쓰레드를 실행대기상태로 만든다.
    void stop() // 쓰레드를 즉시 종료시킨다.

     

     

    ※ suspend() , resume() , stop() 는 데드락이 발생할 수 있어서

    deprecated 되었다.

     

    사용하면 안된다!!!

    인텔리제이에서 사용하니깐 deprecated 되었다고 줄을 그어놓음.

    join()

     

    지정된 시간동안 특정 스레드가 작업하는 것을 기다린다.

     

    sleep()과 마찬가지로 예외처리를 해줘야 한다.

    void join() // 작업이 모두 끝날 때까지
    void join(long millis) // 천분의 일초 동안
    void join(long millis , int nanos) // 천분의 일초 + 나노초 동안

     

    메인 쓰레드가 thread1의 작업이 끝날 때 까지 기다렸다가 

    작업이 끝나면 그제서야 밑에 있는 "소요 시간 : " 을 출력한다. 

    public class JoinTest {
        public static void main(String[] args) {
            Thread1 thread1 = new Thread1();
            thread1.start();
            long startTime = System.currentTimeMillis(); // 시작시간
    
            try {
                thread1.join(); // 메인 쓰레드가 thread1 의 작업이 끝날 때 까지 기다린다.
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // thread1 의 작업이 끝난 뒤 출력
            System.out.println("소요 시간 : " + (System.currentTimeMillis() - startTime));
        }
    }
    
    class Thread1 extends Thread {
        @Override
        public void run() {
            for (int i=0; i<200; i++){
                System.out.print("-");
            }
        }
    }

     

    yield()

     

    yield() 가 호출되면 쓰레드는 running 상태에서 runnable 상태로 이동한다.

    즉, 남은 시간을 다음 쓰레드에게 양보하고 자신은 실행대기 한다.

    thraed scheuler 는 현재 RUNNING 상태 쓰레드를  실행대기 상태로 보내고

    우선순위가 같거나 더 높은 다른 쓰레드를 선택한 뒤 실행 시킨다. 

     

    public class A implements Runnable {
        public void run() {
            System.out.println(Thread.currentThread());
            Thread.yield();  // 현재 실행 쓰레드가 실행 대기 상태로 이동한다. 
            System.out.println(Thread.currentThread());
        }
    
        public static void main(String[] args) {
            A a1 = new A();
            Thread t1 = new Thread(a1, "First Child Thread");
    
            A a2 = new A();
            Thread t2 = new Thread(a2, "Second Child Thread");
    
            t1.start();
            t2.start();
        }
    }

    t1 쓰레드가 실행 중이다.  -> yield()  호출 ->   t1 쓰레드 실행 대기 상태로 이동 ->   t2 쓰레드 실행

    -> yield() 호출 -> t2 쓰레드 실행 대기 상태로 이동

     

     

    쓰레드의 동기화(synchronization)

    멀티 쓰레드 프로세스에서는 같은 자원을 공유하고 접근하기 때문에 다른 쓰레드의 작업에 영향을 미칠 수 있다

    그로 인해 오류나 예상치 못한 결과가 발생한다.

     

    진행중인 작업이 다른 쓰레드에게 간섭받지 않게 하려면 '동기화'가 필요하다.

    동기화를 통해 공유된 자원에 하나의 쓰레드만 접근 가능하게 할수 있다.

     

    동기화를 하려면 간섭받지 않아야 하는 문장들을 '임계 영역'으로 설정해야 한다.

    임계 영역은 락(lock)을 얻은 단 하나의 쓰레드만 출입가능하다 (객체 1개에 락 1개)

     

    lock

    모든 객체에는 연결된 lock이 있다.  멀티 쓰레드가 공유된 자원에 접근할 때 lock을 사용한다.

    한 쓰레드가 lock을 사용하고 있으면 , 다른 쓰레드는 lock이 풀릴 때까지 기다린다. 

    lock은 작업이 끝나면 풀린다.

    쓰레드에서 synchronized 메소드가 호출되면 lock을 요청하여 얻어야 한다. 

    이때 다른 쓰레드에서 lock을 얻으려고 하면 blocked 된다.

     

    예제

     

    동기화 사용 X

    public class WithoutSynchronization {
        public static void main(String[] args) {
            Count count = new Count(); // 하나의 객체
            MyThead1 t1 = new MyThead1(count);
            MyThead2 t2 = new MyThead2(count);
            t1.start();
            t2.start();
        }
    }
    
    class MyThead1 extends Thread {
        Count c;
        public MyThead1(Count c) {
            this.c = c;
        }
        @Override
        public void run() {
            c.print(5);
        }
    }
    
    class MyThead2 extends Thread {
        Count c;
        public MyThead2(Count c) {
            this.c = c;
        }
        @Override
        public void run() {
            c.print(100);
        }
    }
    class Count {
        void print(int n) {
            for (int i = 1; i <= 5; i++) {
                System.out.println(n * i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    결과

    동기화 사용 

    public class WithSynchronization {
        public static void main(String[] args) {
            Count count = new Count(); // 하나의 객체
            MyThead1 t1 = new MyThead1(count);
            MyThead2 t2 = new MyThead2(count);
            t1.start();
            t2.start();
        }
    }
    
    class MyThead1 extends Thread {
        Count c;
        public MyThead1(Count c) {
            this.c = c;
        }
        @Override
        public void run() {
            c.print(5);
        }
    }
    
    class MyThead2 extends Thread {
        Count c;
        public MyThead2(Count c) {
            this.c = c;
        }
        @Override
        public void run() {
            c.print(100);
        }
    }
    class Count {
        synchronized void print(int n) { // 동기화 메서드
            for (int i = 1; i <= 5; i++) {
                System.out.println(n * i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    결과

     

    wait() 와 notify()

     

    동기화의 효율을 높이기 위해 wait() , notify()를 사용한다.

    Object 클래스에 정의되어 있으며, 동기화 블록 내에서만 사용할 수 있다.

    • wait()  - 객체의 lock()을 풀고 쓰레드를 해당 객체의 waiting pool 에 넣는다.
    • notify() - waiting pool에서 대기중인 쓰레드 중의 하나를 깨운다.
    • notifyAll() - waiting pool에서 대기중인 모든 쓰레드를 깨운다.

     

     

     

     

     

    REFERENCE

    https://tcpschool.com/java/java_thread_multi
    https://www.geeksforgeeks.org/difference-between-thread-start-and-thread-run-in-java/ https://javagoal.com/thread-in-java/
    https://javagoal.com/synchronization-in-java/
    자바의 정석
    이것이 자바다

     

     

     

    댓글

Designed by Tistory.