일반적으로 저자는 종종 많은 학생들이 Java 동시 개발 모델을 치료하는 데 몇 가지 기본 방법 만 사용한다는 것을 알 수 있습니다. 예를 들어, 휘발성, 동기화. 잠금 및 원자와 같은 고급 동시 패키지는 종종 많은 사람들이 사용하지 않습니다. 나는 대부분의 이유가 원칙에 대한 속성이 없기 때문이라고 생각합니다. 바쁜 개발 작업에서 누가 올바른 동시성 모델을 정확하게 파악하고 사용할 수 있습니까?
최근 에이 아이디어를 바탕으로 동시성 제어 메커니즘을 기사로 구성 할 계획입니다. 그것은 자신의 지식에 대한 기억 일뿐 만 아니라이 기사에 언급 된 내용이 대부분의 개발자에게 도움이되기를 희망합니다.
병렬 프로그램 개발에는 필연적으로 멀티 스레딩 및 멀티 태스킹 협업 및 데이터 공유와 같은 문제가 포함됩니다. JDK에서는 여러 스레드 간의 동시 제어를 구현하기위한 여러 가지 방법이 제공됩니다. 예를 들어, 일반적으로 사용되는 내부 잠금, 재입국 잠금, 읽기 쓰기 잠금 및 세마포어.
자바 메모리 모델
Java에서 각 스레드에는 작업 메모리 영역이 있으며 모든 스레드가 공유하는 기본 메모리에 변수 값의 사본을 저장합니다. 스레드가 실행되면 자체 작업 메모리에서 이러한 변수를 작동시킵니다.
공유 변수에 액세스하기 위해 스레드는 일반적으로 잠금을 얻고 작업 메모리 영역을 지우므로 공유 변수가 모든 스레드의 공유 메모리 영역에서 스레드의 작업 메모리 영역에 올바르게로드되도록합니다. 스레드가 잠금 해제되면 작업 메모리 영역의 변수 값은 공유 메모리와 관련이 있습니다.
스레드가 특정 변수를 사용하는 경우, 프로그램이 스레드 동기화 작업을 올바르게 사용하는지 여부에 관계없이, 획득 한 값은 자체 또는 다른 스레드로 변수에 저장된 값이어야합니다. 예를 들어, 두 스레드가 다른 값 또는 객체 참조를 동일한 공유 변수에 저장하는 경우 변수의 값은이 스레드 또는 해당 스레드에서 나오고 공유 변수의 값은 두 스레드의 참조 값으로 구성되지 않습니다.
변수를 사용할 때 Java 프로그램이 액세스 할 수있는 주소. 기본 유형 변수와 기준 유형 변수뿐만 아니라 배열 유형 변수도 포함됩니다. 기본 메모리 영역에 저장된 변수는 모든 스레드에서 공유 할 수 있지만 한 스레드는 다른 스레드의 매개 변수 또는 로컬 변수에 액세스하는 것이 불가능하므로 개발자는 로컬 변수의 스레드 안전 문제에 대해 걱정할 필요가 없습니다.
휘발성 변수는 여러 스레드간에 볼 수 있습니다
각 스레드에는 자체 작업 메모리 영역이 있으므로 한 스레드가 자체 작업 메모리 데이터를 변경하면 다른 스레드에 보이지 않을 수 있습니다. 이렇게하려면 휘발성 키워드를 사용하여 모든 스레드를 깨뜨려 메모리에서 변수를 읽고 쓸 수 있으므로 휘발성 변수가 여러 스레드 중에서 보이도록 할 수 있습니다.
휘발성으로 선언 된 변수는 다음과 같이 보장 될 수 있습니다.
1. 다른 스레드에 의한 변수의 수정은 현재 스레드에 즉시 반영 될 수 있습니다.
2. 휘발성 변수에 대한 현재 스레드의 수정을 시간에 공유 메모리에 다시 작성하고 다른 스레드에서 볼 수 있는지 확인하십시오.
3. 휘발성에 의해 선언 된 변수를 사용하면 컴파일러가 질서를 보장합니다.
동기화 된 키워드
동기화 된 키워드 동기화 된 키워드는 Java 언어에서 가장 일반적으로 사용되는 동기화 방법 중 하나입니다. 초기 JDK 버전에서는 Synchronized의 성능이 좋지 않았으며, 잠금 경쟁이 특히 치열하지 않은 경우에는 그 가치가 적합했습니다. JDK6에서는 동기화 된 자물쇠와 불공정 자물쇠 사이의 간격이 좁아졌습니다. 더 중요한 것은 동기화 된 것이 더 간결하고 명확하며 코드를 읽을 수 있고 유지 관리합니다.
객체를 잠그는 방법 :
public synchronized void method () {}
메소드 () 메소드가 호출되면 호출 스레드는 먼저 현재 객체를 얻어야합니다. 현재 객체 잠금 장치가 다른 스레드에 의해 유지되면 호출 스레드가 대기됩니다. 위반이 끝나면 객체 잠금이 해제됩니다. 위의 방법은 다음 쓰기 방법과 동일합니다.
public void method () {synchronized (this) {// 뭔가…}} 둘째, 동기화 된 블록을 구축하는 데 동기화 될 수도 있습니다. 동기화 방법과 비교하여 동기화 블록은 동기화 코드 범위를보다 정확하게 제어 할 수 있습니다. 작은 동기화 코드는 잠금 장치 안팎에서 매우 빠르므로 시스템에 더 높은 처리량을 제공합니다.
public void method (object o) {// beforesynchronized (o) {// 뭔가 ...} // 이후} 정적 기능에도 동기화 될 수도 있습니다.
public synchronized static void method () {}
이 장소에서 동기화 된 잠금이 현재 클래스 객체에 추가된다는 점에 유의해야 하므로이 방법의 모든 호출은 클래스 객체의 잠금을 얻어야합니다.
동기화 된 객체 또는 코드 세그먼트의 스레드 안전을 보장 할 수 있지만 동기화 된 단독을 사용하는 것만으로는 복잡한 로직과의 스레드 상호 작용을 제어하기에 충분하지 않습니다. 여러 스레드 간의 상호 작용을 달성하기 위해서는 대상 객체의 대기 () 및 notify () 메소드도 필요합니다.
일반적인 사용 :
동기화 (obj) {while (<?>) {obj.wait (); // 알림을받은 후에도 계속 실행됩니다. }} Wait () 메소드를 사용하기 전에 객체 잠금을 얻어야합니다. 대기 () 메소드가 실행되면 현재 스레드는 다른 스레드에서 사용하기 위해 OBJ의 독점 잠금을 해제 할 수 있습니다.
OBJ의 스레드가 obj.notify ()를 받기를 기다릴 때 OBJ의 독점 잠금을 되찾고 계속 실행할 수 있습니다. Notify () 메소드는 현재 객체에서 대기하는 스레드를 무작위로 불러 일으키는 것입니다.
다음은 차단 대기열의 구현입니다.
공개 클래스 블록 큐 {private list list = new ArrayList (); public synchronized object pop ()는 interruptedException {while (list.size () == 0) {this.wait (); } if (list.size ()> 0) {return list.remove (0); } else {return null; }} public synchronized 객체 put (object obj) {list.add (obj); this.notify (); }} Synchronized and Wait () 및 notify ()는 Java 개발자가 마스터 해야하는 기본 기술이어야합니다.
Reintrantlock Reentrantlock 잠금
ReintrantLock을 ReentrantLock이라고합니다. 동기화 된 것보다 더 강력한 기능을 가지고 있으며 인터럽트와 시간을 가질 수 있습니다. 동시성이 높은 경우 동기화 된 것보다 명백한 성능 이점이 있습니다.
ReintrantLock은 공정하고 불공정 한 자물쇠를 제공합니다. 공정한 자물쇠는 자물쇠의 첫 번째 우선 출력이며, 공정한 자물쇠는 줄을 끊을 수 없습니다. 물론, 성능 관점에서 볼 때 불공정 자물쇠의 성능이 훨씬 좋습니다. 따라서 특별한 요구가 없으면 불공정 한 자물쇠가 선호되어야하지만 동기화 된 자물쇠 산업은 절대적으로 공평하지 않습니다. ReintrantLock은 구축 할 때 잠금 장치가 공정한지 여부를 지정할 수 있습니다.
재입국 잠금 장치를 사용할 때는 프로그램 끝에서 잠금 장치를 릴리스하십시오. 일반적으로 잠금을 공개하기위한 코드는 마지막으로 작성해야합니다. 그렇지 않으면 프로그램 예외가 발생하면 Loack이 출시되지 않습니다. 동기화 된 잠금은 끝에 JVM에 의해 자동으로 해제됩니다.
고전적인 사용법은 다음과 같습니다.
{if (lock.rylock (5, timeUnit.seconds)) {// 잠긴 경우 5S를 기다리면 잠금 장치를 얻을 수 있는지 확인하십시오. 5 초 이후에 잠금을 얻을 수없는 경우, false를 반환하여 계속 실행 // lock.lockinterruptibly (); 인터럽트 이벤트에 응답 할 수 있습니다. {// operation} 마지막으로 {lock.unlock (); }}} catch (InterruptedException e) {e.printstacktrace (); // 현재 스레드가 중단되면 (인터럽트) 인터럽트 꺼짐이 발생합니다}ReintrantLock은 풍부한 다양한 잠금 제어 기능을 제공하며 이러한 제어 방법을 유연하게 적용하여 응용 프로그램의 성능을 향상시킵니다. 그러나 여기에서 ReentrantLock을 사용하는 것이 좋습니다. 재진입 잠금 장치는 JDK에서 제공되는 고급 개발 도구입니다.
readWritElock 읽기 및 쓰기 잠금
읽기 및 쓰기 분리는 매우 일반적인 데이터 처리 아이디어입니다. SQL에서 필요한 기술로 간주되어야합니다. ReadWritelock은 JDK5에서 제공되는 read-write 분리 잠금입니다. 분리 잠금을 읽고 쓰면 잠금 경쟁을 효과적으로 줄여 시스템 성능을 향상시킬 수 있습니다. 읽기 및 쓰기 분리를위한 사용 시나리오는 주로 시스템에서 읽기 작업 수가 쓰기 작업보다 훨씬 큰 경우입니다. 사용 방법은 다음과 같습니다.
private reentrantreadWritelock readWritelock = new ReentRantreadWritelock (); private lock readlock = readWritelock.readlock (); private lock writelock = readWritelock.writelock (); public object handleread () throws interruptedException {readlock.lock (); Thread.sleep (1000); 반환 값; } 마침내 {readlock.unlock (); }} public object handleread ()는 interruptedException {try {writelock.lock (); Thread.sleep (1000); 반환 값; } 마침내 {writelock.unlock (); }} 조건 객체
ConditionD 객체는 여러 스레드 간의 복잡한 협업을 조정하는 데 사용됩니다. 주로 자물쇠와 관련이 있습니다. 잠금에 바인딩 된 조건 인스턴스는 잠금 인터페이스의 NewCondition () 메소드를 통해 생성 될 수 있습니다. 조건 객체와 잠금의 관계는 두 함수 객체를 사용하는 것과 같습니다. wait (), object.notify () 및 동기화 된 키워드.
여기에서 ArrayBlockingqueue의 소스 코드를 추출 할 수 있습니다.
공개 클래스 ArrayBlockingqueue 확장 AbstractQueue 구현 블록 큐어 큐어, Java.io.serializable {/** 모든 액세스를 보호하는 메인 잠금 장치*/최종 재입자 조건*/** 대기 조건*/개인 최종 조건에 대한 조건*/개인 최종 조건에 대한 대기 조건*/개인 최종 조건; 공공 배열 자격) (int) 불법 행위 덱싱 (); this.Items = 새로운 객체 [용량]; 잠금 = 새로운 재진입 락 (FAIR); notempty = lock.newcondition (); // 조건을 생성하지 못한다 = lock.newcondition ();} public void put (e e) 던지기 중단 exception {checknotnull (e); 최종 재진입 락 잠금 = this.lock; lock.lockinterruptibly (); try {while (count == items.length) notfull.await (); 삽입 (e); } 마침내 {lock.unlock (); }} private void insert (e x) {항목 [posindex] = x; potindex = inc (푸틴 덱스); ++ 수; notempty.signal (); // notification} public e take ()가 중단 된 exception {Final ReintrantLock Lock = this.lock; lock.lockinterruptibly (); try {while (count == 0) // 큐가 비어있는 경우 notempty.await (); // 그러면 소비자 큐는 비어 있지 않은 신호 return extract ()를 기다려야합니다. } 마침내 {lock.unlock (); }} private e extract () {final object [] items = this.Items; e x = this. <e> 캐스트 (항목 [TakeIndex]); 항목 [takeIndex] = null; TakeIndex = inc (TakeIndex); --세다; notfull.signal (); // 스레드 큐에 여유 공간 반환 x;} // 기타 코드}가 있음을 알리기 () 세마포어 세마포어 <br /> 세마포어는 멀티 스레드 협업을위한보다 강력한 제어 방법을 제공합니다. 세마포어는 자물쇠의 확장입니다. 내부 잠금이 동기화 된 내부 잠금이든 재진입 락이든, 한 스레드는 한 번에 리소스에 액세스 할 수있는 반면, 세마포어는 여러 스레드가 동시에 리소스에 액세스하도록 지정할 수 있습니다. 생성자로부터 우리는 다음을 볼 수 있습니다.
공개 세마포어 (Int remits) {}
public semaphore (int remits, boolean fair) {} // 공정 여부를 지정할 수 있습니다.
허가는 세마포어의 액세스 북을 지정합니다. 즉, 동시에 적용 할 수있는 라이센스 수를 의미합니다. 각 스레드가 한 번에 하나의 라이센스에만 적용되는 경우, 이는 특정 리소스에 동시에 액세스 할 수있는 스레드 수를 지정하는 것과 같습니다. 다음은 사용하는 주요 방법입니다.
public void arcire () 던지기 중국적 exception {} // 액세스 권한을 얻으십시오. 사용할 수없는 경우 스레드가 권한을 공개하거나 현재 스레드가 중단된다는 것을 알면서 스레드가 대기합니다.
공개 void arcireUnInterruptibly () {} // arcire ()와 유사하지만 인터럽트에 응답하지 않습니다.
Public Boolean tryacquire () {} // 성공하면, 그렇지 않으면 false를 얻으십시오. 이 방법은 대기하지 않으며 즉시 돌아갑니다.
공개 부울 tryacquire (긴 시간 초
Public Void Release () //는 현장 액세스 리소스가 완료된 후 라이센스를 해제하는 데 사용되므로 권한을 기다리는 다른 스레드가 리소스에 액세스 할 수 있습니다.
JDK 문서에 제공된 세마포어를 사용하는 예를 살펴 보겠습니다. 이 예는 세마포어를 통해 자원 액세스를 제어하는 방법을 설명합니다.
공개 클래스 풀 {private static final int max_available = 100; 개인 최종 세마포어 사용 가능한 = 새로운 세마포어 (max_available, true); public object getItem () 던지기 인터럽트 exception {advere.acquire (); // 라이센스를 신청 // 100 스레드 만 입력하여 사용 가능한 항목 만 입력 할 수 있습니다. // 사용 가능한 항목을 추가하고 라이센스를 릴리스하고 리소스를 요청하는 스레드가 활성화됩니다}} // 참조 만 참조, 비실한 데이터 보호 객체 [] items = new Object [max_available]; // 객체 풀 다중화 객체에 사용되는 부울 [] 사용 = 새로운 부울 [max_available]; // Markup 함수 보호 된 동기화 된 객체 getNextAvailableItem () {for (int i = 0; i <max_available; ++ i) {if (! indred [i]) {used [i] = true; 반품 항목 [i]; }} return null;} 보호 된 동기화 된 boolean markaSunued (객체 항목) {for (int i = 0; i <max_available; ++ i) {if (item == eiret [i]) {if (ind -[i]) {사용 [i] = false; 진실을 반환하십시오. } else {return false; }}} return false;}} 이 인스턴스는 단순히 최대 용량이 100 인 객체 풀을 구현합니다. 따라서 100 개의 객체 요청이 동시에있을 때 객체 풀은 자원 부족이 발생하고 리소스를 얻지 못하는 스레드는 기다려야합니다. 스레드가 객체를 사용하여 완료되면 객체를 객체 풀로 되돌려 야합니다. 현재 사용 가능한 리소스가 증가함에 따라 리소스를 기다리는 스레드를 활성화 할 수 있습니다.
ThreadLocal 스레드 로컬 변수 <br /> threadLocal에 연락하기 시작한 후이 스레드 로컬 변수의 사용 시나리오를 이해하기가 어렵습니다. 지금 되돌아 보면 ThreadLocal은 여러 스레드 사이의 변수에 대한 동시 액세스를위한 솔루션입니다. Synchronized 및 기타 잠금 방법과 달리 ThreadLocal은 전혀 잠금 장치를 제공하지 않지만 스레드 안전을 보장하기 위해 각 스레드에 독립적 인 변수 사본을 제공하기 위해 시간 동안 공간을 교환하는 방법을 사용합니다. 따라서 데이터 공유 솔루션이 아닙니다.
ThreadLocal은 스레드 안전 문제를 해결하는 것이 좋습니다. ThreadLocal 클래스에는 각 스레드에 대한 변수 사본을 저장하는 맵이 있습니다. 맵의 요소의 키는 스레드 객체이며 값은 스레드 변수의 사본에 해당합니다. 키 값을 반복 할 수 없으므로 각 "스레드 개체"는 스레드의 "변수 사본"에 해당하며 스레드 안전에 도달합니다.
특히 주목할 만하다. 성능 측면에서 ThreadLocal에는 절대 성능이 없습니다. 동시 부피가 높지 않으면 잠금 성능이 더 나을 것입니다. 그러나, 잠금과 완전히 관련이없는 스레드-안전 솔루션 세트로서, Threadlocal을 사용하면 높은 동시성 또는 치열한 경쟁에서 잠금 경쟁을 어느 정도 줄일 수 있습니다.
다음은 ThreadLocal의 간단한 사용입니다.
public class testnum {// threadlocal의 initialvalue () 메소드를 익명 내부 클래스를 통한 초기 값을 지정하고, 개인 정적 threadlocal seqnum = new ThreadLocal () {public integer initialValue () {return 0; }}; // 다음 시퀀스 값 가져 오기 public int getnextnum () {seqnum.set (seqnum.get () + 1); return seqnum.get ();} public static void main (String [] args) {testnum sn = new testnum (); // 3 스레드는 SN을 공유하고, 각각 시퀀스 번호 testClient t1 = new TestClient (SN); testclient t2 = new testclient (SN); 시험 클리어 T3 = 새로운 시험 클리어 (SN); t1.start (); t2.start (); t3.start (); } private static class testclient 확장 스레드 {private testnum sn; public testclient (testnum sn) {this.sn = sn; } public void run () {for (int i = 0; i <3; i ++) {// 각 스레드는 3 시퀀스 값 system.out.println을 생성합니다 (스레드 [ " + thread.currentthread (). getName () +"] -> sn [ " + sn.getNextNum () +"]); }}}} 출력 결과 :
스레드 [스레드 -0]> SN [1]
스레드 [Thread-1]> SN [1]
스레드 [Thread-2]> SN [1]
스레드 [Thread-1]> SN [2]
스레드 [스레드 -0]> SN [2]
스레드 [Thread-1]> SN [3]
스레드 [Thread-2]> SN [2]
스레드 [스레드 -0]> SN [3]
스레드 [Thread-2]> SN [3]
출력 결과 정보는 각 스레드에 의해 생성 된 시퀀스 번호가 동일한 TestNum 인스턴스를 공유하지만 서로를 방해하지 않지만 각각은 독립적 인 시퀀스 번호를 생성한다는 것을 알 수 있습니다. ThreadLocal은 각 스레드에 대해 별도의 사본을 제공하기 때문입니다.
"잠금" 의 잠금 성능 및 최적화는 가장 일반적으로 사용되는 동기화 방법 중 하나입니다. 정상적인 개발에서는 종종 많은 학생들이 잠금에 큰 코드를 직접 추가하는 것을 볼 수 있습니다. 일부 학생들은 하나의 잠금 방법 만 사용하여 모든 공유 문제를 해결할 수 있습니다. 분명히, 그러한 인코딩은 용납 할 수 없습니다. 특히 높은 동시성 환경에서 치열한 자물쇠 경쟁은 프로그램의보다 명백한 성능 저하로 이어질 것입니다. 따라서 자물쇠의 합리적인 사용은 프로그램의 성능과 직접 관련이 있습니다.
1. 스레드 오버 헤드 <br /> 멀티 코어의 경우 멀티 스레딩을 사용하면 시스템의 성능이 크게 향상 될 수 있습니다. 그러나 실제 상황에서는 멀티 스레딩을 사용하면 시스템 오버 헤드가 추가됩니다. 단일 코어 시스템 작업 자체의 리소스 소비 외에도 멀티 스레드 애플리케이션은 추가 멀티 스레드 고유 정보를 유지해야합니다. 예를 들어, 스레드 자체의 메타 데이터, 스레드 스케줄링, 스레드 컨텍스트 전환 등.
2. 잠금 보유 시간을 줄입니다
동시 제어를 위해 잠금 장치를 사용하는 프로그램에서, 잠금 장치가 경쟁 할 때 단일 스레드의 잠금 상태는 시스템 성능과 직접적인 관계를 갖습니다. 스레드가 잠금 장치를 오랫동안 유지하면 자물쇠 경쟁이 더 강렬해질 것입니다. 따라서 프로그램 개발 과정에서 스레드 간의 상호 배제 가능성을 줄이기 위해 특정 잠금을 차지할 시간을 최소화해야합니다. 예를 들어 다음 코드 :
public synchronized void syncmehod () {beforemethod (); mutexmethod (); AfterMethod ();} 이 인스턴스의 mutexmethod () 메소드 만 동기식이지만 Beforemethod () 및 AfterMethod ()에서는 동기화 제어가 필요하지 않습니다. Beforemethod () 및 AfterMethod ()가 헤비급 방법이라면 CPU의 경우 오랜 시간이 걸립니다. 현재 동시성이 크면이 동기화 체계를 사용하면 대기 스레드가 크게 증가합니다. 현재 실행중인 스레드는 모든 작업이 실행 된 후에 만 잠금을 해제하기 때문에.
다음은 필요한 경우에만 동기화되는 최적화 된 솔루션으로 스레드가 잠금을 고정하는 시간을 크게 줄이고 시스템의 처리량을 개선 할 수 있습니다. 코드는 다음과 같습니다.
public void syncmehod () {beforemethod (); synchronized (this) {mutexmethod ();} apermethod ();} 3. 잠금 입자 크기를 줄입니다
자물쇠 세분화를 줄이는 것은 멀티 스레드 자물쇠에 대한 경쟁을 약화시키는 효과적인 수단입니다. 이 기술의 일반적인 사용 시나리오는 ConsurenTashMap 클래스입니다. 일반 해시 맵에서는 add () 조작 또는 get () 조작이 컬렉션에서 수행 될 때마다 컬렉션 객체의 잠금이 항상 얻어집니다. 잠금이 전체 컬렉션 개체에 있기 때문에이 작업은 완전히 동기 동작입니다. 따라서 동시성이 높은 경우 치열한 잠금 경쟁은 시스템의 처리량에 영향을 미칩니다.
소스 코드를 읽은 경우 해시 맵이 배열 + 링크 목록에서 구현되었음을 알아야합니다. ConsurenThashmap 전체 해시 맵을 여러 세그먼트 (세그먼트)로 나누고 각 세그먼트는 하위 하쉬 맵입니다. 새 테이블 항목을 추가 해야하는 경우 해시 맵을 잠그지 않습니다. 20 개의 검색 라인은 해시 코드에 따라 테이블 항목을 저장 해야하는 섹션을 얻은 다음 섹션을 잠그고 put () 작업을 완료합니다. 이러한 방식으로, 다중 스레드 환경에서, 다중 스레드가 동시에 쓰기 작업을 수행하는 경우, 쓰여진 항목이 동일한 세그먼트에 존재하지 않는 한 스레드간에 실제 병렬 처리를 달성 할 수 있습니다. 특정 구현을 위해서는 독자들이 ConsprEthashMap 클래스의 소스 코드를 읽는 데 시간이 걸리기를 바랍니다.
4. 잠금 분리 <br /> readwritelock 읽기 및 쓰기 잠금 앞에서 언급 한 다음 읽기 및 쓰기 분리의 확장은 잠금의 분리입니다. 잠금 분리의 소스 코드는 JDK에서도 찾을 수 있습니다.
공개 클래스 링크 블록 큐어는 추상적 인 큐를 확장합니다. blockingqueue, java.io. java.io.serializable {/*weke hold by take, poll etc/private final reintrantlock takelock = new ReentrantLock ();/** 대기 대기 대기 테이크*/개인 최종 조건에 대한 대기 대기 큐*/개인적 최종 조건에 대한 대기 대기 시간 (); putlock = new ReintrantLock ();/** 대기 대기 대기 대기 푸트*/개인 최종 조건 notefull = putlock.newcondition (); public e take ()가 중단 exception {ex; int c = -1; 최종 Atomicinteger count = this.count; 최종 재진입 락 takelock = this.takelock; takelock.lockinterruptibly (); // 데이터를 동시에 읽을 두 스레드가있을 수 없습니다. {count.get () == 0) {// 사용 가능한 데이터가 없으면 put () notempty.await ()의 알림을 기다리십시오. } x = dequeue (); // 항목을 제거합니다 c = count.getAndEcrement (); // 크기 마이너스 1 if (c> 1) notempty.signal (); // 다른 take () 조작} 알림} 마침내 {takelock.unlock (); // 릴리스 잠금} if (c == 용량) SignalNotFull (); // notify put () 작동, 이미 여유 공간 반환 x;} 공개 void put (e e) 던지기 인터럽트 exception {if (e == null) 던질 새 nullpointerexception (); // 참고 : 모든 put/take/etc의 규칙은 로컬 var를 사전 설정하는 것입니다. int c = -1; 노드 <e> 노드 = 새로운 노드 (e); 최종 재진입 락 Putlock = this.putlock; 최종 Atomicinteger count = this.count; putlock.lockinterruptibly (); // 두 개의 스레드는 동시에 데이터를 넣을 수 없습니다. { / * * Count는 잠금으로 보호되지 않더라도 대기 가드에 사용됩니다. 이것은 Count 가이 시점에서만 감소 할 수 있기 때문에 작동합니다 (다른 모든 풋은 잠금으로 종료 *), 우리 (또는 다른 대기 중)가 용량에서 변경되면 서명합니다. 마찬가지로 * 다른 대기 경비원에서 다른 모든 카운트 사용. */ while (count.get () == 용량) {// 대기열이 가득 찬 경우 retfull.await (); } enqueue (노드); // 큐에 가입 c = count.getAndIncrement (); // size plus 1 if (c + 1 <용량) notfull.signal (); // 공간이 충분하면 다른 스레드에 알림} 마지막으로 {putlock.unlock (); // 잠금을 해제}} if (c == 0) signalnotempty (); // 삽입이 성공한 후, take () 작업을 읽는 데 도움이됩니다} // 기타 코드}여기서 설명해야 할 것은 take ()와 put () 함수가 서로 독립적이며 그들 사이에 잠금 경쟁 관계가 없다는 것입니다. Take () 및 put ()의 각 방법 내에서 Takelock 및 Putlock에 대해서만 경쟁하면됩니다. 따라서 자물쇠 경쟁 가능성이 약화됩니다.
5. 잠금 거친 <br /> 위에서 언급 한 자물쇠 시간과 세분성 감소는 각 스레드가 잠금을 고정하는 가장 짧은 시간을 충족시키기 위해 수행됩니다. 그러나 학위는 세분화되어야합니다. 잠금 장치가 지속적으로 요청, 동기화 및 릴리스되면 시스템의 귀중한 리소스를 소비하고 시스템 간접비를 증가시킵니다.
우리가 알아야 할 것은 가상 머신이 일련의 연속 요청과 동일한 잠금의 릴리스를 만나면 모든 잠금 작업을 한 번의 요청에 통합하여 잠금 요청 수를 줄이는 것입니다. 이 작업을 잠금 거친 느낌이라고합니다. 다음은 통합 예의 데모입니다.
public void syncmehod () {synchronized (lock) {method1 ();} synchronized (lock) {method2 ();}} jvm 통합 후 양식 : public void syncmehod () {lock) {method1 (); 메소드 2 ();}}따라서 이러한 통합은 우리의 개발자가 자물쇠 세분성의 파악에 좋은 데모 효과를 제공합니다.
잠금이없는 병렬 컴퓨팅 <br /> 위의 위의 고정에 대해 많은 시간이 걸렸으며, 잠금이 특정 컨텍스트 전환을 위해 추가 자원을 오버 헤드로 가져올 것이라고 언급되어 있습니다. 높은 동시성에서 "잠금"에 대한 치열한 경쟁은 시스템 병목 현상이 될 수 있습니다. 따라서 여기서는 비 블로킹 동기화 방법을 사용할 수 있습니다. 이 잠금 방법은 여전히 데이터와 프로그램이 높은 동시성 환경에서 여러 스레드 간의 일관성을 유지하도록 할 수 있습니다.
1. 비 차단 동기화/잠금식
비 블로킹 동기화 방법은 실제로 이전 ThreadLocal에 반영됩니다. 각 스레드에는 자체 독립적 인 변수 사본이 있으므로 계산할 때 서로 대기 할 필요가 없습니다. 여기서 저자는 주로 CAS 알고리즘 비교 및 스왑을 기반으로 더 중요한 잠금 동시성 제어 방법을 권장합니다.
CAS 알고리즘의 프로세스 : 3 개의 매개 변수 cas (v, e, n)가 포함됩니다. v는 업데이트 될 변수를 나타내고, e는 예상 값을 나타내고, n은 새 값을 나타냅니다. v 값은 v 값이 e 값과 같을 때만 n으로 설정됩니다. V 값이 e 값과 다르면 다른 스레드가 업데이트를 수행하고 현재 스레드는 아무것도하지 않음을 의미합니다. 마지막으로, CAS는 현재 V의 실제 가치를 반환합니다. CAS를 운영 할 때 낙관적 인 태도로 수행되며 항상 운영을 성공적으로 완료 할 수 있다고 생각합니다. 여러 스레드가 CAS를 사용하여 동시에 변수를 작동시킬 때, 하나만 이기고 성공적으로 업데이트되는 반면 Junhui의 나머지 부분은 실패합니다. 실패한 스레드가 매달리지 않으며, 실패가 허용되고 다시 시도 할 수 있으며, 실패한 스레드는 작업을 포기할 수 있습니다. 이 원칙에 따라 CAS 작동은 잠금이 적시에 없으며 다른 스레드는 현재 스레드에 대한 간섭을 감지하고 적절하게 처리 할 수 있습니다.
2. 원자 중량 작동
JDK의 java.util.concurrent.comic 패키지는 잠금 알고리즘을 사용하여 구현 된 원자 운영 클래스를 제공하며 코드는 주로 기본 원시 코드 구현을 사용합니다. 관심있는 학생들은 기본 수준 코드를 계속 추적 할 수 있습니다. 표면 코드 구현을 여기에 게시하지 않겠습니다.
다음은 주로 예제를 사용하여 일반 동기화 방법과 잠금없는 동기화 사이의 성능 간격을 보여줍니다.
공개 클래스 테스트 동의 {private static final int max_threads = 3; private static final int task_count = 3; private static final int target_count = 100 * 10000; private atomicinteger 계정 = new atomicinteger (0); private int count = 0; synchronized inc () {return ++ 칸; 실행 가능한 {문자열 이름; 긴 시작 시간; 시험 항구; public syncthread (testomic o, long starttime) {this.out = o; this.startTime = startTime; } @override public void run () {int v = out.inc (); while (v <target_count) {v = out.inc (); } long endtime = System.CurrentTimeMillis (); System.out.println ( "SyncThread Speend :" + (EndTime -StartTime) + "MS" + ", V =" + V); }} public class atomicthread는 runnable {문자열 이름; 긴 시작 시간; public atomicthread (long starttime) {this.starttime = starttime; } @override public void run () {int v = account.incrementandget (); while (v <target_count) {v = account.incrementandget (); } long endtime = System.CurrentTimeMillis (); System.out.println ( "atomicthread spend :" + (endtime -starttime) + "ms" + ", v =" + v); }}@testpublic void testync ()는 InterruptedException {executorService exe = executors.newfixedthreadpool (max_threads); Long StartTime = System.CurrentTimeMillis (); syncthread sync = new syncthread (this, starttime); for (int i = 0; i <task_count; i ++) {exe.submit (sync); } strook.sleep (10000);}@testpublic void testatomic ()는 InterruptedException {executorService exe = executors.newfixedthreadpool (max_threads); Long StartTime = System.CurrentTimeMillis (); atomicthread atomic = new atomicthread (starttime); for (int i = 0; i <task_count; i ++) {exe.submit (atomic); } thread.sleep (10000);}} 테스트 결과는 다음과 같습니다.
testsync () :
SyncThread Speend : 201ms, v = 1000002
SyncThread Speend : 201ms, v = 10000000
SyncThread Speend : 201ms, v = 1000001
TestAtomic () :
Atomicthread Speend : 43ms, v = 10000000
Atomicthread Speend : 44ms, v = 1000001
Atomicthread Speend : 46ms, v = 1000002
이러한 테스트 결과는 내부 잠금과 비 차단 동기화 알고리즘의 성능 차이를 명확하게 반영 할 것이라고 생각합니다. 따라서 저자는이 원자 클래스를 Atomic에서 직접 간주하는 것이 좋습니다.
결론
마지막으로, 나는 표현하고 싶은 것들을 분류했습니다. 실제로, CountdownLatch와 같은 일부 클래스는 언급되지 않은 일부 클래스가 있습니다. 그러나 위에서 언급 한 것은 확실히 동시 프로그래밍의 핵심입니다. 아마도 일부 독자들은 인터넷에서 그러한 지식 포인트를 많이 볼 수 있지만, 나는 여전히 비교할 때만 지식이 적절한 사용 시나리오에서 발견 될 수 있다고 생각합니다. 따라서 이것이 편집자 가이 기사를 편집 한 이유이기도합니다.이 기사가 더 많은 학생들에게 도움이되기를 바랍니다.
위는이 기사의 모든 내용입니다. 모든 사람의 학습에 도움이되기를 바랍니다. 모든 사람이 wulin.com을 더 지원하기를 바랍니다.