1. 程式人生 > >執行緒同步基礎

執行緒同步基礎

class Cinema {
    private long vacanciesCinema1;
    private long vacanciesCinema2;

    private final Object controlCinema1;
    private final Object controlCinema2;

    public Cinema() {
        controlCinema1 = new Object();
        controlCinema2 = new Object();

        vacanciesCinema1 = 20;
        vacanciesCinema2 = 20;
    }

    public boolean sellTickets1(long number) {
        synchronized (controlCinema1) {
            if (number <= vacanciesCinema1) {
                vacanciesCinema1 -= number;
                return true;
            }
            else {
                return false;
            }
        }
    }

    public boolean sellTickets2(long number) {
        synchronized (controlCinema2) {
            if (number <= vacanciesCinema2) {
                vacanciesCinema2 -= number;
                return true;
            }
            else {
                return false;
            }
        }
    }

    public long getVacanciesCinema1() {
        return vacanciesCinema1;
    }

    public long getVacanciesCinema2() {
        return vacanciesCinema2;
    }
}

3. wait/notify/notifyAll

在生產者-消費者模型中,通常用一個buffer作為共享的資料結構,當buffer滿的時候,生產者不能再放入資料;當buffer空的時候,消費者不能取資料。對於以上兩種情況,java在Object類中提供了wait notify notifyAll方法。wait方法需要在同步程式碼塊中被呼叫,否則,將會丟擲IllegalMonitorStateException。 當被呼叫時,JVM將會使當前執行緒sleep,同時釋放控制同步程式碼塊的物件鎖,必須使用nofity或者notifyAll來喚醒該執行緒。

class EventStorage {
    int maxSize;
    LinkedList<Date> storage;
    
    public EventStorage() {
        maxSize = 10;
        storage = new LinkedList<>();
    }
    
    public synchronized void set() {
        while (storage.size() == maxSize) {
            try {
                wait();
            }
            catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        storage.offer(new Date());
        System.out.printf("Set: %d\n", storage.size());
        notifyAll();
    }
    
    public synchronized void get() {
        while (storage.size() == 0) {
            try {
                wait();
            }
            catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        
        storage.poll();
        System.out.printf("Get: %d\n", storage.size());
        notifyAll();
    }
}

4. Lock

Java提供了另一種同步程式碼塊的機制,它是基於Lock介面和它的實現類(例如ReentrantLock)來實現的,這種機制更加強大和靈活,主要的優點表現在:
1)Lock介面允許更加複雜的結構,synchronized關鍵字必須要用結構化的方法來獲取或者釋放同步程式碼塊;
2)Lock介面提供了一些額外的功能。例如tryLock()方法。
3)當只有一個寫和多個讀的執行緒時,Lock介面允許讀寫操作的分離
4)Lock介面的效能更高

class PrintQueue {
    private final Lock queueLock = new ReentrantLock();
    
    public void printJob(Object document) {
        queueLock.lock();

        try {
            long duration = (long)(Math.random()*10000);
            System.out.println(Thread.currentThread().getName() + ": PrintQueue: Printing a Job during " + (duration/1000) + " seconds");
            Thread.sleep(duration);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        finally {
            queueLock.unlock();
        }
    }
}

class Job implements Runnable {
    private PrintQueue printQueue;
    
    public Job(PrintQueue printQueue) {
        this.printQueue = printQueue;
    }

    @Override
    public void run() {
        System.out.printf("%s: Going to print a document\n", Thread.currentThread().getName());
        printQueue.printJob(new Object());
        System.out.printf("%s: The document has been printed\n", Thread.currentThread().getName()); 
    }
}

5. 讀寫鎖

ReadWriteLock是所有Lock介面中最重要的介面之一,ReentrentReadWriteLock是它的唯一實現類。該類有兩個鎖,一個是讀操作另一個是寫操作。它能夠同時包括多個讀操作,但是隻能有一個寫操作。當某個執行緒執行寫操作時,其他任何執行緒都不能執行讀操作。

class PricesInfo {
    private double price1;
    private double price2;

    private ReadWriteLock lock;

    public PricesInfo() {
        price1 = 1.0;
        price2 = 2.0;
        lock = new ReentrantReadWriteLock();
    }

    public double getPrice1() {
        lock.readLock().lock();
        double value = price1;
        lock.readLock().unlock();
        return value;
    }

    public double getPrice2() {
        lock.readLock().lock();
        double value = price2;
        lock.readLock().unlock();
        return value;
    }

    public void setPrices(double price1, double price2) {
        lock.writeLock().lock();
        this.price1 = price1;
        this.price2 = price2;
        lock.writeLock().unlock();
    }
}

6. 公平鎖

ReentrantLock和ReentrantReadWriteLock的建構函式可以傳入一個boolean型的引數fair,該引數預設為false,如果設定為true,則在多個執行緒等待同一個鎖時,選擇等待時間最長的執行緒優先執行。

class MessageQueue implements MyQueue {
    private Lock queueLock = new ReentrantLock(true);

    public void printJob(Object document) {
        queueLock.lock();
        try {
            long duration = (long)(Math.random()*10000);
            System.out.println(Thread.currentThread().getName() + ": PrintQueue: Printing a Job during " + (duration/1000) + " seconds");
            Thread.sleep(duration);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        finally {
            queueLock.unlock();
        }

        queueLock.lock();
        try {
            long duration = (long)(Math.random()*10000);
            System.out.println(Thread.currentThread().getName() + ": PrintQueue: Printing a Job during " + (duration/1000) + " seconds");
            Thread.sleep(duration);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        finally {
            queueLock.unlock();
        }
    }
}

7. 一個鎖的多個condition

class Buffer {
    private LinkedList<String> buffer;
    private int maxSize;
    private ReentrantLock lock;
    private Condition lines;
    private Condition space;
    private boolean pendingLines;

    public Buffer(int maxSize) {
        this.maxSize = maxSize;
        buffer = new LinkedList<>();
        lock = new ReentrantLock();
        lines = lock.newCondition();
        space = lock.newCondition();
        pendingLines = true; //是否還會有lines insert到Buffer中
    }

    public void insert(String line) {
        lock.lock();
        try {
            while(buffer.size() == maxSize) {
                space.wait();
            }
            buffer.offer(line);
            System.out.printf("%s: Inserted Line: %d\n", Thread.currentThread().getName(), buffer.size());
            lines.signalAll();
        }
        catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public String get() {
        String line = null;
        lock.lock();
        try {
            while ((buffer.size() == 0) && hasPendingLines()) {
                lines.await();
            }

            if(hasPendingLines()) {
                line = buffer.poll();
                System.out.printf("%s: Line Readed: %d\n", Thread.currentThread().getName(), buffer.size());
                space.signalAll();
            }
        } catch(InterruptedException e) {
            e.printStackTrace();
        }
        finally {
            lock.unlock();
        }

        return line;
    }

    public boolean hasPendingLines() {
        return pendingLines || buffer.size() > 0;
    }

    public void setPendingLines(boolean pendingLines) {
        this.pendingLines = pendingLines;
    }
}