(二)線程同步_7---在一個鎖中使用多個條件(Condition)

在一個鎖中使用多個條件(Using multiple conditions in a Lock)

一個Lock可能關聯着一個或者多個條件,這些條件表如今Condition接口。這些條件(conditions)的目的是容許多個線程控制一個鎖對象而且檢查一個條件是真仍是假,當一個條件爲false時,那麼線程將會被掛起,直到其餘線程喚醒它;Condition接口提供了掛起一個線程和喚醒一個線程的機制;java

在以前的生產者消費者例子中,使用了Lock來同步臨界區,生產者和消費者都只有一個線程;在下面的例子中,生產者和消費者都將有多個線程,當緩衝區滿時,全部的生產者將會被掛起,消費者負責喚醒生產者;當緩衝區空時,消費者將被掛起,生茶者負責喚醒消費者;dom

動手實現

(1)建立一個大小固定的隊列做爲生產者和消費者的緩衝區;ide

public class MyQueue<T> {
    private ReentrantLock lock=new ReentrantLock();
    private Condition pullConditon=lock.newCondition();
    private Condition pushCondition=lock.newCondition();
    private int maxSize;
    private LinkedList<T> list=new LinkedList<>();

    public MyQueue(int size) {
        maxSize=size;
    }

    public void push(T t){
        lock.lock();
        try {
            while (list.size()== maxSize) {
                // Current push thread release lock and sleep.
                pushCondition.await();
            }
            list.push(t);
            System.out.printf("%s Push Size %d\n", Thread.currentThread().getName(), list.size());
            // Week up all pull thread
            pullConditon.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public T pull(){
        T t=null;
        lock.lock();
        try {
            while (list.size() == 0) {
                // Current poll thread release lock and sleep.
                pullConditon.await();
            }
            t=list.poll();
            System.out.printf("%s Pull Size %d\n", Thread.currentThread().getName(), list.size());
            //Week up all push threads
            pushCondition.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
        return t;
    }

}
(2)建立消費者

public class Consumer implements Runnable{
    private MyQueue<Integer> myQueue;

    public Consumer(MyQueue<Integer> myQueue) {
        this.myQueue = myQueue;
    }

    @Override
    public void run() {
        for (int i = 0; i < 50; i++) {
            myQueue.pull();
            try {
                Random random=new Random();
                Thread.sleep(random.nextInt(500));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

(3)建立生產者

public class Producer implements Runnable{
    private MyQueue<Integer> myQueue;

    public Producer(MyQueue<Integer> myQueue) {
        this.myQueue = myQueue;
    }

    @Override
    public void run() {
        for (int i = 0; i < 50; i++) {
            myQueue.push(i);
            try {
                Random random=new Random();
                Thread.sleep(random.nextInt(500));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
(4)Main,建立3個消費者,3個生產者

public class Main {
    public static void main(String[] args) {
        MyQueue<Integer> myQueue = new MyQueue<>(100);
        Thread[] producer = new Thread[3];
        Thread[] consumer = new Thread[3];
        for (int i=0;i<producer.length;i++) {
            producer[i] = new Thread(new Producer(myQueue));
        }
        for (int j=0;j<consumer.length;j++) {
            consumer[j] = new Thread(new Consumer(myQueue));
        }

        for (Thread c : consumer) {
            c.start();
        }
        for (Thread p : producer) {
            p.start();
        }

    }
}
因爲生產者和消費者同樣多,生產者生產的和消費者消費的恰好,因此最終程序會正常結束;

要點

在該例子中,當一個生產者線程佔用了鎖對象,那麼其餘的生產者線程和消費者線程都將被掛起,直到該線程釋放了鎖對象;當隊列滿時,生產者將執行pushConditon接口的await()方法,執行該方法,該線程將會被掛起,而且同時釋放掉鎖對象,從而容許其它線程執行;到消費者執行pushConditon的signalAll()方法時(注意這裏是signalAll()而不是notifyAll()),將會喚醒被全部被pushCondtion掛起的線程,即喚醒生產者;從這個例子生愈來愈感受Java線程機制的強大和靈活;

this

相關文章
相關標籤/搜索