자바 는 어떻게 스 레 드 간 통신 을 실현 합 니까?

25282 단어
정상 적 인 상황 에서 모든 하위 라인 은 각자 의 임 무 를 완성 하면 끝 낼 수 있다.그러나 때때로 우 리 는 여러 스 레 드 가 협동 하여 어떤 임 무 를 완성 하 기 를 희망 하 는데 이때 스 레 드 간 통신 과 관련 되 었 다.
본 논문 과 관련 된 지식 점:
  • thread.join(),
  • object.wait(),
  • object.notify(),
  • CountdownLatch,
  • CyclicBarrier,
  • FutureTask,
  • Callable .

  • 다음은 자바 에서 어떤 방법 으로 스 레 드 간 통신 을 실현 하 는 지 몇 가지 예 를 들 어 착안점 으로 설명 하 겠 습 니 다.
  • 어떻게 두 스 레 드 를 순서대로 집행 합 니까?
  • 그러면 어떻게 두 라인 을 지 정 된 방식 에 따라 질서 있 게 교차 운행 하 게 합 니까?
  • 네 개의 스 레 드 A B C D, 그 중에서 D 는 A B C 가 모두 실 행 된 후에 야 실 행 됩 니 다. 그리고 A B C 는 동기 적 으로 실 행 됩 니 다
  • .
  • 세 선 수 는 각자 준비 하고 세 사람 이 모두 준비 한 후에 함께 뛴다
  • .
  • 서브 스 레 드 가 특정한 임 무 를 완성 한 후에 얻 은 결 과 를 메 인 스 레 드
  • 에 전달 합 니 다.
    어떻게 두 라인 을 순서대로 집행 합 니까?
    두 개의 스 레 드 가 있다 고 가정 하면 하 나 는 스 레 드 A 이 고 다른 하 나 는 스 레 드 B 이 며 두 스 레 드 는 각각 1 - 3 개의 숫자 를 순서대로 인쇄 하면 된다.코드 를 살 펴 보 겠 습 니 다.
    private static void demo1() {
        Thread A = new Thread(new Runnable() {
            @Override
            public void run() {
                printNumber("A");
            }
        });
        Thread B = new Thread(new Runnable() {
            @Override
            public void run() {
                printNumber("B");
            }
        });
        A.start();
        B.start();
    }

    그 중의 printNumber (String) 는 다음 과 같이 1, 2, 3 개의 숫자 를 순서대로 인쇄 합 니 다.
    private static void printNumber(String threadName) {
        int i=0;
        while (i++ < 3) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(threadName + " print: " + i);
        }
    }

    이때 우리 가 얻 은 결 과 는:
    B print: 1
    A print: 1
    B print: 2
    A print: 2
    B print: 3
    A print: 3

    A 와 B 가 동시에 인쇄 된 것 을 볼 수 있다.
    그렇다면 B 가 A 가 모두 인쇄 된 후에 인쇄 를 시작 하 기 를 원한 다 면?우 리 는 thread. join () 방법 을 이용 할 수 있 습 니 다. 코드 는 다음 과 같 습 니 다.
    private static void demo2() {
        Thread A = new Thread(new Runnable() {
            @Override
            public void run() {
                printNumber("A");
            }
        });
        Thread B = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("B 开始等待 A");
                try {
                    A.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                printNumber("B");
            }
        });
        B.start();
        A.start();
    }

    얻 은 결 과 는 다음 과 같다.
    B 开始等待 A
    A print: 1
    A print: 2
    A print: 3
    B print: 1
    B print: 2
    B print: 3

    그래서 우 리 는 A. join () 방법 을 볼 수 있 고 B 는 A 가 실 행 될 때 까지 기다 릴 수 있 습 니 다.
    그럼 어떻게  개 스 레 드 는 지 정 된 방식 에 따라 질서 있 게 교차 운행 합 니까?
    아니면 위의 예 입 니까? 저 는 지금 A 가 1 을 인쇄 한 후에 B 에 게 1, 2, 3 을 인쇄 하 게 하고 마지막 에 A 로 돌아 가 2, 3 을 계속 인쇄 하 기 를 바 랍 니 다.이런 수요 에서 Thread. join () 은 이미 만족 할 수 없 음 이 분명 하 다.우 리 는 실행 순 서 를 조절 하기 위해 서 좀 더 세밀 한 자물쇠 가 필요 하 다.
    여기 서 우 리 는 object. wait () 와 object. notify () 두 가지 방법 으로 실현 할 수 있 습 니 다.코드 는 다음 과 같 습 니 다:
    /**
     * A 1, B 1, B 2, B 3, A 2, A 3
     */
    private static void demo3() {
        Object lock = new Object();
        Thread A = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lock) {
                    System.out.println("A 1");
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("A 2");
                    System.out.println("A 3");
                }
            }
        });
        Thread B = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lock) {
                    System.out.println("B 1");
                    System.out.println("B 2");
                    System.out.println("B 3");
                    lock.notify();
                }
            }
        });
        A.start();
        B.start();
    }

    인쇄 결 과 는 다음 과 같 습 니 다.
    A 1
    A waiting…
    B 1
    B 2
    B 3
    A 2
    A 3

    바로 우리 가 원 하 는 결과 다.
    그렇다면 이 과정 에서 무슨 일이 일 어 났 을 까?
  • 먼저 A 와 B 가 공유 하 는 대상 잠 금 lock = new Object () 를 만 듭 니 다.
  • A 가 자 물 쇠 를 얻 은 후에 먼저 1 을 인쇄 한 다음 에 lock. wait () 방법 을 호출 하여 자물쇠 의 제어 권 을 내 놓 고 wait 상태 에 들어간다.
  • B 에 게 A 가 처음에 자 물 쇠 를 얻 었 기 때문에 B 는 실행 할 수 없다.A 가 lock. wait () 를 호출 하여 제어 권 을 방출 한 후에 야 B 는 자 물 쇠 를 얻 었 다.
  • B 는 자 물 쇠 를 얻 은 후에 1, 2, 3 을 인쇄 한다.그리고 lock. notify () 방법 을 호출 하여 wait 에 있 는 A 를 깨 웁 니 다.
  • A 가 깨 어 난 후 남 은 2, 3 을 계속 인쇄 한다.

  • 더 잘 이해 하기 위해 서 나 는 위의 코드 에 로 그 를 더 해서 독자 들 이 쉽게 볼 수 있 도록 했다.
    private static void demo3() {
        Object lock = new Object();
        Thread A = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("INFO: A 等待锁 ");
                synchronized (lock) {
                    System.out.println("INFO: A 得到了锁 lock");
                    System.out.println("A 1");
                    try {
                        System.out.println("INFO: A 准备进入等待状态,放弃锁 lock 的控制权 ");
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("INFO: 有人唤醒了 A, A 重新获得锁 lock");
                    System.out.println("A 2");
                    System.out.println("A 3");
                }
            }
        });
        Thread B = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("INFO: B 等待锁 ");
                synchronized (lock) {
                    System.out.println("INFO: B 得到了锁 lock");
                    System.out.println("B 1");
                    System.out.println("B 2");
                    System.out.println("B 3");
                    System.out.println("INFO: B 打印完毕,调用 notify 方法 ");
                    lock.notify();
                }
            }
        });
        A.start();
        B.start();
    }

    인쇄 결 과 는 다음 과 같 습 니 다.
    INFO: A 等待锁
    INFO: A 得到了锁 lock
    A 1
    INFO: A 准备进入等待状态,调用 lock.wait() 放弃锁 lock 的控制权
    INFO: B 等待锁
    INFO: B 得到了锁 lock
    B 1
    B 2
    B 3
    INFO: B 打印完毕,调用 lock.notify() 方法
    INFO: 有人唤醒了 A, A 重新获得锁 lock
    A 2
    A 3

    네 개의 스 레 드 A B C D, 그 중에서 D 는 A B C 가 모두 실 행 된 후에 야 실 행 됩 니 다. 그리고 A B C 는 동기 화 되 어 실 행 됩 니 다.
    처음에 우 리 는 thread. join () 을 소 개 했 습 니 다. 한 스 레 드 가 다른 스 레 드 가 실 행 된 후에 계속 실행 할 수 있 습 니 다. 그러면 우 리 는 D 스 레 드 에서 차례대로 join A B C 를 실행 할 수 있 습 니 다. 그러나 이것 은 A B C 가 순서대로 실행 해 야 합 니 다. 우리 가 원 하 는 것 은 이 세 사람 이 동시에 실행 할 수 있 도록 하 는 것 입 니 다.
    또는 우리 가 달성 하고 자 하 는 목적 은 A. B. C 세 개의 스 레 드 가 동시에 운행 되 고 각자 독립 적 으로 운행 한 후에 D 에 게 알 리 는 것 이다.D 에 게 는 A, B, C 가 모두 실행 되면 D 가 다시 운행 을 시작한다.이런 상황 에 대해 우 리 는 Countdown Latch 를 이용 하여 이런 통신 방식 을 실현 할 수 있다.그것 의 기본 용법 은:
  • 계수 기 를 만 들 고 초기 값 을 설정 합 니 다. Countdown Latch countDownlLatch = new Countdown Latch (2);
  • 대기 라인 에서 countDownlatch. awat () 방법 을 호출 하여 계수 값 이 0 이 될 때 까지 대기 상태 로 들 어가 기;
  • 다른 스 레 드 에서 countDownlatch. countDown () 방법 을 호출 하면 계수 값 을 1 로 줄 입 니 다.
  • 다른 스 레 드 의 countDown () 방법 이 계수 값 을 0 으로 바 꿀 때 스 레 드 의 countDownlatch. await () 가 즉시 종료 되 기 를 기다 리 고 아래 코드 를 계속 실행 합 니 다.

  • 구현 코드 는 다음 과 같 습 니 다:
    private static void runDAfterABC() {
        int worker = 3;
        CountDownLatch countDownLatch = new CountDownLatch(worker);
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("D is waiting for other three threads");
                try {
                    countDownLatch.await();
                    System.out.println("All done, D starts working");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
        for (char threadName='A'; threadName <= 'C'; threadName++) {
            final String tN = String.valueOf(threadName);
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(tN + " is working");
                    try {
                        Thread.sleep(100);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    System.out.println(tN + " finished");
                    countDownLatch.countDown();
                }
            }).start();
        }
    }

    다음은 실행 결과 입 니 다.
    D is waiting for other three threads
    A is working
    B is working
    C is working
    A finished
    C finished
    B finished
    All done, D starts working

    사실은 간단하게 말 하면 Countdown Latch 는 카운터 입 니 다. 우 리 는 초기 계수 값 을 3 으로 설정 합 니 다. D 가 실 행 될 때 먼저 countDownlatch. await () 를 호출 하여 카운터 값 이 0 인지 확인 하고 0 이 아니라면 대기 상 태 를 유지 합 니 다.A B C 가 각각 실행 되면 countDownlatch. countDown () 을 이용 하여 카운터 1 을 줄 이 고 세 개 를 모두 실행 한 후에 카운터 가 0 으로 줄어든다.이 때 D 를 즉시 터치 하 는 await () 실행 이 끝나 면 계속 아래로 실행 합 니 다.
    따라서 Count Downlatch 는 한 라인 에서 여러 라인 을 기다 리 는 경우 에 적용 된다.
    세 선 수 는 각자 준비 하고 세 사람 이 모두 준비 가 다 된 후에 다시 함께 뛴다.
    위 는 스 레 드 A B C 에 대해 각자 준 비 를 시작 하고 세 사람 이 모두 준 비 를 마 친 다음 에 동시에 운행 하 는 이미 지 를 비유 한 것 이다.즉, 하나의 스 레 드 간 에 서로 기다 리 는 효 과 를 실현 하려 면 어떻게 실현 해 야 합 니까?
    위의 Count Downlatch 는 거꾸로 계산 할 수 있 지만, 계산 이 끝나 면 한 라인 의 await () 만 응답 을 받 을 수 있 으 며, 여러 라인 이 동시에 터치 할 수 없습니다.
    스 레 드 간 에 서로 기다 리 는 수 요 를 실현 하기 위해 서 우 리 는 Cyclic Barrier 데이터 구 조 를 이용 할 수 있 습 니 다. 그의 기본 적 인 용법 은:
  • 먼저 공공 CyclicBarrier 대상 을 만 들 고 동시에 기다 리 는 스 레 드 수 를 설정 합 니 다. CyclicBarrier cyclicBarrier = new CyclicBarrier (3);
  • 이 스 레 드 들 은 동시에 자신 이 준 비 를 시작 합 니 다. 자신 이 준 비 를 마 친 후에 다른 사람 이 준 비 를 마 칠 때 까지 기 다 려 야 합 니 다. 이때 cyclicBarrier. await () 를 호출 합 니 다.다른 사람 을 기다 리 기 시작 할 수 있다.
  • 지정 한 동시에 기다 리 는 스 레 드 수 는 모두 cyclicBarrier. awat () 를 호출 합 니 다.이 스 레 드 가 모두 준 비 된 후에 야 이 스 레 드 가 동시에 실 행 된 다 는 것 을 의미한다.

  • 실현 코드 는 다음 과 같다. 세 명의 달리기 선수 가 각자 준 비 를 다 한 후에 다른 사람 을 기다 리 고 모두 준 비 를 한 후에 야 달리 기 를 시작 할 것 이 라 고 구상 한다.
    private static void runABCWhenAllReady() {
        int runner = 3;
        CyclicBarrier cyclicBarrier = new CyclicBarrier(runner);
        final Random random = new Random();
        for (char runnerName='A'; runnerName <= 'C'; runnerName++) {
            final String rN = String.valueOf(runnerName);
            new Thread(new Runnable() {
                @Override
                public void run() {
                    long prepareTime = random.nextInt(10000) + 100;
                    System.out.println(rN + " is preparing for time: " + prepareTime);
                    try {
                        Thread.sleep(prepareTime);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    try {
                        System.out.println(rN + " is prepared, waiting for others");
                        cyclicBarrier.await(); // 当前运动员准备完毕,等待别人准备好
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } catch (BrokenBarrierException e) {
                        e.printStackTrace();
                    }
                    System.out.println(rN + " starts running"); // 所有运动员都准备好了,一起开始跑
                }
            }).start();
        }
    }

    인쇄 결 과 는 다음 과 같 습 니 다.
    A is preparing for time: 4131
    B is preparing for time: 6349
    C is preparing for time: 8206
    A is prepared, waiting for others
    B is prepared, waiting for others
    C is prepared, waiting for others
    C starts running
    A starts running
    B starts running

    하위 스 레 드 가 어떤 임 무 를 완성 한 후, 얻 은 결 과 를 주 스 레 드 에 되 돌려 줍 니 다.
    실제 개발 에서 우 리 는 자주 서브 스 레 드 를 만들어 서 시간 이 걸 리 는 임 무 를 한 다음 에 작업 수행 결 과 를 메 인 스 레 드 에 전송 하여 사용 해 야 합 니 다. 이런 상황 은 자바 에서 어떻게 실현 해 야 합 니까?
    스 레 드 생 성 을 돌 이 켜 보면 runnable 대상 을 Thread 에 전달 합 니 다.Runnable 정 의 는 다음 과 같 습 니 다.
    public interface Runnable {
        public abstract void run();
    }

    run () 이 실 행 된 후에 어떤 결과 도 되 돌려 주지 않 는 것 을 볼 수 있 습 니 다.그럼 결과 로 돌아 가 고 싶다 면?다른 인터페이스 클래스 Callable 을 사용 할 수 있 습 니 다.
    @FunctionalInterface
    public interface Callable {
        /**
         * Computes a result, or throws an exception if unable to do so.
         *
         * @return computed result
         * @throws Exception if unable to compute a result
         */
        V call() throws Exception;
    }

    이 를 통 해 알 수 있 듯 이 Callable 의 가장 큰 차 이 는 리 턴 모델 V 결과 다.
    그렇다면 다음 문 제 는 하위 스 레 드 의 결 과 를 어떻게 되 돌려 주 느 냐 하 는 것 이다.자바 에 서 는 Callable 에 맞 춰 사용 되 는 클래스 가 있 습 니 다. Future Task 입 니 다. 하지만 결 과 를 얻 는 get 방법 은 메 인 스 레 드 를 막 을 수 있 습 니 다.
    예 를 들 어 우 리 는 서브 스 레 드 로 하여 금 1 에서 100 까지 계산 하 게 하고 계산 한 결 과 를 메 인 스 레 드 로 되 돌려 주 고 싶다.
    private static void doTaskWithResultInWorker() {
        Callable callable = new Callable() {
            @Override
            public Integer call() throws Exception {
                System.out.println("Task starts");
                Thread.sleep(1000);
                int result = 0;
                for (int i=0; i<=100; i++) {
                    result += i;
                }
                System.out.println("Task finished and return result");
                return result;
            }
        };
        FutureTask futureTask = new FutureTask<>(callable);
        new Thread(futureTask).start();
        try {
            System.out.println("Before futureTask.get()");
            System.out.println("Result: " + futureTask.get());
            System.out.println("After futureTask.get()");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }

    인쇄 결 과 는 다음 과 같 습 니 다.
    Before futureTask.get()
    Task starts
    Task finished and return result
    Result: 5050
    After futureTask.get()

    주 스 레 드 가 future Task. get () 방법 을 호출 할 때 주 스 레 드 를 막 는 것 을 볼 수 있 습 니 다.그리고 Callable 내부 에서 실행 을 시작 하고 연산 결 과 를 되 돌려 줍 니 다.이 때 future Task. get () 이 결 과 를 얻 고 메 인 스 레 드 가 다시 실 행 됩 니 다.
    여기 서 우 리 는 Future Task 와 Callable 을 통 해 메 인 스 레 드 에서 하위 스 레 드 의 연산 결 과 를 직접 얻 을 수 있 고 메 인 스 레 드 를 막 아야 한 다 는 것 을 배 울 수 있 습 니 다.물론 메 인 스 레 드 를 막 지 않 으 려 면 Executor Service 를 이용 하여 Future Task 를 스 레 드 탱크 에 넣 어 관리 하고 실행 하 는 것 을 고려 할 수 있 습 니 다.
    작은 매듭
    다 중 스 레 드 는 현대 언어의 공 통 된 특성 이 고 스 레 드 간 통신, 스 레 드 동기 화, 스 레 드 안전 은 매우 중요 한 화제 이다.본 고 는 자바 의 스 레 드 간 통신 에 대해 대체적인 설명 을 했 고 그 다음 에 스 레 드 동기 화, 스 레 드 안전 에 대해 서도 설명 할 것 이다.

    좋은 웹페이지 즐겨찾기