基于信号量的统一线程池机制

7 篇文章 0 订阅
5 篇文章 0 订阅

    上次说要写一篇关于统一线程池的文章,拖了好久,下午就来写一写,温故而知新。

线程池机制大家都很熟悉,把任务添加到池中,然后按照一定的机制使用多个线程来执行任务。在java中通常是使用以下代码来创建。newCachedThreadPool()是内置的几种获取池的方法。注释说:Creates a thread pool that creates new threads as needed, but will reuse previously constructed threads when they are available. These pools will typically improve the performance of programs that execute many short-lived asynchronous tasks. 创建一个可以线程重用的池,线程在需要时创建。在执行大量短期异步的任务时通常可以提高性能。更多信息请见线程池 threadPoolExecutor详解

ExecutorService executorService = Executors.newCachedThreadPool()  
    由于我们的入口都是由quartz定时触发的,触发后的每次任务都要new一个Pool。这就可能带来一个问题。如果quartz的Concurrent设置成了任务可以并发,那每次时间一到都要new一个pool,如果一个pool中有子任务执行时间太长,导致pool对象不能被回收,那pool和线程就会越来越多,会出问题的。大boss提出了构建一个全局的pool,所有需要多线程都到这来申请。然后我就把创建pool和添加到pool的方法写到单独一个类中,利用Future代表每个任务的状态,在程序中任务一个地方的task要添加到pool中时先用future.isDone()判断是否有空闲,有了则加进来。咋一听感觉可以哟...master王看了说这不好,假假的。他花了一个下午的时间,构思出了一个符合场景的。赞!

    要和信号量结合起来。

    Semaphore当前在多线程环境下被扩放使用,操作系统的信号量是个很重要的概念,在进程控制方面都有应用。Java 并发库 的Semaphore 可以很轻松完成信号量控制,Semaphore可以控制某个资源可被同时访问的个数,通过 acquire() 获取一个许可,如果没有就等待,而release() 释放一个许可。比如在Windows下可以设置共享文件的最大客户端访问个数。 

    Semaphore实现的功能就类似厕所有5个坑,假如有10个人要上厕所,那么同时只能有多少个人去上厕所呢?同时只能有5个人能够占用,当5个人中 的任何一个人让开后,其中等待的另外5个人中又有一个人可以占用了。另外等待的5个人中可以是随机获得优先机会,也可以是按照先来后到的顺序获得机会,这取决于构造Semaphore对象时传入的参数选项。单个信号量的Semaphore对象可以实现互斥锁的功能,并且可以是由一个线程获得了“锁”,再由另一个线程释放“锁”,这可应用于死锁恢复的一些场合。

    显然这里用信号量控制比判断任务的状态更好。不用关心pool的大小,只需要初始化Semaphore的总许可数。根据任务类的类名分“块”,一个子Semaphore控制一个block,任务提交时判断相应的块有没有,有了就从子Semaphore获取许可。任务类中定义一个私有Semaphore变量,将上步获取到许可的Semaphore通过反射注入到实例中,在run方法的最后面释放掉Semaphore,接着executorService.execute(task); 所有的子Semaphore的许可总数一定等于全局Semaphore的许可总数。

    首先来看看变量。

	private final String[] KEY_WORDS = {"syncSemaphore", "BLOCK_SIZE", "SUB_SEMAPHORE"};
	private final String DEFAULT_SIZE = "50";
	private final boolean MODEL = true; //true代表公平模式,谁等待的久谁先获取
	//线程池
	private ExecutorService executorService = Executors.newCachedThreadPool();
	//全局信号量
        private Semaphore semaphore = null;
        //信号量块区
        private ConcurrentHashMap<String, Map<String, Object>> blockSeamphore = new ConcurrentHashMap<String, Map<String, Object>>();
    初始化信号量:

     public void initPool() {
    	this.setSeamaphore(Integer.valueOf(confSize));
    	logger.info("线程池初始化大小:" + confSize);
     }

    public void setSeamaphore(int size) {
        if(0 >= size) {
            logger.info("信号量值设置非法!");
            return;
        }
        this.semaphore = new Semaphore(size, MODEL);
        logger.info("线程池大小设置为:" + size);
    }
    定义成单例的bean
<bean id="threadPoolFairEx" class="com.yicong.kisp.invoke.threadpool.ThreadPoolFairEx" scope="singleton"/>
    普通的线程类只需要实现一下Runnable接口,这里要改造一下。

public interface RunnableEx extends Runnable {
	//同步信号量
	public void syncSemaphore(Semaphore semaphore);
	//释放信号量
	abstract void releaseSema();
}
    再由我们自己编写的做具体业务的线程类来实现RunnableEx,并且定义一个私有subSemphore
public class SubmitMessageToThreadPool implements RunnableEx{

	private static Logger logger = Logger.getLogger(SubmitMessageToThreadPool.class);
        private Semaphore semaphore = null;
	
	@Override
	public void run() {
	    // 业务逻辑          
            releaseSema();
        }

	@Override
	public void syncSemaphore(Semaphore semaphore) {
	    this.semaphore = semaphore; // 此方法用
	}

