• Java 并发专题 : Semaphore 实现 互斥 与 连接池


    继续并发方面的知识。今天介绍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并发的博客。

  • 相关阅读:
    PHP入门:在Windows中安装PHP工作环境
    Oracle数据库优化的经验总结
    引入js和css文件的总结
    PHP的加密解密字符串函数
    js+html5双人五子棋(源码下载)
    JAVA的网络编程基础概念
    asp.net的code-Behind技术
    10个调试Java的技巧
    Oracle基础 各种语句的定义格式
    CSS行高line-height的理解
  • 原文地址:https://www.cnblogs.com/oversea201405/p/3752003.html
Copyright © 2020-2023  润新知