자바 멀티스레딩 카테고리에는 21개의 멀티스레딩 글이 있는데, 그 21개의 글에는 많은 내용이 담겨있습니다. 개인적으로 내용이 많고 복잡한 지식을 배울수록 심오한 요약이 필요하다고 생각합니다. 당신이 그것을 깊이 기억하고 지식을 당신의 것으로 바꿀 수 있도록. 이번 글에서는 멀티스레딩 문제를 주로 요약해서 40가지 멀티스레딩 문제를 나열해보겠습니다.
이러한 멀티스레딩 문제 중 일부는 주요 웹사이트에서 발생하고 일부는 내 생각에서 발생합니다. 인터넷에 몇 가지 질문이 있을 수도 있고, 몇 가지 질문에 대한 답변이 있을 수도 있고, 모든 네티즌이 읽은 것도 있을 수 있지만, 이 글을 쓰는 초점은 모든 질문에 대해 본인이 이해한 대로 답변하는 것입니다. 온라인에서 답변을 보지 마십시오. 일부 질문이 틀렸을 수도 있습니다. 제 의견을 바로잡아 주시기를 바랍니다.
1. 멀티스레딩의 용도는 무엇인가요?
많은 사람들에게 말도 안 되는 질문으로 보일 수 있습니다. 멀티스레딩을 사용할 수 있는 한, 그것이 무슨 소용이 있습니까? 내 생각에는 이 대답이 훨씬 더 말도 안 되는 것 같다. 소위 "있는 것을 아는 것, 왜인지 아는 것", "사용하는 방법을 아는 것"은 단지 "있는 것을 아는 것", "왜 사용하는지 아는 것"은 "있는 것을 아는 것"입니다. "어떨지 아는 것, 왜 그런지 아는 것"의 수준은 "어떠한지 아는 것"이라고 할 수 있습니다. 지식 포인트를 자유롭게 사용할 수 있습니다. 좋습니다. 이 문제에 대한 내 견해를 이야기해 보겠습니다.
(1) 멀티코어 CPU 활용
산업이 발전함에 따라 오늘날의 노트북, 데스크탑, 심지어 상용 애플리케이션 서버도 최소한 4코어, 8코어 또는 심지어 16코어인 경우가 많습니다. 듀얼코어 CPU는 50%가 낭비되고, 4코어 CPU에서는 75%가 낭비됩니다. 단일 코어 CPU의 소위 "멀티 스레딩"은 가짜 멀티 스레딩입니다. 프로세서는 동시에 로직의 일부만 처리하지만 스레드 간 전환이 더 빠르며 여러 스레드가 있는 것처럼 보입니다. "동시" 실행 중입니다. 멀티 코어 CPU의 멀티 스레딩은 진정한 멀티 스레딩으로, 여러 로직이 동시에 작동할 수 있도록 하여 멀티 코어 CPU를 최대한 활용하는 목적을 달성할 수 있습니다. CPU의.
(2) 차단 방지
프로그램 실행 효율성의 관점에서 볼 때, 단일 코어 CPU는 멀티스레딩을 활용하지 못할 뿐만 아니라, 단일 코어 CPU에서 멀티스레드를 실행하면 스레드 컨텍스트로 이어지기 때문에 프로그램의 전반적인 효율성도 감소합니다. 스위칭. 그러나 단일 코어 CPU의 경우 차단을 방지하려면 여전히 멀티스레딩을 사용해야 합니다. 단일 코어 CPU가 단일 스레드를 사용하는 경우 이 스레드가 차단되는 한, 예를 들어 특정 데이터를 원격으로 읽고 피어가 아직 반환되지 않았고 시간 제한을 설정하지 않은 경우 전체 프로그램이 데이터가 반환되기 전에 차단됩니다. 멀티 스레딩을 사용하면 여러 스레드가 동시에 실행되므로 데이터 읽기 시 한 스레드의 코드 실행이 차단되더라도 다른 작업의 실행에는 영향을 미치지 않습니다.
(3) 모델링이 쉽다
이것은 또 다른 덜 분명한 이점입니다. 대규모 작업 A, 단일 스레드 프로그래밍이 있다고 가정하면 고려해야 할 것이 많고 전체 프로그램 모델을 구축하는 것이 번거롭습니다. 하지만 이 큰 작업 A를 여러 개의 작은 작업인 작업 B, 작업 C, 작업 D로 나누고 각각 프로그램 모델을 설정하고 이러한 작업을 멀티 스레드를 통해 별도로 실행하면 훨씬 간단해질 것입니다.
2. 스레드 생성 방법
비교적 일반적인 질문으로 일반적으로 두 가지 유형이 있습니다.
(1) 스레드 클래스 상속
(2) Runnable 인터페이스 구현
어느 쪽이 더 낫다고 하면 후자가 더 낫다는 것은 말할 필요도 없습니다. 왜냐하면 인터페이스를 구현하는 방식이 클래스를 상속하는 방식보다 더 유연하고, 프로그램 간의 결합도 줄일 수 있는 것도 인터페이스 지향 프로그래밍의 핵심이기 때문입니다. 디자인 패턴의 6가지 원칙 중 하나입니다.
3. start() 메소드와 run() 메소드의 차이점
start() 메소드를 호출해야만 멀티스레딩 기능이 나타나며, 서로 다른 스레드의 run() 메소드에 있는 코드가 교대로 실행됩니다. run() 메서드만 호출하는 경우에도 코드는 동기식으로 실행됩니다. 다른 스레드가 해당 run() 메서드의 코드를 실행할 수 있으려면 한 스레드의 run() 메서드에 있는 모든 코드가 실행될 때까지 기다려야 합니다.
4. Runnable 인터페이스와 Callable 인터페이스의 차이점
이것은 다소 깊은 질문이며 Java 프로그래머가 얻을 수 있는 지식의 폭을 보여줍니다.
Runnable 인터페이스의 run() 메소드의 반환 값은 void이고, 이것이 수행하는 작업은 순전히 run() 메소드의 코드를 실행하는 것입니다. Callable 인터페이스의 call() 메소드는 반환 값을 가지며 일반입니다. , 비동기 실행 결과를 얻기 위해 Future 및 FutureTask와 함께 사용할 수 있습니다.
이는 실제로 매우 유용한 기능입니다. 멀티 스레딩이 싱글 스레딩보다 더 어렵고 복잡한 중요한 이유는 멀티 스레딩이 알려지지 않은 항목으로 가득 차 있기 때문입니다. 스레드가 실행된 기간은 얼마나 됩니까? 스레드가 실행될 때 우리가 기대하는 데이터가 할당되었습니까? 알 수 있는 방법은 없습니다. 우리가 할 수 있는 일은 이 멀티스레드 작업이 완료될 때까지 기다리는 것뿐입니다. Callable+Future/FutureTask는 멀티스레드 작업의 결과를 얻을 수 있고, 대기 시간이 너무 길어서 필요한 데이터를 얻지 못한 경우 스레드의 작업을 취소할 수 있어 매우 유용합니다.
5. CyclicBarrier와 CountDownLatch의 차이점
java.util.concurrent 아래에서 약간 비슷해 보이는 두 클래스를 사용하여 코드가 특정 지점까지 실행된다는 것을 나타낼 수 있습니다. 두 클래스의 차이점은 다음과 같습니다.
(1) CyclicBarrier 스레드가 특정 지점까지 실행된 후 스레드는 실행을 중지합니다. 모든 스레드가 이 지점에 도달할 때까지 모든 스레드는 다시 실행되지 않으며, 그 후에는 스레드가 특정 지점까지 실행됩니다. 특정 값 -1, 스레드는 계속 실행됩니다.
(2) CyclicBarrier는 하나의 작업만 호출할 수 있고, CountDownLatch는 여러 작업을 호출할 수 있습니다
(3) CyclicBarrier는 재사용이 가능하지만, CountDownLatch는 재사용이 불가능합니다. count 값이 0이면 CountDownLatch는 재사용이 불가능합니다
6. 휘발성 키워드의 역할
멀티스레딩을 배우고 적용하는 모든 Java 프로그래머가 마스터해야 하는 매우 중요한 문제입니다. 휘발성 키워드의 역할을 이해하기 위한 전제 조건은 Java 메모리 모델을 이해하는 것입니다. 여기서는 Java 메모리 모델에 대해 언급하지 않겠습니다. 휘발성 키워드에는 두 가지 주요 기능이 있습니다.
(1) 멀티스레딩은 주로 가시성과 원자성의 두 가지 특성을 중심으로 진행됩니다. 휘발성 키워드로 수정된 변수는 멀티스레드 간의 가시성을 보장합니다. 즉, 휘발성 변수를 읽을 때마다 최신 데이터여야 합니다
(2) 코드의 기본 실행은 우리가 보는 고급 언어(Java 프로그램)만큼 간단하지 않습니다. 실행은 Java 코드입니다.->바이트코드-->바이트코드에 따라 해당 C/C++를 실행합니다. ->C/C++ 코드는 어셈블리 언어로 컴파일됩니다.-> 실제로는 더 나은 성능을 얻기 위해 JVM이 명령을 재정렬할 수 있으며 멀티스레딩 문제에서는 예상치 못한 문제가 발생할 수 있습니다. 휘발성을 사용하면 금지 의미 체계가 재정렬되어 코드 실행 효율성도 어느 정도 감소합니다.
실용적인 관점에서 휘발성의 중요한 역할은 CAS와 결합하여 원자성을 보장하는 것입니다. 자세한 내용은 AtomicInteger와 같은 java.util.concurrent.atomic 패키지 하위 클래스를 참조하세요.
7. 스레드 안전성이란
이것은 또 다른 이론적 질문이며 다양한 답변이 있습니다. 제가 가장 잘 설명하는 답변을 드리겠습니다. 코드가 여러 스레드에서 실행되고 단일 스레드에서 실행되면 항상 동일한 결과를 얻게 됩니다. 그러면 코드가 스레드로부터 안전합니다.
이 질문에 대해 언급할 가치가 있는 점은 스레드 안전성에는 여러 수준이 있다는 것입니다.
(1) 불변
String, Integer, Long과 같은 클래스는 모두 최종 유형이므로 새 클래스를 생성하지 않는 한 어떤 스레드도 해당 값을 변경할 수 없습니다. 따라서 이러한 불변 객체는 동기화 수단 없이 멀티 스레드 환경에서 직접 사용할 수 있습니다. 🎜>
(2) 절대적인 스레드 안전성 런타임 환경에 관계없이 호출자에게는 추가 동기화 조치가 필요하지 않습니다. 이를 달성하려면 일반적으로 많은 추가 비용을 지불해야 합니다. 실제로 스레드로부터 안전하다고 표시된 대부분의 Java 클래스는 스레드로부터 안전하지 않습니다. 그러나 Java에는 완전히 스레드로부터 안전한 클래스도 있습니다. CopyOnWriteArrayList 및 CopyOnWriteArraySet과 같이 안전합니다. (3) 스레드의 상대적 안전성 상대적 스레드 안전성은 우리가 일반적으로 스레드 안전성이라고 부르는 것입니다. Vector와 마찬가지로 추가 및 제거 메서드는 원자성 작업이므로 중단되지 않지만 스레드가 Vector를 순회하는 경우 이를 추가하는 스레드가 있는 경우 이것으로 제한됩니다. 벡터와 동시에 ConcurrentModificationException은 99%의 경우에 발생하며 이는 빠른 실패 메커니즘입니다. (4) 스레드는 안전하지 않습니다 이에 대해서는 말할 것도 없습니다. ArrayList, LinkedList, HashMap 등은 모두 스레드에 안전하지 않은 클래스입니다8. Java에서 스레드 덤프 파일을 얻는 방법
무한 루프, 교착 상태, 차단 및 느린 페이지 열기와 같은 문제의 경우 스레드 덤핑이 문제를 해결하는 가장 좋은 방법입니다. 소위 스레드 덤프는 스레드 스택을 얻는 데 두 가지 단계가 있습니다. (1) 스레드의 pid를 얻으려면 jps 명령을 사용할 수도 있습니다. Linux 환경에서는 ps -ef | grep java (2) 스레드 스택을 인쇄하려면 jstack pid 명령을 사용할 수도 있습니다. Linux 환경에서는 kill -3 pid를 사용할 수도 있습니다. 또한 Thread 클래스는 스레드 스택을 얻는 데에도 사용할 수 있는 getStackTrace() 메서드를 제공합니다. 이는 인스턴스 메서드이므로 이 메서드를 특정 스레드 인스턴스에 바인딩할 때마다 특정 스레드가 현재 실행 중인 스택을 가져옵니다.9. 스레드에서 런타임 예외가 발생하면 어떻게 되나요?
이 예외가 포착되지 않으면 스레드 실행이 중지됩니다. 또 다른 중요한 점은 이 스레드가 특정 개체에 대한 모니터를 보유하고 있으면 개체 모니터가 즉시 해제된다는 것입니다.
10. 두 스레드 간 데이터 공유 방법
스레드 간에 객체를 공유한 다음 wait/notify/notifyAll, wait/signal/signalAll을 통해 깨어나고 기다리면 됩니다. 예를 들어 차단 대기열 BlockingQueue는 스레드 간에 데이터를 공유하도록 설계되었습니다
11. 수면 방식과 대기 방식의 차이점은 무엇인가요?
자주 묻는 질문입니다. sleep 메서드와 wait 메서드 모두 일정 시간 동안 CPU를 포기하는 데 사용할 수 있습니다. 차이점은 스레드가 개체의 모니터를 보유하는 경우에는 sleep 메서드를 포기하지 않는다는 것입니다. 이 개체의 모니터와 wait 메서드는 개체의 모니터를 포기합니다
12. 생산자-소비자 모델의 역할은 무엇인가요
이 질문은 매우 이론적이지만 매우 중요합니다.
(1) 생산자의 생산능력과 소비자의 소비능력의 균형을 통해 전체 시스템의 운영 효율성을 높이는 것이 생산자-소비자 모델의 가장 중요한 역할입니다
(2) 생산자-소비자 모델의 부수적인 기능인 디커플링은 생산자와 소비자 사이의 연결이 적을수록 상호 제약 없이 독립적으로 발전할 수 있다는 것을 의미합니다.
13. ThreadLocal의 용도는 무엇인가요?
간단히 말해서 ThreadLocal은 공간을 시간으로 교환하는 방법입니다. 각 스레드는 데이터를 격리하고 공유하지 않는 개방형 주소 메서드로 구현된 ThreadLocal.ThreadLocalMap을 유지합니다. 당연히 스레드 안전성 문제는 없습니다.
14. wait() 메소드와 inform()/notifyAll() 메소드가 동기화된 블록에서 호출되는 이유는 무엇입니까? 이는 JDK에서 필수입니다. wait() 메소드와 inform()/notifyAll() 메소드 모두
를 호출하기 전에 객체 잠금을 획득해야 합니다.
15. 객체 모니터를 포기할 때 wait() 메서드와 inform()/notifyAll() 메서드의 차이점은 무엇인가요? 객체 모니터를 포기할 때 wait() 메서드와 inform()/notifyAll() 메서드의 차이점은 wait() 메서드는 객체 모니터를 즉시 해제하는 반면, inform()/notifyAll() 메서드는 객체 모니터를 기다린다는 점이다. 실행을 완료하기 위한 스레드의 나머지 코드는 개체 모니터를 포기합니다.
16. 스레드 풀을 사용하는 이유
스레드 개체를 재사용하려면 스레드를 자주 생성하고 삭제하지 마세요. 또한 스레드 풀을 사용하면 프로젝트에 따라 동시성 수를 유연하게 제어할 수도 있습니다.
17. 스레드에 개체 모니터가 있는지 확인하는 방법
나는 또한 인터넷에서 멀티스레딩 인터뷰 질문을 보고 스레드가 객체 모니터를 보유하는지 여부를 결정하는 방법이 있다는 것을 배웠습니다. Thread 클래스는 객체 obj의 모니터가 있는 경우에만holdLock(Object obj) 메소드를 제공합니다. 특정 스레드에 의해 유지되는 경우에만 true를 반환합니다. 이는 "특정 스레드"가 현재 스레드를 참조한다는 의미인 정적 메서드입니다.
18. 동기화와 재진입 잠금의 차이점
동기화는 if, else, for, while과 동일한 키워드이며 ReentrantLock은 클래스입니다. 이것이 둘 사이의 본질적인 차이점입니다. ReentrantLock은 클래스이기 때문에 동기화보다 더욱 유연한 기능을 제공합니다. 상속이 가능하고, 메서드를 가질 수 있으며, 다양한 클래스 변수를 가질 수 있습니다. 동기화에 비해 ReentrantLock의 확장성은 여러 가지 점에서 반영됩니다. > (1) ReentrantLock은 잠금 획득 대기 시간을 설정하여 교착 상태를 피할 수 있습니다
(2) ReentrantLock은 다양한 잠금에 대한 정보를 얻을 수 있습니다
(3) ReentrantLock은 여러 알림을 유연하게 구현할 수 있습니다 또한 둘의 잠금 메커니즘은 실제로 다릅니다. ReentrantLock의 맨 아래 레이어는 Unsafe의 park 메소드를 호출하여 잠그고 동기화는 객체 헤더의 표시 단어에서 작동해야 합니다.
19. ConcurrentHashMap의 동시성은 무엇인가요
ConcurrentHashMap의 동시성은 세그먼트의 크기입니다. 이는 최대 16개의 스레드가 동시에 작동할 수 있음을 의미합니다. 이는 Hashtable에 비해 ConcurrentHashMap의 가장 큰 장점이기도 합니다. Hashtable 데이터의 내용을 얻기 위해 동시에 스레드를 사용합니까?
20. ReadWriteLock이란
우선, ReentrantLock이 나쁘다는 것이 아니라 ReentrantLock이 어떤 경우에는 제한이 있다는 점을 분명히 해두겠습니다. ReentrantLock을 사용하면 스레드 A가 데이터를 쓰고 스레드 B가 데이터를 읽는 경우 발생하는 데이터 불일치를 방지할 수 있습니다. 그러나 스레드 C가 데이터를 읽고 있으면 스레드 D도 데이터를 읽어도 데이터가 변경되지 않습니다. 잠금은 필요하지 않지만 여전히 잠겨 있어 프로그램 성능이 저하됩니다.
이로 인해 읽기-쓰기 잠금 ReadWriteLock이 탄생했습니다. ReadWriteLock은 읽기-쓰기 잠금 인터페이스입니다. ReentrantReadWriteLock은 읽기와 쓰기의 분리를 실현하는 ReadWriteLock 인터페이스입니다. 읽기 잠금은 공유되고 쓰기 잠금은 배타적입니다. 읽기와 쓰기, 쓰기와 읽기, 쓰기와 쓰기는 상호 배타적이므로 읽기와 쓰기 성능이 향상됩니다.
21. 퓨처태스크란
실제로 앞서 언급했듯이 FutureTask는 비동기 작업 작업을 나타냅니다. Callable의 특정 구현 클래스를 FutureTask에 전달할 수 있으며, 이 비동기 작업 작업의 결과를 얻기 위해 대기하고 완료 여부를 확인하고 작업을 취소하는 등의 작업을 수행할 수 있습니다. 물론 FutureTask는 Runnable 인터페이스의 구현 클래스이기도 하므로 FutureTask도 스레드 풀에 배치할 수 있습니다.
22. 리눅스 환경에서 어떤 스레드가 CPU를 가장 오래 사용하는지 찾는 방법
이것은 좀 더 실용적인 질문이고, 꽤 의미가 있다고 생각합니다. 이렇게 할 수 있습니다:
(1) 프로젝트의 pid, jps 또는 ps -ef | grep java를 가져옵니다. 이는 이전에 언급되었습니다.
(2) top -H -p pid, 순서는 변경할 수 없습니다
그러면 현재 프로젝트와 각 스레드가 차지하는 CPU 시간 비율이 인쇄됩니다. 여기에 입력된 것은 운영 체제의 기본 스레드의 스레드 번호인 LWP입니다. 내 노트북은 Linux 환경에 Java 프로젝트를 배포하지 않았으므로 네티즌이 시연할 수 있는 방법이 없습니다. 회사에서 Linux 환경을 사용하여 프로젝트를 배포하는 경우 한 번 시도해 볼 수 있습니다.
"top -H -p pid" + "jps pid"를 사용하면 CPU를 많이 차지하는 스레드의 스레드 스택을 쉽게 찾을 수 있으며, 이로 인해 CPU 점유율이 높아지는 원인을 찾을 수 있습니다. 이는 대개 잘못된 코드 작업으로 인해 발생합니다. 무한 루프로 이어지게 됩니다.
마지막으로 언급할 점은 "top -H -p pid"로 인쇄된 LWP는 10진수이고, "jps pid"로 인쇄된 로컬 스레드 번호는 16진수로 변환하면 해당 스레드를 찾을 수 있다는 것입니다. 현재 스레드가 스택되어 있습니다.
23. 교착 상태를 일으키는 Java 프로그래밍 프로그램을 작성하세요
처음 이 주제를 봤을 때 정말 좋은 질문이라는 생각이 들었습니다. 많은 사람들이 교착 상태가 무엇인지 알고 있습니다. 스레드 A와 스레드 B는 서로의 잠금을 기다리므로 프로그램이 무한 루프에서 계속됩니다. 물론 이것에 국한되어 어떻게 교착상태 프로그램을 작성하느냐고 묻는다면, 직설적으로 말하면 이 상황은 교착상태가 무엇인지 이해하지 못한다는 뜻이다. 실제로는 교착 상태 문제가 발생합니다.
교착 상태가 무엇인지 진정으로 이해하려면 이 문제는 실제로 어렵지 않습니다. 몇 단계만 거치면 됩니다.
(1) 두 스레드는 각각 두 개의 Object 개체인 lock1과 lock2를 보유합니다. 이 두 잠금은 동기화된 코드 블록에 대한 잠금 역할을 합니다.
(2) 스레드 1의 run() 메소드에 있는 동기화 코드 블록은 먼저 lock1의 객체 잠금인 Thread.sleep(xxx)을 획득합니다. 시간은 너무 길 필요는 없으며 50밀리초이면 거의 충분합니다. lock2의 객체 잠금입니다. 이는 주로 스레드 1이 lock1 및 lock2 객체의 객체 잠금을 지속적으로 획득하는 것을 방지하기 위해 수행됩니다
(3) Thread 2의 실행) (메소드의 동기화 코드 블록은 먼저 lock2의 객체 잠금을 획득한 후 lock1의 객체 잠금을 획득합니다. 물론 이때 lock1의 객체 잠금은 스레드 1에 의해 잠겨 있습니다. , 스레드 2는 lock1의 개체 잠금을 해제하는 스레드 1
을 기다려야 합니다. 이런 방식으로 스레드 1이 "sleep"된 후 스레드 2는 lock2의 개체 잠금을 획득했습니다. 스레드 1은 lock2의 개체 잠금을 획득하려고 시도하고 이때 교착 상태가 발생합니다. 공간을 많이 차지하므로 코드를 작성하지 않겠습니다. 이 기사에는 위 단계의 코드 구현인 Java Multithreading 7: Deadlock이 포함되어 있습니다.
24. 차단된 스레드를 깨우는 방법
wait(), sleep() 또는 Join() 메소드 호출로 인해 스레드가 차단된 경우 스레드가 IO 차단을 발견하면 스레드를 중단하고 깨울 수 있습니다. 운영 체제가 구현되었으므로 Java 코드에는 운영 체제에 직접 연결할 수 있는 방법이 없습니다.
25. 불변 객체는 멀티스레딩에 어떻게 도움이 되나요? 앞에서 언급했듯이 불변 객체는 객체의 메모리 가시성을 보장합니다. 불변 객체를 읽는 데는 추가 동기화 방법이 필요하지 않으므로 코드 실행 효율성이 향상됩니다.
26. 멀티스레드 컨텍스트 스위칭이란
다중 스레드 컨텍스트 전환은 이미 실행 중인 한 스레드에서 CPU 실행 권한을 얻기 위해 준비되어 대기 중인 다른 스레드로 CPU 제어를 전환하는 프로세스를 의미합니다.
27. 작업 제출 시 스레드 풀 대기열이 가득 차면 어떻게 되나요?
제한되지 않은 대기열인 LinkedBlockingQueue를 사용하는 경우에는 문제가 되지 않습니다. LinkedBlockingQueue를 사용하는 경우 거의 무한 대기열로 간주될 수 있으므로 작업을 계속해서 차단 대기열에 추가하고 실행을 기다립니다. 예를 들어 ArrayBlockingQueue의 경우 작업은 먼저 ArrayBlockingQueue에 추가됩니다. ArrayBlockingQueue가 가득 차면 거부 정책 RejectedExecutionHandler가 전체 작업을 처리하는 데 사용됩니다.
28. Java에서 사용되는 스레드 스케줄링 알고리즘은 무엇인가요?
선제적. 스레드가 CPU를 모두 사용한 후 운영 체제는 스레드 우선순위, 스레드 기아 및 기타 데이터를 기반으로 총 우선순위를 계산하고 실행을 위해 다음 시간 조각을 특정 스레드에 할당합니다.
29. Thread.sleep(0)의 기능은 무엇인가요
이 질문은 위의 질문과 연관되어 있어서 하나로 연결해 봤습니다. Java는 선점형 스레드 스케줄링 알고리즘을 사용하므로 특정 스레드가 CPU 제어권을 획득하는 경우가 발생할 수 있습니다. 우선순위가 낮은 일부 스레드가 CPU 제어권을 획득하도록 하려면 Thread.sleep( 0)을 사용하여 작업을 수동으로 트리거할 수 있습니다. 운영 체제는 타임 슬라이스를 할당하는데, 이는 CPU 제어의 균형을 맞추는 작업이기도 합니다.
30.스핀이란
많은 동기화된 코드는 매우 간단한 코드일 뿐이며 실행 시간이 매우 빠릅니다. 스레드 차단에는 사용자 모드와 커널 모드 간 전환이 포함되므로 이때 대기 중인 모든 스레드를 잠그는 것은 가치 있는 작업이 아닐 수 있습니다. 동기화된 코드는 매우 빠르게 실행되므로 잠금을 기다리는 스레드가 차단되는 것을 방지하는 것이 좋지만 동기화된 경계에서 바쁜 루프를 수행하는 것이 스핀입니다. 여러 번 사용 중인 루프를 수행하고 잠금을 얻지 못한 경우 다시 차단하는 것이 더 나은 전략일 수 있습니다.
31. 자바 메모리 모델이란
Java 메모리 모델은 Java 메모리에 대한 다중 스레드 액세스 사양을 정의합니다. Java 메모리 모델에 대한 완전한 설명은 몇 문장으로 명확하게 설명할 수 없습니다. Java 메모리 모델의 일부를 간략하게 요약하겠습니다.
(1) 자바 메모리 모델은 메모리를 메인 메모리와 작업 메모리로 나눈다. 클래스의 상태, 즉 클래스 간에 공유되는 변수는 주 메모리에 저장됩니다. Java 스레드는 이러한 변수를 주 메모리에서 사용할 때마다 주 메모리의 변수를 한 번 읽어서 존재하게 합니다. 자신의 작업 메모리에 복사본이 있습니다. 자신의 스레드 코드를 실행할 때 이러한 변수를 사용하면 자신의 작업 메모리에서 복사본을 작동하게 됩니다. 스레드 코드가 실행된 후 최신 값이 메인 메모리에 업데이트됩니다
(2) 메인 메모리와 작업 메모리의 연산 변수에 대해 여러 원자 연산이 정의되어 있습니다
(3) 휘발성 변수 사용 규칙을 정의합니다
(4) 이전 발생, 즉 이전 발생 원칙은 작업 A가 작업 B보다 먼저 발생해야 한다는 몇 가지 규칙을 정의합니다. 예를 들어 동일한 스레드에서 제어 흐름 앞에 있는 코드는 뒤에 있는 코드보다 먼저 발생해야 합니다. 제어 흐름, 잠금 해제 잠금 해제 작업은 동일한 잠금에 대한 후속 잠금 작업보다 선행해야 합니다. 이러한 규칙이 충족되는 한 코드 조각이 모든 상황을 충족하지 않는 경우 추가 동기화 조치가 필요하지 않습니다. 규칙 앞에 이 코드가 있으면 스레드가 안전하지 않아야 합니다
32. CAS란
CAS는 Compare and Swap의 약자로 비교하고 대체한다는 뜻입니다. 세 개의 피연산자, 즉 메모리 값 V, 이전 기대 값 A, 수정될 값 B가 있다고 가정합니다. 기대 값 A와 메모리 값 V가 동일한 경우에만 메모리 값은 B로 수정되고 true는 그렇지 않으면 아무것도 하지 않고 false를 반환합니다. 물론 CAS는 매번 얻은 변수가 주 메모리의 최신 값인지 확인하기 위해 휘발성 변수와 협력해야 합니다. 그렇지 않으면 특정 스레드에 대해 이전 예상 값 A는 항상 변하지 않는 값 A입니다. CAS 작업이 실패하면 결코 성공할 수 없습니다.
33. 낙관적 잠금과 비관적 잠금이란
(1) 낙관적 잠금(Optimistic lock): 이름처럼 동시 작업으로 인해 발생하는 스레드 안전성 문제에 대해 낙관적입니다. 낙관적 잠금은 경쟁이 항상 발생하지 않는다고 믿기 때문에 잠금을 유지할 필요가 없으며 이를 비교하고 교체합니다. two 원자성 작업으로서 이 작업은 메모리의 변수를 수정하려고 시도합니다. 실패하면 충돌이 발생하고 해당 재시도 논리가 있어야 합니다.
(2) 비관적 잠금: 이름과 같이 동시 작업으로 인해 발생하는 스레드 안전성 문제에 대해 비관적입니다. 비관적 잠금은 경쟁이 항상 발생한다고 믿기 때문에 리소스가 작동될 때마다 배타적 잠금을 유지합니다. 무슨 일이 있어도 리소스를 잠근 후 직접 운영할 수 있습니다.
34.AQS란
AQS에 대해 간단히 이야기해 보겠습니다. AQS의 전체 이름은 AbstractQueuedSynchronizer이며 이는 추상 대기열 동기화 장치로 번역되어야 합니다.
java.util.concurrent의 기반이 CAS라면 AQS는 전체 Java 동시성 패키지의 핵심이며 ReentrantLock, CountDownLatch, Semaphore 등이 모두 사용합니다. AQS는 실제로 모든 항목을 ReentrantLock과 같은 양방향 대기열 형식으로 연결합니다. 대기 중인 모든 스레드는 항목에 배치되고 이전 스레드가 ReentrantLock을 사용하는 경우 실제로는 양방향 대기열이 실행되기 시작합니다.
AQS는 양방향 대기열의 모든 작업을 정의하지만 개발자에게 tryLock 및 tryRelease 메서드만 공개합니다. 개발자는 자체 구현에 따라 tryLock 및 tryRelease 메서드를 다시 작성하여 자체 동시성 기능을 달성할 수 있습니다.
35. 싱글턴 모드의 안전성
가장 흔한 질문입니다. 싱글톤 모드의 스레드 안전성은 특정 클래스의 인스턴스가 멀티 스레드 환경에서 한 번만 생성된다는 것을 의미합니다. 싱글턴 패턴을 작성하는 방법에는 여러 가지가 있습니다. 요약하자면:
(1) Hungry 스타일 싱글턴 패턴 작성 방법: 스레드 안전성
(2) 게으른 싱글턴 패턴 작성 방법: 스레드로부터 안전하지 않음
(3) 이중 확인 잠금 싱글톤 모드 작성 방법: 스레드 안전성
36. 세마포어의 기능은 무엇인가요?
세마포어는 세마포어이며, 그 기능은 특정 코드 블록의 동시성 수를 제한하는 것입니다. 세마포어에는 정수 n을 전달할 수 있는 생성자가 있습니다. 즉, 특정 코드 조각은 최대 n 스레드에서만 액세스할 수 있습니다. n이 초과되면 스레드가 이 코드 블록 실행을 마칠 때까지 기다리세요. 스레드 재진입. 이를 통해 Semaphore 생성자에 전달된 int 유형 정수 n=1이 동기화되는 것과 동일하다는 것을 알 수 있습니다.
37. Hashtable의 size() 메소드에는 "return count" 문이 하나만 있습니다. 왜 동기화해야 합니까?
이것은 제가 전에 겪었던 혼란이었습니다. 누군가 이 문제에 대해 생각해 본 적이 있는지 궁금합니다. 메서드에 여러 개의 문이 있고 모두 동일한 클래스 변수에서 작동하는 경우 다중 스레드 환경에서 잠그지 않으면 필연적으로 스레드 안전 문제가 발생합니다. 이는 이해하기 쉽지만 size() 메서드에는 분명히 있습니다. 단 하나의 문만 있는데 왜 잠궈야 합니까?
이 문제에 대해서는 일과 공부를 하면서 점차 이해하게 됐습니다. 이유는 크게 두 가지입니다.
(1) 고정 클래스의 동기화 메서드는 하나의 스레드만 동시에 실행할 수 있지만, 클래스의 비동기 메서드는 여러 스레드에서 동시에 접근할 수 있습니다. 따라서 문제가 있을 수 있습니다. 스레드 A는 데이터를 추가하기 위해 Hashtable의 put 메서드를 실행하고 있고 스레드 B는 정상적으로 Hashtable의 현재 요소 수를 읽기 위해 size() 메서드를 호출할 수 있지만 읽은 값이 그렇지 않을 수도 있습니다. 최신. 아마도 스레드 A가 데이터를 추가했지만 스레드 B가 이미 size++를 조정하지 않고 크기를 읽었으므로 스레드 B가 읽은 크기가 정확하지 않을 것입니다. size() 메서드에 동기화를 추가하면 스레드 A가 put 메서드 호출을 완료한 후에만 스레드 B가 size() 메서드를 호출할 수 있으므로 스레드 안전성이 보장됩니다.
(2) CPU는 Java 코드가 아닌 코드를 실행합니다. 이는 매우 중요하며 반드시 기억해야 합니다. Java 코드는 결국 실행을 위해 어셈블리 코드로 변환됩니다. 어셈블리 코드는 실제로 하드웨어 회로와 상호 작용할 수 있는 코드입니다. Java 코드가 한 줄만 있는 것을 보더라도, 또는 Java 코드를 컴파일한 후 생성된 바이트코드가 단 한 줄인 것을 보더라도 하위 계층에 대해 이 명령문에는 하나의 작업만 있다는 의미는 아닙니다. "반환 횟수"라는 문장이 실행을 위해 세 개의 어셈블리 문으로 변환된다고 가정하면 첫 번째 문장이 실행된 후 스레드가 전환되는 것이 전적으로 가능합니다.
38.
에 의해 호출되는 스레드 클래스 생성자와 정적 블록은 무엇입니까? 이것은 매우 까다롭고 교활한 질문입니다. 기억하세요: 스레드 클래스의 생성 메서드와 정적 블록은 새 스레드 클래스가 있는 스레드에 의해 호출되고, run 메서드의 코드는 스레드 자체에 의해 호출됩니다.
위의 설명으로 인해 혼란스러울 경우 Thread1이 Thread2에서 새 항목이고 Thread2가 기본 함수에서 새 항목이라고 가정해 보겠습니다.
(1) Thread2의 생성 메소드와 정적 블록은 메인 스레드에 의해 호출되고, Thread2의 run() 메소드는 Thread2 자체에 의해 호출됩니다
(2) Thread1의 생성 메소드와 정적 블록은 Thread2에 의해 호출되고, Thread1의 run() 메소드는 Thread1 자체에 의해 호출됩니다
39. 동기화 방식과 동기화 차단 중 어느 것이 더 좋은가요?
동기화된 블록(Synchronized Block)은 동기화된 블록 외부의 코드가 비동기적으로 실행되는 것을 의미하며, 이는 전체 메서드를 동기화하는 것보다 코드의 효율성을 향상시킵니다. 한 가지 원칙을 알아두십시오. 동기화 범위가 작을수록 좋습니다.
이를 염두에 두고 동기화 범위가 작을수록 좋지만 Java 가상 머신에는 동기화 범위를 더 크게 만드는 잠금 조정이라는 최적화 방법이 여전히 있다는 점을 언급하고 싶습니다. 예를 들어 StringBuffer는 스레드로부터 안전한 클래스입니다. 당연히 가장 일반적으로 사용되는 Append() 메서드는 코드를 작성할 때 문자열을 반복적으로 추가하므로 잠금 해제가 반복됩니다. 이는 JVM(Java Virtual Machine)이 이 스레드에서 커널 모드와 사용자 모드 사이를 반복적으로 전환해야 하므로 JVM(Java Virtual Machine)이 여러 추가 메소드를 호출하는 코드를 처리해야 하기 때문에 성능에 좋지 않습니다. 추가 작업을 추가 메서드의 시작과 끝에 추가하고 이를 대규모 동기화 블록으로 전환합니다. 이렇게 하면 잠금 및 잠금 해제 횟수가 줄어들고 코드 실행 효율성이 효과적으로 향상됩니다.
40. 동시성이 높고 작업 실행 시간이 짧은 기업에서 스레드 풀을 사용하는 방법은 무엇입니까? 동시성이 낮고 작업 실행 시간이 긴 기업에서 스레드 풀을 사용하는 방법은 무엇입니까? 동시성이 높고 비즈니스 실행 시간이 긴 기업에서 스레드 풀을 사용하는 방법은 무엇입니까?
이것은 동시 프로그래밍 인터넷에서 본 질문입니다. 이 질문을 마지막 질문에 넣었습니다. 이 질문은 매우 훌륭하고 실용적이며 매우 전문적이기 때문에 모두가 그것을 보고 생각해 볼 수 있기를 바랍니다. 이 문제에 대한 내 개인적인 의견은 다음과 같습니다.
(1) 동시성이 높고 작업 실행 시간이 짧은 기업의 경우 스레드 풀 스레드 수를 CPU 코어 수 + 1로 설정하여 스레드 컨텍스트 전환을 줄일 수 있습니다
(2) 동시성이 낮고 작업 실행 시간이 긴 비즈니스를 구별해야 합니다:
a) IO 작업, 즉 IO 집약적인 작업에 업무 시간이 장시간 집중되는 경우 IO 작업은 CPU를 점유하지 않으므로 모든 CPU를 유휴 상태로 두지 말고 스레드 수를 늘릴 수 있습니다. CPU가 더 많은 업무를 처리할 수 있도록 스레드 풀
b) 오랫동안 컴퓨팅 작업, 즉 컴퓨팅 집약적인 작업에 업무 시간이 집중되는 경우에는 (1)과 동일합니다. 스레드 컨텍스트 전환을 줄이기 위해 더 작게 설정
(3) 높은 동시성 및 긴 비즈니스 실행 시간 이러한 유형의 작업을 해결하는 열쇠는 스레드 풀이 아니라 이러한 비즈니스의 특정 데이터를 캐시할 수 있는지 확인하는 것이 첫 번째 단계입니다. 서버 추가는 세 번째입니다. 두 번째 단계에서는 Thread Pool 설정은 (2)를 참고하세요. 마지막으로, 긴 비즈니스 실행 시간 문제를 분석하여 미들웨어를 사용하여 작업을 분할하고 분리할 수 있는지 확인해야 할 수도 있습니다.
위 내용은 40가지 Java 멀티스레딩 문제 요약의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!