Java 并發專題 : Semaphore 實現 互斥 與 連接池

jopen 8年前發布 | 12K 次閱讀 Android開發 移動開發

繼續并發方面的知識。今天介紹Semaphore,同樣在java.util.concurrent包下。

本來準備通過例子,從自己實現到最后使用并發工具實現,但是貌似效果并不是很好,有點太啰嗦的感覺,所有準備直入主題。

介紹:Semaphore中管理著一組虛擬的許可,許可的初始數量可通過構造函數來指定【new Semaphore(1);】,執行操作時可以首先獲得許可【semaphore.acquire();】,并在使用后釋放許可【semaphore.release();】。如果沒有許可,那么acquire方法將會一直阻塞直到有許可(或者直到被終端或者操作超時)。

作用:可以用來控制同時訪問某個特定資源的操作數量,或者某個操作的數量。

下面使用Semaphore實現兩個例子:

1、互斥

大家都學過操作系統,都知道互斥的概念,比較簡單的互斥實現,比如PV操作,判斷資源,然后忙等實現互斥;上一篇博客也說過,忙等對CPU的消耗巨大,下面我們通過Semaphore來實現一個比較好的互斥操作:

假設我們公司只有一臺打印機,我們需要對這臺打印機的打印操作進行互斥控制:

package com.zhy.concurrency.semaphore;

import java.util.concurrent.Semaphore;

/**
 * 使用信號量機制,實現互斥訪問打印機
 * 
 * @author zhy
 * 
 */
public class MutexPrint
{

    /**
     * 定義初始值為1的信號量
     */
    private final Semaphore semaphore = new Semaphore(1);

    /**
     * 模擬打印操作
     * @param str
     * @throws InterruptedException
     */
    public void print(String str) throws InterruptedException
    {
        //請求許可
        semaphore.acquire();

        System.out.println(Thread.currentThread().getName()+" enter ...");
        Thread.sleep(1000);
        System.out.println(Thread.currentThread().getName() + "正在打印 ..." + str);
        System.out.println(Thread.currentThread().getName()+" out ...");
        //釋放許可
        semaphore.release();
    }

    public static void main(String[] args)
    {
        final MutexPrint print = new MutexPrint();

        /**
         * 開啟10個線程,搶占打印機
         */
        for (int i = 0; i < 10; i++)
        {
            new Thread()
            {
                public void run()
                {
                    try
                    {
                        print.print("helloworld");
                    } catch (InterruptedException e)
                    {
                        e.printStackTrace();
                    }
                };
            }.start();
        }

    }

}

輸出結果:

Thread-1 enter ...
Thread-1正在打印 ...helloworld
Thread-1 out ...
Thread-2 enter ...
Thread-2正在打印 ...helloworld
Thread-2 out ...
Thread-0 enter ...
Thread-0正在打印 ...helloworld
Thread-0 out ...
Thread-3 enter ...
Thread-3正在打印 ...helloworld
Thread-3 out ...

通過初始值為1的Semaphore,很好的實現了資源的互斥訪問。

2、連接池的模擬實現

在項目中處理高并發時,一般數據庫都會使用數據庫連接池,假設現在數據庫連接池最大連接數為10,當10個連接都分配出去以后,現在有用戶繼續請求連接,可能的處理:

a、手動拋出異常,用戶界面顯示,服務器忙,稍后再試

b、阻塞,等待其他連接的釋放

從用戶體驗上來說,更好的選擇當然是阻塞,等待其他連接的釋放,用戶只會覺得稍微慢了一點,并不影響他的操作。下面使用Semaphore模擬實現一個數據庫連接池:

package com.zhy.concurrency.semaphore;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Semaphore;
/**
 * 使用Semaphore模擬數據庫鏈接池的使用
 * @author zhy
 *
 */
public class ConnectPool
{
    private final List<Conn> pool = new ArrayList<Conn>(3);
    private final Semaphore semaphore = new Semaphore(3);

    /**
     * 初始化分配3個連接
     */
    public ConnectPool()
    {
        pool.add(new Conn());
        pool.add(new Conn());
        pool.add(new Conn());
    }

    /**
     * 請求分配連接
     * @return
     * @throws InterruptedException
     */
    public Conn getConn() throws InterruptedException
    {
        semaphore.acquire();
        Conn c = null  ;
        synchronized (pool)
        {
            c = pool.remove(0);
        }
        System.out.println(Thread.currentThread().getName()+" get a conn " + c);
        return c ;
    }

    /**
     * 釋放連接
     * @param c
     */
    public void release(Conn c)
    {
        pool.add(c);
        System.out.println(Thread.currentThread().getName()+" release a conn " + c);
        semaphore.release();
    }

    public static void main(String[] args)
    {

        final ConnectPool pool = new ConnectPool();

        /**
         * 第一個線程占用1個連接3秒
         */
        new Thread()
        {
            public void run()
            {
                try
                {
                    Conn c = pool.getConn();
                    Thread.sleep(3000);
                    pool.release(c);
                } catch (InterruptedException e)
                {
                    e.printStackTrace();
                }
            };
        }.start();
        /**
         * 開啟3個線程請求分配連接
         */
        for (int i = 0; i < 3; i++)
        {
            new Thread()
            {
                public void run()
                {
                    try
                    {
                        Conn c = pool.getConn();
                    } catch (InterruptedException e)
                    {
                        e.printStackTrace();
                    }
                };
            }.start();
        }

    }

    private class Conn
    {
    }

}

Thread-0 get a conn com.zhy.concurrency.semaphore.ConnectPool$Conn@12b6651
Thread-2 get a conn com.zhy.concurrency.semaphore.ConnectPool$Conn@e53108
Thread-1 get a conn com.zhy.concurrency.semaphore.ConnectPool$Conn@1888759
Thread-0 release a conn com.zhy.concurrency.semaphore.ConnectPool$Conn@12b6651
Thread-3 get a conn com.zhy.concurrency.semaphore.ConnectPool$Conn@12b6651
我們測試時,讓Thread-0持有一個連接3秒,然后瞬間讓3個線程再去請求分配連接,造成Thread-3一直等到Thread-0對連接的釋放,然后獲得連接。


通過兩個例子,基本已經了解了Semaphore的用法,這里的線程池例子只是為了說明Semaphore的用法,真實的實現代碼比這復雜的多,而且可能也不會直接用Semaphore。


好了,之后會繼續Java并發的博客。

來自: http://blog.csdn.net//lmj623565791/article/details/26810813

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!