동시 다발 에 관 한 간단 한 면접 문제

제목: 두 개의 스 레 드, 스 레 드 1 은 list 에 10 개의 요 소 를 추가 하고 스 레 드 2 검 측 list 의 요소 개 수 는 개수 가 5 일 때 스 레 드 2 는 자신 을 알 리 고 끝 냅 니 다.
생각 나 는 방안 1:
public class Demo1 {


	public static void main(String[] args) {
		
		List integers = new ArrayList<>(10);
		new Thread(() -> {
			for (int i=0;i<10;i++) {
			    integers.add(i);
				System.out.println("thread1 -> current integers size = " + integers.size());
			}
		}).start();

		new Thread(()->{
			while (true){
				if (5 == integers.size()){
					break;
				}
			}
			System.out.println("thread2 -> integers size equals to 5");
		}).start();
	}
}

그러나 이 방안 은 옳지 않다
이유: 위의 코드 에서 두 스 레 드 의 list 는 같은 list 가 아 닙 니 다.이것 은 모든 스 레 드 가 생 성 될 때 각 스 레 드 내부 메모리 공간 에서 integers 집합 현재 상태의 복사 본 을 생 성 하기 때 문 입 니 다.따라서 스 레 드 1 이 integers 를 조작 할 때 스 레 드 2 가 보이 지 않 고 스 레 드 2 가 영원히 integers 의 초기 상태 이기 때문에 스 레 드 2 는 하나의 whiel (true) 순환 을 합 니 다.
 
프로젝트 1 수정: integers 를 구성원 변수 로 바 꾸 고 volatile 키 워드 를 추가 합 니 다.
public class Demo1 {


	private volatile static List integers = new ArrayList<>(10);
	public static void main(String[] args) throws Exception{
		
		new Thread(() -> {
			for (int i = 0; i < 10; i++) {
				integers.add(i);				
				System.out.println("thread1 -> current integers size = " + integers.size());
			}
		}).start();

		new Thread(() -> {
			while (true) {
				if (5 == integers.size()) {
					break;
				}
			}
			System.out.println("thread2 -> integers size equals to 5");
		}).start();
	}
}

이때 스 레 드 2 는 integers 요소 의 변 화 를 감지 할 수 있 습 니 다. 이것 은 volatile 키워드 의 가시 적 제약 때 문 입 니 다. 스 레 드 1 대 integers 의 수정 은 메 인 저장 소 에 있 는 integers 에 즉시 반응 하고 스 레 드 2 는 integers 변 수 를 사용 해 야 할 때 스 레 드 2 가 메 인 저장 소 에 있 는 integers 가 가리 키 는 대상 에 접근 하도록 강제 합 니 다.그래서 이때 스 레 드 2 는 integers 중의 요소 변 화 를 모니터링 할 수 있다.
여기 서 선 volatile 키워드 의 의 미 를 소개 해 야 합 니 다. volatile 의 한 역할 은 모든 스 레 드 가 volatile 에 의 해 수 정 된 변 수 를 방문 할 때 바로 메 인 저장 소 에 기록 하 는 것 입 니 다. 다른 스 레 드 는 volatile 에 의 해 수 정 된 변 수 를 읽 을 때 메 인 저장 소 에서 다시 읽 어야 합 니 다. 그러면 다른 스 레 드 (스 레 드 2) 를 보장 합 니 다.방문 한 integers 변 수 는 메 인 메모리 의 최신 입 니 다.
그래서 수 정 된 코드 도 완벽 하지 않다.
이 유 는 위 에서 말 한 스 레 드 2 가 메 인 메모리 의 최신 에 만 접근 할 수 있 기 때 문 입 니 다. 스 레 드 2 가 메 인 메모리 의 최신 integers 에 접근 한 후에 integers 변 수 를 판단 하기 전에 스 레 드 1 은 integers 에 요 소 를 추가 하 는 것 을 다시 완 료 했 고 스 레 드 2 는 최신 메 인 메모리 데이터 에 접근 하여 스 레 드 2 코드 만 계속 실행 할 것 입 니 다.메 인 저장 소 에 있 는 데 이 터 를 다시 방문 하지 않 습 니 다. 이 로 인해 스 레 드 2 가 integers 가 6 개 요소 일 때 만 integers 요소 가 5 개 라 는 힌트 를 보 냅 니 다.
 
