동기화 된 키워드
동기화 된, 우리는 잠금을 호출하며, 주로 메소드와 코드 블록을 잠그는 데 사용됩니다. 메소드 또는 코드 블록이 동기화되면 최대 한 스레드가 코드를 동시에 실행합니다. 여러 스레드가 동일한 객체의 잠금 메소드/코드 블록에 액세스하면 하나의 스레드 만 동시에 실행되고 나머지 스레드는 실행하기 전에 현재 스레드가 코드 세그먼트를 실행할 때까지 기다려야합니다. 그러나 나머지 스레드는 객체의 고정되지 않은 코드 블록에 액세스 할 수 있습니다.
동기화 된 주로 동기화 된 방법과 동기화 된 블록의 두 가지 방법이 포함됩니다.
동기화 된 방법
메소드 선언에 동기화 된 키워드를 추가하여 동기화 된 메소드를 선언합니다. 좋다:
공개 동기화 된 void getResult ();
동기화 된 메소드는 클래스 멤버 변수에 대한 액세스를 제어합니다. 클래스 멤버 변수의 액세스 제어를 어떻게 피합니까? 이 메소드는 동기화 된 키워드를 사용하여 메소드가 잠겨 있음을 나타냅니다. 스레드가 수정 된 메소드에 액세스하면 다른 스레드가 "독점적"인지 확인해야합니다. 각 클래스 인스턴스는 잠금에 해당합니다. 각 동기화 된 방법은 실행되기 전에 메소드의 클래스 인스턴스의 잠금을 호출해야합니다. 그렇지 않으면, 그것이 속한 실이 차단됩니다. 메소드가 실행되면 잠금은 배타적입니다. 잠금은 메소드에서 돌아올 때까지 해제되지 않으며 차단 된 스레드는 잠금을 얻을 수 있습니다.
실제로 동기화 된 방법에는 결함이 있습니다. 큰 방법을 동기화로 선언하면 효율성에 큰 영향을 미칩니다. 여러 스레드가 동기화 된 메소드에 액세스하는 경우 하나의 스레드 만 동시에 메소드를 실행하는 반면 다른 스레드는 대기해야합니다. 그러나 메소드가 동기화 된 메소드를 사용하지 않으면 모든 스레드가 동시에 실행할 수있어 총 실행 시간이 줄어 듭니다. 따라서 메소드가 여러 스레드에 의해 실행되지 않거나 리소스 공유 문제가 없다는 것을 알고 있다면 동기화 된 키워드를 사용할 필요가 없습니다. 그러나 동기화 된 키워드를 사용해야한다면 동기화 된 메소드를 동기화 된 코드 블록으로 바꿀 수 있습니다.
동기화 된 블록
동기화 된 코드 블록은 중요한 영역을 최대한 짧게 만드는 것을 제외하고는 동기화 된 방법과 동일한 역할을합니다. 다시 말해, 필요한 공유 데이터 만 보호하여 나머지 긴 코드 블록에 대해이 작업을 남깁니다. 구문은 다음과 같습니다.
동기화 된 (개체) {// 액세스 제어를 허용하는 코드} 동기화 된 키워드를 사용해야하는 경우 객체 참조를 매개 변수로 사용해야합니다. 일반적으로 우리는 종종이 매개 변수를 this.synchronized (this) {// 액세스 제어를 허용하는 코드}를 사용합니다.동기화 된 (this)에 대한 다음과 같은 이해가 있습니다.
1. 두 개의 동시 스레드가 동일한 개체 객체에서 동기화 된 (이) 동기화 된 코드 블록에 액세스하면 한 번 이내에 하나의 스레드 만 실행할 수 있습니다. 다른 스레드는 코드 블록을 실행하기 전에 현재 스레드 가이 코드 블록을 실행할 때까지 기다려야합니다.
2. 그러나 하나의 스레드가 객체의 동기화 된 (이) 동기화 코드 블록에 액세스하면 다른 스레드가 여전히 객체의 비 동기화 된 (이) 동기화 코드 블록에 액세스 할 수 있습니다.
3. 스레드가 객체의 동기화 된 (이) 동기화 코드 블록에 액세스 할 때 객체의 다른 모든 동기화 된 (이) 동기화 코드 블록에 액세스 할 수없는 다른 스레드가 차단되는 것이 특히 중요합니다.
4. 세 번째 예제는 다른 동기화 코드 블록에도 적용됩니다. 즉, 스레드가 객체의 동기화 된 (이) 동기화 코드 블록에 액세스하면이 객체의 객체 잠금을 얻습니다. 결과적으로 객체 객체의 모든 동기 코드 부분에 대한 다른 스레드 액세스가 일시적으로 차단됩니다.
잠그다
Java Multithreading에는 "Come First, Nex Meel"원칙이 있습니다. 즉, 먼저 키를 잡는 사람은 먼저 사용합니다. 우리는 자원 경쟁 문제를 피하기 위해 Java는 동기화 메커니즘을 사용하여 피하고 동기화 메커니즘은 잠금 개념을 사용하여 제어됩니다. 그렇다면 자바 프로그램에 자물쇠가 어떻게 반영됩니까? 여기서 우리는 두 가지 개념을 알아 내야합니다.
자물쇠 란 무엇입니까? 일상 생활에서는 문, 상자, 서랍 및 기타 물체에 추가 된 실러로, 다른 사람들이 엿보거나 훔치는 것을 방지하며 보호 역할을합니다. Java에서도 마찬가지입니다. 자물쇠는 물체를 보호하는 데 역할을합니다. 스레드가 독점적으로 특정 리소스를 차지하는 경우 다른 스레드를 사용하고 싶지 않지만 사용하고 싶습니까? 내가 그것을 사용하면 그것에 대해 이야기합시다!
Java 프로그램 실행 환경에서 JVM은 두 가지 유형의 스레드가 공유하는 데이터를 조정해야합니다.
1. 인스턴스 변수는 힙에 저장되었습니다
2. 방법 영역에 저장된 클래스 변수.
Java 가상 머신에서 각 객체 및 클래스는 논리적으로 모니터와 관련이 있습니다. 객체의 경우 관련 모니터는 객체의 인스턴스 변수를 보호합니다. 클래스의 경우 모니터는 클래스 변수를 보호합니다. 객체에 인스턴스 변수가 없거나 클래스에 변수가없는 경우 관련 모니터는 아무것도 모니터링하지 않습니다.
Java Virtual Machine은 모니터의 독점 모니터링 기능을 구현하기 위해 각 객체 및 클래스에 대한 잠금을 연관시킵니다. 하나의 스레드만이 가질 수있는 권한을 나타냅니다. 인스턴스 변수 또는 클래스 변수에 액세스 할 때 스레드가 잠글 필요가 없습니다. 스레드가 잠금을 취득하면 다른 스레드가 잠금을 풀기 전에 동일한 잠금을 얻는 것은 불가능합니다. 스레드는 동일한 객체를 여러 번 잠글 수 있습니다. 각 객체에 대해 Java 가상 머신은 잠금 카운터를 유지합니다. 스레드가 객체를 얻을 때마다 카운터가 1만큼 증가하고, 릴리스 될 때마다 카운터가 1만큼 줄어 듭니다. 카운터 값이 0이면 잠금이 완전히 해제됩니다.
Java 프로그래머는 자체적으로 잠금 장치를 추가 할 필요가 없으며, 객체 잠금 장치는 Java 가상 머신에서 내부적으로 사용됩니다. Java 프로그램에서는 모니터링 영역을 표시하기 위해 동기화 된 블록 또는 동기화 된 방법 만 사용하면됩니다. 모니터링 영역에 들어갈 때마다 Java Virtual Machine은 자동으로 객체 또는 클래스를 잠그게합니다.
간단한 자물쇠
동기화 된 경우 다음과 같은 자물쇠를 사용합니다.
공개 클래스 스레드 테스트 {public void test () {synchronized (this) {// do do}}}}Synchronized는 하나의 스레드만이 동시에 복용량을 실행하도록합니다. 동기화 대신 잠금을 사용하는 것은 다음과 같습니다.
공개 클래스 스레드 테스트 {lock lock = new Lock (); public void test () {lock.lock (); // 잠금 장치를 수행합니다. Unlock (); }}Lock () 메소드는 잠금 인스턴스 객체를 잠금하므로 객체의 잠금 () 메서드를 호출하는 모든 스레드가 잠금 객체의 Unlock () 메소드가 호출 될 때까지 차단됩니다.
잠긴 것은 무엇입니까?
이 질문 이전에는 동기화 된 키워드가 메소드 또는 객체에 추가되어 있는지 여부는 획득하는 잠금이 객체입니다. Java에서는 모든 객체가 잠금으로 사용될 수 있으며, 주로 다음 세 가지 측면에 반영됩니다.
동기화 방법의 경우 잠금은 현재 인스턴스 객체입니다.
동기화 메소드 블록의 경우, 잠금은 동기화 된 괄호로 구성된 객체입니다.
정적 동기화 방법의 경우 잠금은 현재 객체의 클래스 객체입니다.
먼저 다음 예를 살펴 보겠습니다.
Public Class ThreadTest_01은 runnable {@override public synchronized void run () {for (int i = 0; i <3; i ++) {system.out.println (thread.currentthread (). getName ()+"run ......"); }} public static void main (String [] args) {for (int i = 0; i <5; i ++) {new Thread (new ThreadTest_01 (), "Thread_"+i) .start (); }}}부분 실행 결과 :
Thread_2run ... Thread_2run ... Thread_4run ... Thread_4run ... Thread_3run ... Thread_3run ... Thread_3run ... Thread_3run ... Thread_2run ... Thread_4run ...
이 결과는 예상 결과와 약간 다릅니다 (이 스레드는 여기에서 실행됩니다). 논리적으로 말하면, 실행 방법과 동기화 된 키워드는 동기화 효과를 생성합니다. 이 스레드는 실행 메소드를 차례로 실행해야합니다. 위에서 언급 한 바와 같이, 동기화 된 키워드를 멤버 메소드에 추가 한 후 실제로는 멤버 메소드에 잠금입니다. 특정 요점은 멤버 메소드가 객체 잠금으로 위치하는 객체 자체를 사용하는 것입니다. 그러나이 예에서는 새로운 10 스레드 테스트 객체가 있으며 각 스레드는 자체 스레드 객체의 객체 잠금을 고정하여 동기 효과를 얻지 못합니다. 따라서 : 이러한 스레드를 동기화하려면 이러한 스레드에서 보유한 객체 잠금 장치를 공유하고 고유해야합니다!
현재 어떤 객체가 동기화되어 있습니까? 잠금은이 동기 메소드 객체를 호출하는 것입니다. 즉, ThreadTest 객체가 다른 스레드에서 동기화 메소드를 실행하면 상호 배타적입니다. 동기화의 효과를 달성하십시오. 따라서 위의 새 스레드를 변경하십시오 (New ThreadTest_01 (), "Thread_" + i) .start (); 새 스레드 (ThreadTest, "Thread_" + i) .start ();
동기화 방법의 경우 잠금은 현재 인스턴스 객체입니다.
위의 예는 동기화 된 방법을 사용합니다. 동기화 된 코드 블록을 살펴 보겠습니다.
공개 클래스 스레드 테스트 _02 스레드 {private String lock; 개인 문자열 이름; public ThreadTest_02 (문자열 이름, 문자열 잠금) {this.name = 이름; this.lock = 잠금; } @override public void run () {synchronized (lock) {for (int i = 0; i <3; i ++) {system.out.println (name+"run ......"); }}} public static void main (String [] args) {String Lock = new String ( "Test"); for (int i = 0; i <5; i ++) {new ThreadTest_02 ( "ThreadTest_"+i, lock) .start (); }}}실행 결과 :
ThreadTest_0 Run ... ThreadTest_0 Run ... ThreadTest_0 Run ... ThreadTest_1 Run ... ResidTest_1 Run ... ThreadTest_1 런 ... ThreadTest_1 Run ... ThreadTest_4 Run ... ThreadTest_4 Run ... ThreadTest_4 Run ... ThreadTest_3 Run ... ThreadTest_3 run ... ThreadTest_2 Run ...
기본 메소드에서는 문자열 객체 잠금을 생성 하고이 개체를 각 ThreadTest2 스레드 객체의 개인 변수 잠금에 할당합니다. 우리는 Java에 문자열 풀이 있다는 것을 알고 있으므로 이러한 스레드의 잠금 비공개 변수는 실제로 힙 메모리의 동일한 영역, 즉 기본 기능의 잠금 변수가 저장되는 영역을 가리키므로 객체 잠금이 고유하고 공유됩니다. 스레드 동기화! !
잠금 문자열 객체는 여기에 동기화 된 잠금.
동기화 메소드 블록의 경우, 잠금은 동기화 된 괄호로 구성된 객체입니다.
public class ThreadTest_03은 스레드 {public synchronized static void test () {for (int i = 0; i <3; i ++) {system.out.println (thread.currentThread (). getName ()+"run ......"); }} @override public void run () {test (); } public static void main (String [] args) {for (int i = 0; i <5; i ++) {new ThreadTest_03 (). start (); }}}실행 결과 :
스레드 -0 run ... 스레드 -0 run ... 스레드 -4 런 ... 스레드 -4 런 ... 스레드 -4 런 ... 스레드 -4 실행 ... 스레드 -1 런 ... 스레드 -1 런 ... 스레드 -2 런 ... 스레드 -2 런 ... 스레드 -3 런 ... 스레드 -3 런 ... 스레드 -3 런 ...
이 예에서 실행 방법은 동기화 메소드와 정적 동기화 방법을 사용합니다. 그렇다면 여기서 동기화 된 잠금은 무엇입니까? 우리는 정적이 객체를 넘어서 클래스 수준에 있다는 것을 알고 있습니다. 따라서 객체 잠금은 정적 릴리스가있는 클래스의 클래스 인스턴스입니다. JVM에서 모든로드 된 클래스에는 고유 한 클래스 객체가 있으며이 인스턴스에서 유일한 ThreadTest_03.class 객체입니다. 우리가 만든 클래스의 몇 가지 인스턴스에 관계없이 클래스 인스턴스는 여전히 하나입니다! 따라서 객체 잠금은 고유하고 공유됩니다. 스레드 동기화! !
정적 동기화 방법의 경우 잠금은 현재 객체의 클래스 객체입니다.
클래스가 동기화 된 정적 함수 A와 동기화 된 인스턴스 함수 B를 정의하면, 잠금이 다르기 때문에 여러 스레드에서 두 가지 메소드 A와 B에 액세스 할 때이 클래스의 동일한 객체 OBJ가 동기화를 구성하지 않습니다. 방법 A의 잠금은 객체 OBJ이고 B의 잠금은 OBJ가 속하는 클래스입니다.
잠금 업그레이드
Java에는 잠금 상태, 바이어스 잠금 상태, 경량 잠금 상태 및 헤비급 잠금 상태가 4 개의 상태가 있습니다. 잠금 장치는 업그레이드 될 수 있지만 다운 그레이드 할 수 없으므로 경량 잠금으로 업그레이드 한 후 바이어스 잠금 장치를 바이어스 잠금으로 다운 그레이드 할 수 없습니다. 잠금 업그레이드 전략이지만 다운 그레이드 할 수는 없지만 잠금을 얻고 방출하는 효율성을 향상시키는 것입니다. 아래의 주요 부분은 블로그의 요약입니다 : 동시 (ii) Java SE1.6에서 동기화되었습니다.
잠금 스핀
스레드가 동기화 메소드/코드 블록에 들어가면 동기화 메소드/코드 블록이 다른 사람이 점유한다는 것을 알게되면 대기하고 차단 상태로 들어갑니다. 이 프로세스의 성능은 낮습니다.
자물쇠의 경쟁에 직면하거나 사물을 기다릴 때 스레드는 차단 상태에 들어가기를 덜 염려 할 수 있지만 잠금이 즉시 해제되는지 기다리고 기다립니다. 이것은 잠금 스핀입니다. 어느 정도까지 잠금 스핀은 스레드를 최적화 할 수 있습니다.
긍정적 인 자물쇠
양의 자물쇠는 주로 경쟁없이 자물쇠의 성능 문제를 해결하는 데 사용됩니다. 대부분의 경우 잠금 잠금 장치에는 멀티 스레드 경쟁이있을뿐만 아니라 동일한 스레드에 의해 항상 여러 번 획득됩니다. 스레드가 저렴한 비용으로 자물쇠를 획득하기 위해 바이어스 잠금 장치가 도입됩니다. 스레드가 잠금을 얻을 때 스레드는 객체를 여러 번 잠글 수 있지만 이러한 작업이 수행 될 때마다 CAS (CPU의 비교 및 SWAP 명령어) 작업으로 인해 약간의 오버 헤드 소비가 소비됩니다. 이 오버 헤드를 줄이기 위해 잠금은 최초의 스레드 인 경향이 있습니다. 다음 실행 프로세스 중에 잠금이 다른 스레드에 의해 획득되지 않으면 바이어스 잠금을 고정하는 스레드를 다시 동기화 할 필요가 없습니다.
다른 스레드가 바이어스 된 잠금 장치를 위해 경쟁하려고 할 때 바이어스 된 잠금을 고정하는 스레드가 잠금을 방출합니다.
확장을 잠그십시오
세분화가 너무 작은 잠금 장치에 대한 다중 호출은 큰 세분성 잠금 장치가있는 호출 잠금 장치만큼 효율적이지 않습니다.
경량 잠금
프로그램의 동기화 성능을 향상시키기위한 경량 잠금의 기초는 "대부분의 잠금의 경우 동기화주기 전체에 경쟁이 없다"는 것입니다. 이는 경험적 데이터입니다. 경량 잠금은 현재 스레드의 스택 프레임에서 잠금 레코드라는 공간을 만듭니다.이 스레드는 현재 포인팅 및 잠금 객체의 상태를 저장하는 데 사용됩니다. 경쟁이없는 경우, 경량 잠금 장치는 CAS 작업을 사용하여 Mutxes 사용의 오버 헤드를 피하기 위해 CAS 작업을 사용하지만 잠금 경쟁이있는 경우 뮤텍스의 오버 헤드 외에도 CAS 작동이 추가로 발생하므로 경쟁의 경우 경량 잠금이 전통적인 헤비급 잠금보다 느리게됩니다.
자물쇠의 공정성
공정성의 반대는 굶주림입니다. 그렇다면 "기아"는 무엇입니까? 다른 스레드가 항상 CPU를 점유하고 있기 때문에 스레드가 CPU 실행 시간을 얻을 수없는 경우, 우리는 스레드를 "배고픈 죽음"이라고 부릅니다. 기아에 대한 해결책은 "공정성"이라고합니다. 모든 스레드는 CPU를 실행할 수있는 기회를 공정하게 얻을 수 있습니다.
스레드 굶주림에는 몇 가지 주요 이유가 있습니다.
우선 순위가 높은 스레드는 모든 우선 순위가 낮은 스레드의 CPU 시간을 소비합니다. 각 스레드에 대해 1에서 10까지 우선 순위를 개별적으로 설정할 수 있습니다. 우선 순위가 높을수록 CPU를 얻는 데 더 많은 시간이 걸립니다. 대부분의 응용 프로그램의 경우 우선 순위 값을 변경하지 않는 것이 가장 좋습니다.
스레드는 동기화 블록에 들어가기를 기다리는 상태에서 영구적으로 차단됩니다. Java의 동기 코드 영역은 스레드 굶주림을 유발하는 중요한 요소입니다. Java의 동기 코드 블록은 스레드의 순서를 입력하는 순서를 보장하지 않습니다. 이는 이론적으로 동기 코드 영역에 들어가려고 할 때 항상 차단되는 스레드가 하나 이상 있다는 것을 의미합니다. 다른 스레드는 항상 액세스 할 수있는 것보다 우수하기 때문에 CPU가 실행 기회를 얻지 못하고 "죽음에 굶어 죽었습니다".
스레드는 자체가 영구적으로 완성을 기다리는 객체를 기다리고 있습니다. 여러 스레드가 Wait () 메소드의 실행 중이며 Notify ()을 호출해도 모든 스레드가 깨어날 것을 보장하지 않으면 모든 스레드가 연속 대기 상태 일 수 있습니다. 따라서 다른 대기 실은 항상 깨어날 수 있기 때문에 하나의 대기 실이 깨어나지 않을 위험이 있습니다.
실 "기아"문제를 해결하기 위해 자물쇠를 사용하여 공정성을 달성 할 수 있습니다.
자물쇠의 재진성
스레드가 다른 스레드로 잠금을 고정하는 객체를 요청하면 스레드가 차단되지만 스레드가 자체적으로 잠금을 고정하는 객체를 요청할 때 성공할 수 있습니까? 답은 성공이 성공할 수 있고 성공 보장은 스레드 잠금의 "재입국"이라는 것입니다.
"Reentable"은 차단하지 않고 자신의 내부 잠금을 다시 얻을 수 있음을 의미합니다. 다음과 같이 :
공개 클래스 아버지 {public synchronized void method () {// do do}} public class child 아버지 {public synchronized void method () {// super.method (); }}
재진입이 아닌 경우 위의 코드는 교착 상태가됩니다. Calling Child 's Method ()는 먼저 부모 클래스 아버지의 내장 잠금 잠금 장치를 획득 한 다음 내장 된 자녀의 자물쇠를 획득하기 때문입니다. 상위 클래스의 메소드를 호출 할 때는 부모 클래스의 내장 잠금으로 돌아 가야합니다. 재진입하지 않으면 교착 상태에 빠질 수 있습니다.
Java Multithreading의 Reentrability 구현은 요청 계산과 각 잠금으로이를 차지하는 스레드를 연관시키는 것입니다. 카운트가 0 인 경우 잠금이 점유되지 않은 것으로 여겨지고 모든 스레드는 잠금의 소유권을 얻을 수 있습니다. 스레드가 성공적으로 요청되면 JVM은 잠금을 고정하는 스레드를 기록하고 카운트를 1으로 설정합니다. 다른 스레드가 잠금을 요청하면 대기해야합니다. 스레드가 잠금을 다시 얻도록 요청하면 카운트는 +1이됩니다. 점유 스레드가 동기 코드 블록을 종료하면 카운트는 0이 될 때까지 -1이면 잠금이 해제됩니다. 그래야만 다른 스레드는 잠금의 소유권을 얻을 수있는 기회를 가질 수 있습니다.
잠금 및 구현 클래스
java.util.concurrent.locks는 매우 유연한 잠금 메커니즘을 제공하여 프레임 워크의 인터페이스 및 잠금 및 대기 조건을위한 클래스를 제공합니다. 내장 동기화 및 모니터와는 다르므로 잠금 및 조건을 사용하는 데 더 많은 유연성이 가능합니다. 클래스 구조 다이어그램은 다음과 같습니다.
ReintrantLock : 잠금 인터페이스의 주요 구현 인 재입술 뮤텍스 잠금 장치.
ReentrantreadWritelock :
readWritElock : readWritelock은 한 쌍의 관련 잠금을 유지합니다. 하나는 읽기 전용 작업 용 및 다른 하나는 쓰기 작업 용입니다.
세마포어 : 세마 포어.
조건 : 잠금의 목적은 스레드가 잠금 장치를 얻고 특정 조건 대기가 충족되는지 확인하는 것입니다.
CyclicBarrier : 공통 장벽 지점에 도달 할 때까지 스레드 그룹이 서로 대기 할 수있는 동기 보조 클래스.