Java併發——Semaphore

Semaphore

Semaphore即信號、信號系統。此類的主要作用就是限制線程併發的數量,如果不限制線程併發的數量,則CPU的資源很快就被耗盡,每個線程執行的任務是相當緩慢,因爲CPU要把時間片分配給不同的線程對象,而且上下文切換也要耗時,最終造成系統運行效率大幅降低,所以限制併發線程的數量還是非常有必要的。

Semaphore的同步性

public class Service {
    private Semaphore semaphore = new Semaphore(1);

    public void testMethod() {
        try {
            semaphore.acquire();
            System.out.println(Thread.currentThread().getName() + "begin:" + System.currentTimeMillis());
            Thread.sleep(2000);
            System.out.println(Thread.currentThread().getName() + "end:" + System.currentTimeMillis());
            semaphore.release();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        Service service = new Service();
        ThreadA a = new ThreadA(service);
        a.setName("A");
        a.start();
        ThreadB b = new ThreadB(service);
        b.setName("B");
        b.start();
        ThreadC c = new ThreadC(service);
        c.setName("C");
        c.start();
    }
}

class ThreadA extends Thread {
    private Service service;
    public ThreadA(Service service) {
        super();
        this.service = service;
    }
    @Override
    public void run() {
        service.testMethod();
    }
}
class ThreadB extends Thread {
    private Service service;
    public ThreadB(Service service) {
        super();
        this.service = service;
    }
    @Override
    public void run() {
        service.testMethod();
    }
}
class ThreadC extends Thread {
    private Service service;
    public ThreadC(Service service) {
        super();
        this.service = service;
    }
    @Override
    public void run() {
        service.testMethod();
    }
}

在這裏插入圖片描述
類Semaphore的構造函數參數permits是許可的意思,代表同一時間內,最多允許多少個線程同時執行acquire()和release()之間的代碼。無參方法acquire(的作用是使用1個許可,是減法操作。

其實還可以傳人>1的許可,代表同-時間內,最多允許有x個線程可以執行acquire()和release()之間的代碼。

acquire(int permits):
每調用一次方法,就使用permits個許可。

release(int permits):
每調用一次方法,就增加permits個許可,可以動態增加許可的個數:

Semaphore semaphore = new Semaphore(5);
        semaphore.acquire();
        semaphore.acquire();
        semaphore.acquire();
        semaphore.acquire();
        semaphore.acquire();
        System.out.println(semaphore.availablePermits());
        semaphore.release();
        semaphore.release();
        semaphore.release();
        semaphore.release();
        semaphore.release();
        semaphore.release();
        System.out.println(semaphore.availablePermits());
        semaphore.release(4);
        System.out.println(semaphore.availablePermits());

在這裏插入圖片描述

acquireUninterruptibly():
使等待進入acquire()方法的線程,不允許被中斷。

public class Service2 {
    private Semaphore semaphore = new Semaphore(1);

    public void test() {
        try {
            semaphore.acquire();
            System.out.println(Thread.currentThread().getName() + "begin:" + System.currentTimeMillis());
            for (int i = 0; i < Integer.MAX_VALUE / 100; i++) {
                String str = new String();
                Math.random();
            }
            System.out.println(Thread.currentThread().getName() + "end:" + System.currentTimeMillis());
            semaphore.release();
        } catch (InterruptedException e) {
            System.out.println("線程" + Thread.currentThread().getName() + "進入了Catch");
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Service2 service2 = new Service2();
        ThreadA a = new ThreadA(service2);
        a.setName("A");
        a.start();

        ThreadB b = new ThreadB(service2);
        b.setName("B");
        b.start();

        b.interrupt();
        System.out.println("main");

    }
}

class ThreadA extends Thread {
    private Service2 service;
    public ThreadA(Service2 service) {
        super();
        this.service = service;
    }

    @Override
    public void run() {
        service.test();
    }
}
class ThreadB extends Thread {
    private Service2 service;
    public ThreadB(Service2 service) {
        super();
        this.service = service;
    }

    @Override
    public void run() {
        service.test();
    }
}

線程B成功被中斷。
在這裏插入圖片描述
修改acquire方法:
在這裏插入圖片描述
再次運行效果如下:
在這裏插入圖片描述
acquireUninterruptibly()方法還有重載的寫法acquireUninterruptibly(int permits),此方法的作用是在等待許可的情況下不允許中斷,如果成功獲得鎖,則取得指定的permits許可個數。

availablePermits()和drainPermits()方法:

availablePermits()返回此Semaphore對象中當前可用的許可數,此方法通常用於調試,因爲許可的數量有可能實時在改變,並不是固定的數量。

drainPermits()可獲取並返回立即可用的所有許可個數,並且將可用許可置0。

Semaphore semaphore = new Semaphore(10);
        semaphore.acquire();
        System.out.println(semaphore.drainPermits() + " " + semaphore.availablePermits());
        System.out.println(semaphore.drainPermits() + " " + semaphore.availablePermits());
        System.out.println(semaphore.drainPermits() + " " + semaphore.availablePermits());
        semaphore.release();

在這裏插入圖片描述

getQueueLength()和hasQueuedThreads():

方法getQueueLength()的作用是取得等待許可的線程個數。

方法hasQueuedThreads()的作用是判斷有沒有線程在等待這個許可。

這兩個方法通常都是在判斷當前有沒有等待許可的線程信息時使用。

public class MyService {
    private Semaphore semaphore = new Semaphore(1);

    public void test() {
        try {
            semaphore.acquire();
            Thread.sleep(1000);
            System.out.println("還有大約" + semaphore.getQueueLength() + "個線程在等待");
            System.out.println("是否有線程正在等待信號量?" + semaphore.hasQueuedThreads());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            semaphore.release();
        }
    }

    public static void main(String[] args) {
        MyService myService = new MyService();

        MyThread firstThread = new MyThread(myService);
        firstThread.start();

        MyThread[] arr = new MyThread[4];
        for (int  i = 0; i < 4; i++) {
            arr[i] = new MyThread(myService);
            arr[i].start();
        }
    }
}

在這裏插入圖片描述

公平與非公平信號量:

有些時候,獲得許可的順序與線程啓動的順序有關,這時信號量就要分爲公平與非公平的。所謂的公平信號量是獲得鎖的順序與線程啓動的順序有關,但不代表100%地獲得信號量,僅僅是在概率上能得到保證。而非公平信號量就是無關的了。

public Semaphore(int permits, boolean fair) {
        sync = fair ? new FairSync(permits) : new NonfairSync(permits);
    }

構造中傳入true,即爲公平信號量;反之爲非公平。公平信號量運行的效果是線程啓動的順序與調用semaphore.acquire()的順序有關,也就是先啓動的線程優先獲得許可。

tryAcquire():
無參方法tryAcquire()的作用是嘗試地獲得1個許可,如果獲取不到則返回false,此方法通常與if語句結合使用,其具有無阻塞的特點。無阻塞的特點可以使線程不至於在同步處一直持續等待的狀態,如果if語句判斷不成立則線程會繼續走else語句,程序會繼續向下運行。

tryAcquire(int permits):
有參方法tryAcquire(int permits)的作用是嘗試地獲得permits個許可,如果獲取不到則返回false。

tryAcquire(long timeout, TimeUnit unit):
有參方法tryAcquire(int long timeout,TimeUnit unit)的作用是在指定的時間內嘗試地獲得1個許可,如果獲取不到則返回false。

tryAcquire(int permits, long timeout, TimeUnit unit):
有參方法tryAcquire(int permits,long timeout,TimeUnit unit)的作用是在指定的時間內嘗試地獲得permits個許可,如果獲取不到則返回false。

使用Semaphore實現多生產者/多消費者模式

public class RepastService {

    volatile private Semaphore setSemaphore = new Semaphore(10);//10個廚師
    volatile private Semaphore getSemaphore = new Semaphore(20);//食客
    volatile private ReentrantLock lock = new ReentrantLock();
    volatile private Condition setCondition = lock.newCondition();
    volatile private Condition getCondition = lock.newCondition();

    //只有3個盤子放菜品
    volatile private Object[] producePosition = new Object[4];

    private boolean isEmpty() {
        boolean isEmty = true;
        for (int i = 0; i < producePosition.length; i++) {
            if (producePosition[i] != null) {
                isEmty = false;
                break;
            }
        }
        if (isEmty == true) {
            return true;
        } else {
            return false;
        }
    }

    private boolean isFull() {
        boolean isFull = true;
        for (int i = 0; i < producePosition.length; i++) {
            if (producePosition[i] == null) {
                isFull = false;
                break;
            }
        }
        return isFull;
    }

    public void set() {
        try {
            setSemaphore.acquire();//允許同時最多有10個廚師炒菜
            lock.lock();
            while (isFull()) {
                setCondition.await();
            }
            for (int i = 0; i < producePosition.length; i++) {
                if (producePosition[i] == null) {
                    producePosition[i] = "菜";
                    System.out.println(Thread.currentThread().getName() + "生產了" + producePosition[i]);
                    break;
                }
            }
            getCondition.signalAll();
            lock.unlock();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            setSemaphore.release();
        }
    }

    public void get() {
        try {
            getSemaphore.acquire();//允許同時最多20個食客
            lock.lock();
            while (isEmpty()) {
                getCondition.await();
            }
            for (int i = 0; i < producePosition.length; i++) {
                if (producePosition[i] != null) {
                    System.out.println(Thread.currentThread().getName() + "吃了" + producePosition[i]);
                    producePosition[i] = null;
                    break;
                }
            }
            setCondition.signalAll();
            lock.unlock();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            getSemaphore.release();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        RepastService service = new RepastService();
        ThreadP[] arrP = new ThreadP[60];
        ThreadC[] arrC = new ThreadC[60];
        for (int i = 0; i < 60; i++) {
            arrP[i] = new ThreadP(service);
            arrC[i] = new ThreadC(service);
        }
        Thread.sleep(2000);
        for (int i = 0; i < 60; i++) {
            arrP[i].start();
            arrC[i].start();
        }
    }
}

//廚師
class ThreadP extends Thread {
    private RepastService service;

    public ThreadP(RepastService service) {
        this.service = service;
    }

    @Override
    public void run() {
        service.set();
    }
}

//食客
class ThreadC extends Thread {
    private RepastService service;

    public ThreadC(RepastService service) {
        this.service = service;
    }

    @Override
    public void run() {
        service.get();
    }
}

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章