프로젝트 1 수 정 된 수정: 자물쇠 사용
정확 해 보 이 는 코드
코드 논리: 스 레 드 1 이 integers 를 수정 한 이상 스 레 드 1 은 최신 integers 상 태 를 알 수 있 습 니 다. 스 레 드 2 는 integers 요소 의 수량 이 5 가 아 닐 때 lock 잠 금 으로 기다 리 고 스 레 드 1 은 실행 과정 에서 integers 요소 의 개 수 를 판단 하 며 5 이면 lock 을 잠 금 으로 하 는 스 레 드 를 깨 웁 니 다.
public class Demo1_2 {

	public static void main(String[] args) throws Exception {
            List integers = new ArrayList<>(10);
		final Object lock = new Object();
		new Thread(() -> {
			synchronized (lock) {
				for (int i = 0; i < 10; i++) {					
						integers.add(i);
						if (5 == integers.size()) {
							lock.notify();
						}					
					System.out.println("thread1 -> current integers size = " + integers.size());
				}
			}
		}).start();

		new Thread(() -> {
			if (5 != integers.size()) {
				synchronized (lock) {
					try {
						lock.wait();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
			System.out.println("thread2 -> integers size equals to 5");
		}).start();
	}
}

코드 가 정확 하지 않 은 이 유 는 스 레 드 1 에서 lock 을 잠 금 으로 실행 하 는 코드 가 for 순환 이기 때문에 integers 요소 가 5 일 때 깨 어 났 지만 스 레 드 1 은 for 순환 이 끝 날 때 까지 lock 자 물 쇠 를 차지 하기 때문에 스 레 드 2 는 자 물 쇠 를 얻 을 수 없습니다. 라인 1 이 완전히 끝 난 후에 스 레 드 2 는 lock 자 물 쇠 를 얻 을 수 있 습 니 다.수정 방법 은 간단 합 니 다. 스 레 드 1 의 synchronized (lock) 를 if 판단 에 넣 고 진행 하면 됩 니 다.
메모: 스 레 드 1 이 integers 를 5 개 요소 에 추가 하여 lock. notify () 를 진행 할 수 있 습 니 다. 그러나 스 레 드 2 가 실행 되 지 않 아 스 레 드 2 가 실 행 될 때 wait 가 깨 어 나 지 않 아 프로그램 이 정상적으로 끝 날 수 없습니다.이 문제 의 수정 은 스 레 드 1 에서 수면 을 취하 고 스 레 드 2 를 먼저 실행 하도록 강제 하 는 것 이다.
 
또 하나의 자물쇠 없 는 실현 방식 이 있다.
CountDownlatch 사용 하기
public class Demo1_4 {

	public static void main(String[] args) throws Exception {
		List integers = new ArrayList<>(10);
		CountDownLatch latch = new CountDownLatch(1);
		new Thread(() -> {
			try {
				TimeUnit.SECONDS.sleep(1);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			for (int i = 0; i < 10; i++) {

				integers.add(i);
				if (5 == integers.size()) {
					latch.countDown();
				}
				System.out.println("thread1 -> current integers size = " + integers.size());
			}
		}).start();

		new Thread(() -> {
			if (5 != integers.size()) {
				try {
					latch.await();
					System.out.println("thread2 -> integers size equals to 5");
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}).start();
	}
}

 
요구 향상: 스 레 드 2 알림 이 완 료 된 후에 스 레 드 1 은 요 소 를 계속 추가 할 수 있 습 니 다.
이중 wait 와 notify 를 사용 하여 스 레 드 1 에서 integers 요소 의 수량 이 5 일 때 스 레 드 2 를 깨 우 고 자신 이 lock. wait () 자 물 쇠 를 풀 어 스 레 드 2 가 자 물 쇠 를 얻 을 수 있 도록 합 니 다. 스 레 드 2 가 실 행 된 후에 스 레 드 2 가 스 레 드 1 을 깨 우 고 스 레 드 2 알림 이 완 료 된 후에 라인 1 이 계속 실행 되 는 기능 을 실현 합 니 다.
public class Demo1_3 {
	public static void main(String[] args) throws Exception {
		List integers = new ArrayList<>(10);
		final Object lock = new Object();
		new Thread(() -> {
			try {
				TimeUnit.SECONDS.sleep(1);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			synchronized (lock) {
				for (int i = 0; i < 10; i++) {

					integers.add(i);
					if (5 == integers.size()) {
						lock.notify();
						try {
							lock.wait();
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
					}
					System.out.println("thread1 -> current integers size = " + integers.size());
				}
			}
		}).start();

		new Thread(() -> {
			if (5 != integers.size()) {
				synchronized (lock) {
					try {
						lock.wait();
						System.out.println("thread2 -> integers size equals to 5");
						lock.notify();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
		}).start();
	}
}

미 완성 계속...

좋은 웹페이지 즐겨찾기