	@Override
	public void releaseSema() {
	    semaphore.release(); // 释放掉当前的信号量,让其他线程可以申请
        }	
}
    syncSemaphore()在反射中使用,将块的semaphore赋给实例中的semaphore,让实例的semaphore变量指向块的semaphore,这样释放后块的semaphore就会得到更新。

    接下来就要把任务添加进pool了。

	ThreadPoolFairEx pool = (ThreadPoolFairEx) BeanHoldFactory.getBean("threadPoolFairEx");
	SubmitMessageToThreadPool submitMessageToThreadPool = (SubmitMessageToThreadPool) BeanHoldFactory
		.getApplicationContext().getBean(
			"submitMessageToThreadPool");
	
        pool.acquireBlock(submitMessageToThreadPool.class, Integer.valueOf(mobSize));
	pool.taskPressIn(submitMessageToThreadPool);
    从spring中取出pool实例,它是singleton的。再拿出一个任务实例,通过acqurieBlock()获取块,taskPressIn(task)添加。来看看acqurieBlock。

    public synchronized boolean acquireBlock(Class<?> clazz, int maxSize) throws Exception {
    	if(0 >= maxSize || null == clazz) return false;
    	int availablePermits = semaphore.availablePermits();
    	if(availablePermits < maxSize) {
    		logger.info("信号量剩余:" + availablePermits + ",还需:" + (maxSize-availablePermits) + "以足分配!");
    		return false;
    	}
    	String clazzName = clazz.getName();
    	if(blockSeamphore.containsKey(clazzName)) {
    		this.releaseBlock(clazz);
    	}
    	semaphore.acquire(maxSize);
    	Semaphore subSemphore = new Semaphore(maxSize, true);
    	Map<String, Object> m = new HashMap<String, Object>();
    	m.put(KEY_WORDS[1], maxSize);
    	m.put(KEY_WORDS[2], subSemphore);
    	blockSeamphore.put(clazzName, m);
    	logger.info(clazzName + "申请占用:" + maxSize + "线程已成功!");
    	return true;
    }
    先判断全局信号量是否还有足以分配。够的话从全局中acquire,再创建具有同数量信号量的子semaphore,放入map,key是类名,value是subSemaphore。
    public synchronized Semaphore taskPressIn(RunnableEx runnableEx) throws Exception {
    	if(null == runnableEx) throw new BizRuntimeException("任务实例不能为空!");
    	//获取任务类
    	Class<?> clazz = getClazz(runnableEx);
    	String clazaName = clazz.getName();
    	if(blockSeamphore.containsKey(clazaName)) {
    		Map<String, Object> m = blockSeamphore.get(clazaName);
    		Semaphore subSemaphore = (Semaphore) m.get(KEY_WORDS[2]);
        	//从块区获取信号量, 反射注入实例块区信号量
        	return this.getSemaphore(subSemaphore, runnableEx);
    	}
    	return this.getSemaphore(semaphore, runnableEx);
    }
    
    private Semaphore getSemaphore(Semaphore semaphore, RunnableEx runnableEx) throws Exception {
        //获取信号量
        this.acquireSemaphore(semaphore);
        //反射注入实例信号量
        this.injectToJobIns(runnableEx, semaphore);
        //任务实例压入线程池
        executorService.execute(runnableEx);
        return semaphore;
    }

takePressIn()传入线程实例,通过反射得到对应的类名,从map中获取到对应的semaphore。getSemaphore()从传入的semaphore acquire许可,然后动态注入到线程实例中的

syncSemaphore(Semaphore semaphore),保证实例使用的信号量对象是map中的对应块的那个。最后把execute任务,任务在run方法中做完后会releaseSema()。

注意到taskPressIn有一个if(blockSeamphore.containsKey(clazaName)) 的判断。前面有个申请block的操作,如果是一些零星的小任务,可以不用获取一个block和子semaphore,直接用全局的semaphore。

这就是我们构想的统一pool机制。目前运行状况良好。


20160318:这两天发现了问题,不同类型的任务实例(不同类型是指实现类不一样)在信号量分配相差悬殊时,会明显地影响了速度。通过Jprofiler的线程栏目看到有thread处于堵塞状态。经排查发现,是我们的同步机制的粒度太粗了,把一些不该锁的代码也锁上了。taskPressIn不应该用synchronized,这样用表示所有的实例都会被同步,即使是使用不同的信号量。应该是让使用同一个信号量的实例在获取许可时被同步。

public synchronized Semaphore taskPressIn(RunnableEx runnableEx) throws Exception {
    ......
}
在该方法里对semaphore上锁
 private Semaphore getSemaphore(Semaphore semaphore, RunnableEx runnableEx) throws Exception {
      //获取信号量
         synchornized(semaphore){
         this.acquireSemaphore(semaphore);
         } 
     //反射注入实例信号量
     this.injectToJobIns(runnableEx, semaphore);
     //任务实例压入线程池
     executorService.execute(runnableEx);
     return semaphore;
}

感悟:使用同步时粒度要小,能影响更少的代码就更少。


20160324:这两天有时间,在看concurrent包下面的几个类,很佩服这些作者,几个重要的类一定要好好看看,包括认真理解JUC , CAS , AQS。今天又看了信号量的一些东西,发现前几天就缩小锁粒度的貌似有问题。因为acuqire()方法本身就是堵塞的,无论多少个线程,获取不到许可的都会堵塞,这和synchronized锁信号量有什么区别吗?没有啊。这么想起来,根本就没有必要锁semaphore。改成这样。

 private Semaphore getSemaphore(Semaphore semaphore, RunnableEx runnableEx) throws Exception {
     //获取信号量,不用加同步
     this.acquireSemaphore(semaphore);
     //反射注入实例信号量
     this.injectToJobIns(runnableEx, semaphore);
     //任务实例压入线程池
     executorService.execute(runnableEx);
     return semaphore;
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值