[잡담] 생산 보고. - 소비 패턴.
우선 생산자 와 소비 자 는 속도 가 일치 하지 않 기 때문에 버퍼 링 에 사용 할 공간 이 필요 하 다.이것 은 생산자 와 소비 자 를 결합 시 킬 수 있다. 생산자 가 데 이 터 를 생산 할 때 데 이 터 를 소비자 에 게 넘 기지 않 아 도 된다. 데 이 터 를 버퍼 에 버 리 면 된다.이렇게 하면 각자 할 수 있다.
왜 버퍼 가 하나의 대기 열 입 니까?
일반적으로 이 버퍼 의 데이터 구 조 는 질서 있 는 대기 열 입 니 다.실제로 처리 순서 에 대한 요구 가 없다 면 굳이 대열 을 써 야 하 는 것 도 아니다.틈 을 내 도 좋다.
왜 버퍼 에 접근 할 때 자 물 쇠 를 얻 습 니까?
버퍼 구역 이라는 데이터 구 조 는 다 중 스 레 드 에 의 해 동시 방문 (생산자, 소비자 스 레 드) 되 기 때문에 자 물 쇠 를 추가 해 야 합 니 다. 한편 으로 는 구조 가 파괴 되 지 않도록 보호 하고 다른 한편 으로 는 코드 의 정확성 을 확보 해 야 합 니 다.
이렇게 하면 되 는 거 아니 야?
네, 사용 할 수 있 지만 성능 이 떨 어 질 수 있 습 니 다.
왜 성능 이 비교적 떨 어 집 니까?
버퍼 가 가득 찼 습 니 다.생산 자 는 항상 안 으로 물건 을 잃 어 버 리 려 고 시도 하기 때문에 '자물쇠 획득 - 자물쇠 방출 - 자물쇠 획득 - 자물쇠 방출' 을 해 왔 다.한편, 생산자 가 공전 하고 CPU 시간 영 화 를 낭비 하면 다른 스 레 드 의 스케줄 에 영향 을 줄 수 있다.이때 한 소비자 가 가지 고 있 는 데 이 터 를 처리 하고 하 나 를 더 꺼 내 처리 하려 고 한다 면 이때 생산자 와 소비 자 는 불필요 한 경쟁 을 할 것 이다. 이때 생산자 가 자 물 쇠 를 빼 앗 아 도 소 용이 없 기 때문이다.
이.. 이 걸 어 쩌 지?
간단 하 다. 두 가지 상황 으로 나 뉘 는데 하 나 는 버퍼 가 가득 찼 을 때 생산자 가 다시 안 으로 물건 을 잃 어 버 리 려 고 하면 걸 어 놓 는 것 이다.마찬가지 로 버퍼 가 비어 있 을 때 소비자 가 다시 안 으로 시도 하면 걸 어 놓는다.
그럼 언제 깨 울 까요?
똑 같 지 않 습 니까? 버퍼 에서 데이터 가 올 때 (무 에서 유 를 창조) 소비자 스 레 드 를 깨 웁 니 다.버퍼 에 여유 공간 이 있 을 때 (가득 차 서 불만 까지) 생산자 스 레 드 를 깨 웁 니 다.
그 코드 는 어떻게 써 야 합 니까?
우선 우 리 는 먼저 간단하게 '자물쇠' 를 실현 한다. 바로 아래 와 같다.
public class Lock {
/**
*
*/
private List waitThreads = new ArrayList<>();
/**
*
*/
private AtomicInteger guard = new AtomicInteger(0);
/**
*
*/
private AtomicInteger lockFlag = new AtomicInteger(0);
/**
*
*/
private Thread holder;
public void lock() {
if(Objects.equals(holder, Thread.currentThread())) // ,
return;
while(!guard.compareAndSet(0, 1)) //
;
if(lockFlag.intValue() == 0) {
lockFlag.set(1); // " "
holder = Thread.currentThread(); //
guard.set(0); //
} else {
waitThreads.add(Thread.currentThread()); //
guard.set(0); //
LockSupport.park(); //
holder = Thread.currentThread(); // ,
}
}
public void unlock() {
if(!Objects.equals(holder, Thread.currentThread())) //
return;
while(!guard.compareAndSet(0, 1))
;
if(waitThreads.size() == 0) { //
lockFlag.set(0); // , " "
holder = null;
guard.set(0); //
} else {
LockSupport.unpark(waitThreads.remove(0)); // ,
guard.set(0); //
}
}
}
그런 후에 우 리 는 버퍼 의 종 류 를 다시 실현 합 시다.
public class BufferCache {
/**
* ,
*/
private Object[] data;
/**
* =>
*/
private int readIndex;
/**
* =>
*/
private int writeIndex;
/**
*
*/
private int count;
/**
*
*/
private List waitProducers = new ArrayList<>();
/**
*
*/
private List waitConsumers = new ArrayList<>();
/**
*
*/
private Lock lock = new Lock();
public BufferCache(int initial) {
this.data = new Object[initial];
}
public void put(Object e) {
lock.lock(); //
while(count == data.length) { // ,
waitProducers.add(Thread.currentThread()); // ,
lock.unlock(); //
LockSupport.park(); //
lock.lock(); // ,
}
data[writeIndex] = e; //
count++;
if(++writeIndex == data.length) { //
writeIndex = 0;
}
while(waitConsumers.size() != 0) { //
LockSupport.unpark(waitConsumers.remove(0));
}
lock.unlock(); //
}
public Object take() { //
lock.lock();
Object e = null;
while (count == 0) {
waitConsumers.add(Thread.currentThread());
lock.unlock();
LockSupport.park();
lock.lock();
}
e = data[readIndex];
count--;
if(++readIndex == data.length) {
readIndex = 0;
}
while(waitProducers.size() != 0) {
LockSupport.unpark(waitProducers.remove(0));
}
lock.unlock();
return e;
}
private static class Task1 implements Runnable { //
private int num;
private BufferCache cache;
private String name;
public Task1(BufferCache cache, String index) {
this.name = "producer-" + index;
this.num = 0;
this.cache = cache;
}
@Override
public void run() {
String data;
while(true) { //
data = num + " from " + name;
cache.put(data);
System.out.println(name + " :" + data);
num++;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
private static class Task2 implements Runnable {
private BufferCache cache;
public Task2( BufferCache cache) {
this.cache = cache;
}
@Override
public void run() {
while(true) { //
Object e = cache.take();
System.out.println(" :" + e);
}
}
}
public static void main(String[] args) { //
BufferCache cache = new BufferCache(20);
Thread producer;
Thread consumer;
for(int i = 0; i < 5; i++) { // 5
producer = new Thread(new Task1(cache, i + ""));
producer.start();
}
for(int i = 0; i < 3; i++) { // 3
consumer = new Thread(new Task2(cache));
consumer.start();
}
}
}
왜 조건 판단 은 while 로 순환 해 야 합 니까? if 는 안 됩 니까?
만약 에 생산자 스 레 드 A 를 깨 웠 다 면 실행 을 재 개 했 을 때 버퍼 는 생산자 스 레 드 B 에 의 해 다시 채 워 졌 기 때문에 다시 한 번 판단 해 야 합 니 다.
왜 라인 이 회 복 될 때 자 물 쇠 를 다시 얻 습 니까?
자 물 쇠 를 얻 는 것 은 판단 과 집행 기간 에 조건 이 변 하지 않 기 위해 서다.이렇게 코드 를 실행 하 는 것 이 정확 하 다.더 자세 한 것 은 생산자 스 레 드 A 가 자 물 쇠 를 얻 을 때 다른 생산자 스 레 드 는 버퍼 의 상 태 를 바 꿀 수 없다 는 것 이다.
이 두 대기 행렬 은 조건 변수 와 비슷 한 것 같 습 니 다. 이것 은 조건 변수 와 무슨 관계 가 있 습 니까?
사실은 이것 이 조건 변수 입 니 다. 조건 변수의 본질은 하나의 대기 열 입 니 다. 조건 이 만족 하지 않 을 때 스 레 드 를 이 대기 열 에 넣 습 니 다.조건 이 만족 할 때 하나 이상 의 스 레 드 를 깨 워 계속 실행 할 수 있 습 니 다.
JDK 에서 BlockingQueue 의 실현 클래스 인 Array BlockingQueue 를 참고 하여 위의 코드 와 비슷 한 지 확인 하 세 요.
자물쇠 와 조건 변수의 관계
한편, 조건 변 수 는 하나의 대기 열 이기 때문에 다 중 스 레 드 가 접근 할 때 스 레 드 의 안전 을 확보 해 야 하기 때문에 보통 잠 금 대상 과 관련 이 있 습 니 다.이 대기 열 에 접근 하려 면 먼저 자 물 쇠 를 가 져 와 야 합 니 다.
다른 한편, 조건 판단 을 할 때 도 자 물 쇠 를 빼 놓 을 수 없다 (판단 과 집행 기간 에 조건 이 변 하지 않 을 것 을 보증한다)
따라서 조건 변수 와 자 물 쇠 는 한데 묶 여 있 거나 조건 변수 가 자 물 쇠 를 떠 날 수 없다.그 러 고 보 니 JDK 에 서 는 Condition 대상 이 Lock 대상 에 의 해 생 성 된다 는 것 을 쉽게 이해 할 수 있다.
Condition x = lock.newCondition();
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
다양한 언어의 JSONJSON은 Javascript 표기법을 사용하여 데이터 구조를 레이아웃하는 데이터 형식입니다. 그러나 Javascript가 코드에서 이러한 구조를 나타낼 수 있는 유일한 언어는 아닙니다. 저는 일반적으로 '객체'{}...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.