[잡담] 생산 보고. - 소비 패턴.

17993 단어
생산자 와 소비자 사이 에는 왜 하나의 대열 이 격 리 되 어 있 습 니까?
우선 생산자 와 소비 자 는 속도 가 일치 하지 않 기 때문에 버퍼 링 에 사용 할 공간 이 필요 하 다.이것 은 생산자 와 소비 자 를 결합 시 킬 수 있다. 생산자 가 데 이 터 를 생산 할 때 데 이 터 를 소비자 에 게 넘 기지 않 아 도 된다. 데 이 터 를 버퍼 에 버 리 면 된다.이렇게 하면 각자 할 수 있다.
왜 버퍼 가 하나의 대기 열 입 니까?
일반적으로 이 버퍼 의 데이터 구 조 는 질서 있 는 대기 열 입 니 다.실제로 처리 순서 에 대한 요구 가 없다 면 굳이 대열 을 써 야 하 는 것 도 아니다.틈 을 내 도 좋다.
왜 버퍼 에 접근 할 때 자 물 쇠 를 얻 습 니까?
버퍼 구역 이라는 데이터 구 조 는 다 중 스 레 드 에 의 해 동시 방문 (생산자, 소비자 스 레 드) 되 기 때문에 자 물 쇠 를 추가 해 야 합 니 다. 한편 으로 는 구조 가 파괴 되 지 않도록 보호 하고 다른 한편 으로 는 코드 의 정확성 을 확보 해 야 합 니 다.
이렇게 하면 되 는 거 아니 야?
네, 사용 할 수 있 지만 성능 이 떨 어 질 수 있 습 니 다.
왜 성능 이 비교적 떨 어 집 니까?
버퍼 가 가득 찼 습 니 다.생산 자 는 항상 안 으로 물건 을 잃 어 버 리 려 고 시도 하기 때문에 '자물쇠 획득 - 자물쇠 방출 - 자물쇠 획득 - 자물쇠 방출' 을 해 왔 다.한편, 생산자 가 공전 하고 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();

좋은 웹페이지 즐겨찾기