이전 장에서는 차단 대기열 차단 큐를 소개했습니다. 아래에 일반적으로 사용되는 구현 클래스 ArrayBlockingQueue를 소개합니다.
1. 배열을 사용하여 대기열을 구현하십시오
큐의 데이터 구조의 특수 요구 사항으로 인해 링크 된 목록의 형태로 자연스럽게 구현하기에 적합합니다. 두 개의 변수는 각각 링크 된 목록의 헤더와 끝을 기록하는 데 사용됩니다. 큐를 삭제하거나 삽입하면 링크 된 목록의 헤더 또는 끝을 변경하십시오. 또한 링크 된 목록은 참조 방식으로 연결되므로 용량은 거의 무제한입니다.
따라서 배열을 사용하여 대기열을 구현하는 방법은 큐에 요소를 저장하기위한 객체 [] 큐 헤드와 테일을 기록하고 큐의 수를 기록합니다.
매우 영리한 방법이 여기에 사용됩니다. 요소가 큐에 삽입되면 배열에서 위치를 차지한다는 것을 알고 있습니다. 요소가 삭제되면 배열의 위치는 실제로 자유 롭고이 위치에 새 요소가 삽입 될 수 있음을 나타냅니다.
따라서 새 요소를 삽입하기 전에 대기열이 가득 찼는 지 확인해야하며 요소를 삭제하기 전에 대기열이 비어 있는지 확인해야합니다.
2. ArrayBlockingqueue의 중요한 멤버 변수
/ ** 큐에 요소를 저장*/ 최종 개체 [] 항목; / ** 큐 헤드의 위치*/ int takeIndex; / ** 대기열 꼬리의 위치*/ int potindex; / ** 현재 대기열의 요소 수*/ int count; / ** 다중 스레드 공유 변수의 보안을 보장하는 데 사용됩니다*/ 최종 재입자 락 잠금; / ** 대기열이 비어 있으면 현재 스레드를 대기하도록 대기하는 대기 방법이 호출됩니다.*/ 개인 최종 조건이 없습니다. / ** 대기열이 가득 차면 대기 중의 대기 방법이 호출되어 현재 스레드가 대기를합니다.*/ 개인 최종 조건이 없습니다.
멀티 스레드 안전 및 스레드 대기 조건을 구현할 수있는 더 많은 잠금, 무의미 및 악의적 인 변수가 있습니다. 그들은 어떻게 운영됩니까?
2.1 자물쇠의 역할
ArrayBlockingqueue는 여러 스레드에서 작동하기 때문에 항목, TakeIndex, Pitindex 및 Count와 같은 멤버 변수를 수정할 때 다중 스레드 안전 문제를 고려해야합니다. 따라서 잠금 독점 잠금 장치는 동시 작업의 안전을 보장하기 위해 여기에 사용됩니다.
2.2 NOTEMPTY와 NOTULL의 역할
차단 큐를 구현해야하므로 큐가 비어 있거나 큐가 가득 차면 큐를 읽거나 삽입하는 작업이 대기해야합니다. 그래서 우리는 동시성 프레임 워크 아래의 조건 객체를 생각하고 그것을 제어하는 데 사용합니다.
AQ에서는이 클래스의 역할을 소개합니다.
3. 요소 방법을 추가하십시오
3.1 추가 (e e) 및 제안 (e e) 방법 :
// AbstractQueue 부모 클래스에서 메소드를 호출합니다. public boolean add (e e) {// If (rofer (e)) return true; 그렇지 않으면 새로운 불법 스테이트 exception ( "대기열 전체")을 던집니다. } // 큐 끝에 새 요소를 추가합니다. return true가 추가가 성공했음을 의미하고, 거짓은 추가가 실패했으며, 예외는 공개 부울 제안 (e e) {checknotnull (e); 최종 재진입 락 잠금 = this.lock; // 잠금을 사용하여 멤버 속성의 다중 스레드 수정을 lek.lock (); try {// 큐가 가득 차면 요소가 추가되면 False를 반환합니다. if (count == items.length) false를 반환합니다. else {// eNqueue 메소드를 호출하여 요소를 큐 eNqueue (e)에 삽입합니다. 진실을 반환하십시오. }} 마침내 {lock.unlock (); }}ADD 메소드는 제안 메소드를 호출하여 구현됩니다. 제안 방법에서는 먼저 대기열이 가득 찼는 지 확인해야합니다. 가득 차면 현재 스레드를 차단하지 않고 직접 거짓을 반환합니다. 만족하지 않으면 Enqueue 메소드를 호출하여 요소를 큐에 삽입하십시오.
참고 : lock.lock ()를 사용하면 여기에서 하나의 스레드 만 동시 작동 문제를 방지하기 위해 멤버 변수를 동시에 수정합니다. 또한 현재 스레드를 차단하지만 조건부 대기가 아니지만 잠금이 다른 스레드에 의해 유지되기 때문에 ArrayBlockingqueue의 메소드 작동 시간이 길지 않기 때문에 스레드를 차단하지 않는 것과 같습니다.
3.2 풋 방법
// 큐 끝에 새 요소를 추가합니다. 대기열이 가득 차면 현재 스레드가 대기됩니다. 인터럽트 예외에 대한 응답 공개 무효 풋 (e e) 던지기 중국적 외환 {checknotnull (e); 최종 재진입 락 잠금 = this.lock; // 잠금을 사용하여 멀티 스레드가 멤버 속성의 보안을 수정했는지 확인하십시오. {// 큐가 가득 찬 경우 tonefull.await () 메소드를 호출하여 현재 스레드가 큐가 가득 차 있지 않을 때까지 기다릴 수 있도록하십시오 (count == items.length) notfull.await (); // eNqueue 메소드를 호출하여 요소를 큐 eNqueue (e)에 삽입합니다. } 마침내 {lock.unlock (); }}제안 방법의 일반적인 프로세스는 제안 방법과 동일합니다. 그러나 큐가 가득 차면 Notefull.await () 메소드가 호출되어 현재 스레드 블록을 만들고 다른 스레드에 의해 대기열이 제거 될 때까지 기다립니다. 대기열이 만족스럽지 않으면 대기 실이 깨어납니다.
3.3 제안 (E e, 긴 타임 아웃, 시간 유닛 단위) 방법
/*** 대기열 끝에 새 요소를 추가합니다. 대기열에 사용 가능한 공간이 없으면 현재 스레드가 대기합니다. * 대기 시간이 시간 초과를 초과하면 False가 반환되어 추가 실패*/ public boolean 제안 (E e, Long Timeout, TimeUnit Unit)이 중단 된 예시 {CheckNotNull (e); // 최대 대기 대기의 시간 값을 계산합니다. 총 나노 길이 nanos = init.tonanos (timeout); 최종 재진입 락 잠금 = this.lock; // 잠금을 사용하여 멤버 속성의 멀티 스레드 수정을 lock.lockinterruptibly (); try {// 큐가 가득 찼습니다. 거짓을 반환하여 추가가 실패했음을 나타냅니다. if (nanos <= 0)가 false를 반환합니다. // notfull.awaitnanos (nanos) 메소드를 호출하면 타임 아웃 Nanos 시간이 자동으로 깨어납니다. // 사전에 깨어나면 나머지 시간은 반환됩니다. } // eNqueue 메소드를 호출하여 요소를 큐 enqueue (e)에 삽입합니다. 진실을 반환하십시오. } 마침내 {lock.unlock (); }}PUT 방법의 일반적인 흐름은 PUT 방법과 동일하며, 현재 스레드를 대기하고 대기 시간을 설정하도록하기 위해 NOTFULL.AWAITNANOS (NANOS) 방법을 호출합니다.
4. 요소 방법을 삭제합니다
4.1 제거 () 및 poll () 메소드 :
// AbstractQueue 부모 클래스에서 메소드를 호출합니다. public e remove () {// poll e x = poll ()을 호출하여 구현; if (x! = null) x; 그렇지 않으면 새로운 nosuchelementexception ()을 던집니다. } // 큐의 첫 번째 요소 (예 : 큐 헤더)를 삭제하고 반환합니다. 대기열이 비어 있으면 예외가 발생하지 않지만 NULL을 반환합니다. public e poll () {Final ReintrantLock Lock = this.lock; // 잠금을 사용하여 멤버 속성의 다중 스레드 수정을 lek.lock (); 시도 {// count == 0이면 목록이 비어 있으면 null을 반환하십시오. 그렇지 않으면 Dequeue 메소드를 호출하여 목록 헤더 요소 (count == 0)를 반환합니까? null : dequeue (); } 마침내 {lock.unlock (); }}제거 방법은 poll () 메소드를 호출하여 구현됩니다. Poll () 메소드에서 목록이 비어 있으면 NULL을 반환합니다. 그렇지 않으면 Dequeue 메소드가 호출되어 목록 헤더 요소를 반환합니다.
4.2 Take () 메소드
/*** 큐의 첫 번째 요소를 반환하고 제거합니다. 대기열이 비어 있으면 전면 스레드가 대기됩니다. 응답 인터럽트 예외*/ public e take take ()가 중단 된 결과 {최종 ReintrantLock Lock = this.lock; // 잠금을 사용하여 멀티 스레드가 멤버 속성의 보안을 수정했는지 확인하십시오. {// 대기열이 비어 있으면 try {// notempty.await () 메소드를 호출하여 현재 스레드가 대기하도록합니다. // 다른 스레드가 요소를 큐에 삽입 할 때까지 스레드가 깨어납니다. while (count == 0) notempty.await (); // 목록 헤더 요소 return dequeue ()를 반환하려면 Dequeue 메소드를 호출합니다. } 마침내 {lock.unlock (); }}Take () 메소드가 비어 있으면 현재 스레드는 다른 스레드가 새로운 요소를 큐에 삽입 할 때까지 기다려야하며 스레드가 깨어납니다.
4.3 설문 조사 (긴 타임 아웃, 시간 유닛) 방법
/*** 큐의 첫 번째 요소를 반환하고 제거합니다. 대기열이 비어 있으면 전면 스레드가 대기됩니다. * 대기 시간이 타임 아웃을 초과하면 False가 반환되어 요소가 실패했음을 나타냅니다.*/ public e poll (긴 타임 아웃, 시간 유닛 단위)이 중단 된 결과를 던졌습니다. {// 총 나노 길이 나노스에서 최대 대기 시간 값을 계산합니다. 최종 재진입 락 잠금 = this.lock; // 잠금을 사용하여 멤버 속성의 다중 스레드 수정을 lek.lockinterruptibly (); try {// 큐는 비어있는 동안 비어 있습니다 (count == 0) {// nanos <= 0은 최대 대기 시간이 도착했음을 의미하므로 더 이상 기다릴 필요가없고 NULL을 반환 할 필요가 없습니다. if (nanos <= 0) return null; // 스케줄을 대기하고 타임 아웃 시간을 설정하기 위해 notempty.awaitnanos (nanos) 메소드를 호출하십시오. nanos = notempty.awaitnanos (nanos); } // 목록 헤더 요소 return dequeue ()를 반환하려면 dequeue 메소드를 호출합니다. } 마침내 {lock.unlock (); }}Take () 메소드 프로세스와 마찬가지로, 현재 스레드를 대기하고 대기 시간을 설정하도록하기 위해 Awaitnanos (NANOS) 메소드라고합니다.
5. 요소를 보는 방법
5.1 요소 () 및 peek () 메소드
// AbstractQueue 부모 클래스에서 메소드를 호출합니다. public e element () {e x = peek (); if (x! = null) x; 그렇지 않으면 새로운 nosuchelementexception ()을 던집니다. } //보기 큐 헤더 요소보기 public e peek () {Final ReintrantLock Lock = this.lock; // 잠금을 사용하여 멤버 속성의 다중 스레드 수정을 lek.lock (); 시도 {// 현재 큐 헤더의 요소를 반환합니다. 반환 Itemat (TakeIndex); // 큐가 비어있을 때 null} 마침내 {lock.unlock (); }}VI. 다른 중요한 방법
6.1 Enqueue 및 Dequeue 방법
// 큐의 꼬리에 요소 X를 삽입 개인 무효 ENQUEUE (e x) {// assert lock.getholdCount () == 1; // 항목을 주장합니다 [pitindex] == null; // 현재 푸틴 덱스 위치 요소는 null final 객체 [] items = this.Items; 항목 [푸틴 덱스] = x; // posindex가 items.length와 같으면 potindex를 0으로 재설정하십시오. // 큐 수에 하나의 카운트 ++를 추가합니다. // 요소가 삽입되어 있으므로 현재 대기열은 분명히 비어 있지 않습니다. 그런 다음 무의미한 조건에서 기다리는 실을 깨우십시오. } // 큐 헤더의 요소를 삭제하고 반환 개인 e dequeue () {// assert lock.getholdcount () == 1; // 항목을 주장합니다 [takeIndex]! = null; 최종 개체 [] 항목 = this.Items; // 현재 큐 헤더의 요소를 가져옵니다 @SuppressWarnings ( "확인되지 않은") e x = (e) 항목 [TakeIndex]; // 현재 큐 헤더 위치를 null 항목으로 설정합니다 [takeIndex] = null; if (++ takeIndex == items.length) takeIndex = 0; // 큐의 수를 1 카운트 씩 빼냅니다. if (itrs! = null) itrs.elementDequeued (); // 요소가 삭제되기 때문에 대기열은 확실히 만족하지 않으므로, NETULL 조건에서 대기하는 스레드를 깨우십시오. Signal (); 반환 x; }이 두 방법은 각각 요소를 삽입하고 큐에서 요소를 제거합니다. 그리고 그들은 대기 실을 깨우고 싶어합니다. 요소를 삽입 한 후에는 대기열이 비어 있지 않아야하므로, 무의미한 조건에서 대기하는 스레드를 깨우 려면야합니다. 요소를 삭제 한 후에는 대기열이 불만족 스러워야하므로 공허 조건에서 대기하는 스레드를 깨우 려야합니다.
6.2 제거 (Object O) 메소드
// 큐에서 OBECT O 요소를 삭제하고 최대 하나의 공개 부울 제거 (오브젝트 O) {if (o == null) RETERT FALSE를 삭제합니다. 최종 개체 [] 항목 = this.Items; // 잠금을 사용하여 멤버 속성의 다중 스레드 수정의 보안을 보장합니다. lock.lock (); {// 대기열에 값이있는 경우에만 삭제하십시오 {// 삭제하십시오. if (count> 0) {// 큐의 끝에서 다음 위치 최종 int potindex = this.putindex; // 큐 헤더의 위치 int i = takeIndex; do {// 현재 위치 요소는 삭제 된 요소와 동일합니다. if (o.equals (items [i])) {// i 위치 요소 removeat (i); // true return true를 반환합니다. } if (++ i == items.length) i = 0; // i == posIndex가 모든 요소가 가로 든 것을 의미 할 때} while (i! = posindex); } false를 반환합니다. } 마침내 {lock.unlock (); }} 큐에서 지정된 객체 O를 삭제 한 다음 큐를 가로 지르고 객체 O와 동일한 첫 번째 요소를 삭제해야합니다. 큐에 Object O 요소가 없으면 False를 삭제하여 삭제하지 못했습니다.
주목 할 두 가지 점이 있습니다.
대기열을 가로 지르는 방법은 대기열의 헤드에서 큐 끝까지 가로 지르는 것입니다. TakeIndex와 Pitindex의 두 변수에 따라 다릅니다.
객체 [] 항목 = this.Items; 이 코드는 동기식 잠금 코드 블록에 배치되지 않습니다. 항목은 회원 변수입니다. 스레드가 너무 많으면 동시성 문제가 없습니까?
항목은 기본 데이터 유형이 아닌 참조 변수이며 큐의 삽입 및 삭제 작업은이 항목 배열에 대한 것이며 배열에 대한 참조는 변경되지 않기 때문입니다. 따라서 잠금 코드에서 항목은 다른 스레드로 최신 수정을받습니다. 그러나 int putindex = this.putindex; 메소드 코드 블록을 외부로 잠그면 문제가 발생합니다.
제거 (최종 int remodIndex) 메소드
// 큐에서 요소를 삭제합니다. removeIndex 위치 void removeat (final int removeindex) {// assert lock.getholdcount () == 1; // 항목을 주장합니다 [removeIndex]! = null; // assert remodIndex> = 0 && remodIndex <eItent.length; 최종 개체 [] 항목 = this.Items; //리스트 헤더로 요소를 삭제하는 것이 훨씬 쉽다는 것을 의미합니다. 이는 Dequeue 메소드 프로세스와 유사한 (removeindex == takeIndex) {// removeindex 위치 항목에서 요소를 제거합니다 [takeIndex] = null; // 배열의 끝이있을 때 (++ takeIndex == items.length) TakeIndex = 0 인 경우 배열 헤더 위치로 이동해야합니다. // 큐의 수를 1 카운트 씩 빼냅니다. if (itrs! = null) itrs.elementDequeued (); } else {// "인테리어"최종 int 폴드 덱스 제거 = this.putIndex; for (int i = remodIndex ;;) {int next = i + 1; if (next == items.length) next = 0; // 대기열의 끝이 아직 큐의 끝에 도달하지 못했고 다음 위치의 요소는 이전 위치의 요소에 의해 (다음! = potIndex) {항목 [i] = 항목 [다음]; i = 다음; } else {// 큐의 꼬리 요소를 큐 널 항목을 설정합니다 [i] = null; // potindex 값을 재설정 this.putindex = i; 부서지다; }} // 카운트 씩 큐 횟수를 줄입니다. if (itrs! = null) itrs.removedat (removeindex); } // 요소가 삭제되기 때문에 큐는 확실히 만족하지 않으므로, 불명확 한 조건에서 대기하는 스레드를 깨우십시오. signal (); }큐의 지정된 위치에서 요소를 삭제하십시오. 삭제 후 배열은 여전히 큐 형태를 유지할 수 있으며, 이는 두 가지 상황으로 나눌 수 있습니다.
위는이 기사의 모든 내용입니다. 모든 사람의 학습에 도움이되기를 바랍니다. 모든 사람이 wulin.com을 더 지원하기를 바랍니다.