前几天客户提了新需求,需要写一个服务并且利用多线程去处理。虽然以前有这种例子但是是方法级的,这里写一个全局的来使用。这几天都在测试今天有空把了解的东西都写下来希望以后再用到直接找自己的博客就可以了。希望做自己的百度。
这几天心得如下:
有摘抄百度的东西:
JAVA中自带的线程池类为 java.util.concurrent.ThreadPoolExecutor
常用首选构造方法:
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,
long keepAliveTime, TimeUnit unit,
BlockingQueue
RejectedExecutionHandler handler)
如果没有特别需求,一般都是这个构造。
corePoolSize: 线程池维护线程的最少数量:核心线程数,会一直存活,即使没有任务,线程池也会维护线程的最少数量
当核心线程被用完以后后来的任务会被加入队列中。
maximumPoolSize:线程池维护线程的最大数量
当队列满的时候会在开新的线程执行任务,直到达到最大线程数,后面的任务则使用策略。
keepAliveTime: 线程池维护线程所允许的空闲时间:线程池维护线程所允许的空闲时间,当线程空闲时间达到keepAliveTime,该线程会退出,直到线程数量等于corePoolSize。如果allowCoreThreadTimeout设置为true,则所有线程均会退出直到线程数量为0。(不常用到这个服务的童鞋可以这样设置)
unit: 线程池维护线程所允许的空闲时间的单位:可选参数值为:TimeUnit中的几个静态属性:NANOSECONDS、MICROSECONDS、MILLISECONDS、SECONDS。(一般用毫秒或秒)
workQueue: 线程池所使用的缓冲队列:线程池所使用的缓冲队列,常用的是:java.util.concurrent.ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue
handler: 线程池对拒绝任务的处理策略: 线程池中的数量大于maximumPoolSize,对拒绝任务的处理策略,默认值ThreadPoolExecutor.AbortPolicy()。
RejectedExecutionHandler 默认有四个选择:
ThreadPoolExecutor.AbortPolicy()当线程池中的数量等于最大线程数时、直接抛出抛出java.util.concurrent.RejectedExecutionException异常。
ThreadPoolExecutor.CallerRunsPolicy() 当线程池中的数量等于最大线程数时、重试执行当前的任务,交由调用者线程来执行任务(我的服务用的是这个)。
ThreadPoolExecutor.DiscardOldestPolicy() 当线程池中的数量等于最大线程数时、抛弃线程池中最后一个要执行的任务,并执行新传入的任务(项目中以前都用的这个策略)。
ThreadPoolExecutor.DiscardPolicy() 当线程池中的数量等于最大线程数时,不做任何动作。
shutdown() 当前空闲的线程interrupt掉,正在执行的会等执行完成。
不shutdown()等线程池任务执行完以后只会留核心线程数,其他的也会停止。
方法级线程池使用,全局的我没使用。
队列有三种通用策略(摘抄自百度):
直接提交。工作队列的默认选项是 SynchronousQueue,它将任务直接提交给线程而不保持它们。在此,如果不存在可用于立即运行任务的线程,则试图把任务加入队列将失败,因此会构造一个新的线程。此策略可以避免在处理可能具有内部依赖性的请求集时出现锁。直接提交通常要求无界 maximumPoolSizes 以避免拒绝新提交的任务。当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。
无界队列。使用无界队列(例如,不具有预定义容量的 LinkedBlockingQueue)将导致在所有 corePoolSize 线程都忙时新任务在队列中等待。这样,创建的线程就不会超过 corePoolSize。(因此,maximumPoolSize 的值也就无效了。)当每个任务完全独立于其他任务,即任务执行互不影响时,适合于使用无界队列;例如,在 Web 页服务器中。这种排队可用于处理瞬态突发请求,当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。
有界队列。当使用有限的 maximumPoolSizes 时,有界队列(如 ArrayBlockingQueue)有助于防止资源耗尽,但是可能较难调整和控制。队列大小和最大池大小可能需要相互折衷:使用大型队列和小型池可以最大限度地降低 CPU 使用率、操作系统资源和上下文切换开销,但是可能导致人工降低吞吐量。如果任务频繁阻塞(例如,如果它们是 I/O 边界),则系统可能为超过您许可的更多线程安排时间。使用小型队列通常要求较大的池大小,CPU 使用率较高,但是可能遇到不可接受的调度开销,这样也会降低吞吐量。
我认为项目里不可能使用无界队列,有资源耗尽风险。
使用有界队列,需要考量资源分配,少线程大队列(我目前使用这种方案,不过根据业务人员的操作频率与数量级分析,想改为小队列线程数大,这还需要考虑服务器硬件水平),或者大数量线程小队列。
线程多开得多耗得资源也多。
截图貌似要上传图片太麻烦了,直接考了代码来(项目使用JDK1.6):
执行:
一个任务通过execute(Runnable)方法添加进线程池,被线程池中的线程执行,执行的就是
Runnable中的run()方法。
这时:execute方法先判断空指针,然后判断当前线程数量是否大于核心线程数,如果小于则调用addIfUnderCorePoolSize。
再次判断如果线程池的状态不为运行状态或当前线程池数为0,则调用ensureQueuedTaskHandled方法
判断线程池运行,如果状态不为运行状态,从workQueue中删除, 并调用reject做拒绝处理。
如线程池workQueue offer失败或不处于运行状态,调用addIfUnderMaximumPoolSize,addIfUnderMaximumPoolSize方法基本和addIfUnderCorePoolSize实现类似,不同点在于根据最大线程数(maximumPoolSize)进行比较,如果超过最大线程数,返回false,调用reject方法
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();//空指针
if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) {
if (runState == RUNNING && workQueue.offer(command)) {
if (runState != RUNNING || poolSize == 0)
ensureQueuedTaskHandled(command);
}
else if (!addIfUnderMaximumPoolSize(command))
reject(command); // is shutdown or saturated
}
}
addIfUnderCorePoolSize首先加锁,防并发,再次判断当前线程数小于corePoolSize并且线程池处于RUNNING状态,则调用addThread增加线程
private boolean addIfUnderCorePoolSize(Runnable firstTask) {
Thread t = null;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
if (poolSize < corePoolSize && runState == RUNNING)
t = addThread(firstTask);
} finally {
mainLock.unlock();
}
if (t == null)
return false;
t.start();
return true;
}
addThread方法首先创建Work对象,然后调用threadFactory创建新的线程,
如果创建的线程不为null,将Work对象的thread属性设置为此创建出来的线程,
并将此Work对象放入workers中,然后在增加当前线程池的中线程数,
增加后回到addIfUnderCorePoolSize方法 ,释放mainLock,最后启动这个新创建的线程来执行新传入的任务。
private Thread addThread(Runnable firstTask) {
Worker w = new Worker(firstTask);
Thread t = threadFactory.newThread(w);
if (t != null) {
w.thread = t;
workers.add(w);
int nt = ++poolSize;
if (nt > largestPoolSize)
largestPoolSize = nt;
}
return t;
}
addIfUnderCorePoolSize中最后t.start();执行Worker的run方法先执行传入的任务然后一直循环获取新的任务并执行。
public void run() {
try {
Runnable task = firstTask;
firstTask = null;
while (task != null || (task = getTask()) != null) {
runTask(task);
task = null;
}
} finally {
workerDone(this);
}
}
通过WorkQueue的poll或task方法来获取下一个要执行的任务。
Runnable getTask() {
for (;;) {
try {
int state = runState;
if (state > SHUTDOWN)
return null;
Runnable r;
if (state == SHUTDOWN) // Help drain queue
r = workQueue.poll();
else if (poolSize > corePoolSize || allowCoreThreadTimeOut)//则通过poll取任务,若等待一定的时间取不到任务,则返回null
r = workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS);
else
r = workQueue.take();
if (r != null)
return r;
if (workerCanExit()) {
if (runState >= SHUTDOWN) // Wake up others
interruptIdleWorkers();
return null;
}
// Else retry
} catch (InterruptedException ie) {
// On interruption, re-check runState
}
}
}
然后也就是判断一下:说如果线程池处于STOP状态、或者任务队列已为空或者允许为核心池线程设置空闲存活时间并且线程数大于1时,允许worker结束。如果允许worker退出,则调用interruptIdleWorkers()中断处于空闲状态的worker。
以上总结为:
当添加任务进行处理时:
1.当前线程数量小于核心线程数量,并处于running状态,创建添加新任务给新的线程。
2.如果当前线程池中的数量等于corePoolSize,并线程池处于Running状态,缓冲队列 workQueue未满,那么任务被放入缓冲队列、等待任务调度执行。
3.如果当前线程池中的数量大于corePoolSize,缓冲队列workQueue已满,并且线程池中的数量小于maximumPoolSize,新提交任务会创建新线程执行任务。
4.如果当前线程池中的数量大于corePoolSize,缓冲队列workQueue已满,并且线程池中的数量等于maximumPoolSize,新提交任务由Handler处理。
5.当线程池中的线程大于corePoolSize时,多余线程空闲时间超过keepAliveTime时,会关闭这部分线程。
没什么惊艳(经验??)的写法DEMO我就不写了,直接写发布后的:
Spring初始化传入参数,并调用init方法
<bean id="ThreadPoolBean" class="com.sinotrans.bms.webServlet.bean.ThreadPoolBean" init-method="initThreadPool">
<property name="COREPOOLSIZE">
<value type="java.lang.Integer">8value>
property>
<property name="MAXIMUMPOOLSIZE">
<value type="java.lang.Integer">16value>
property>
<property name="KEEPALIVETIME">
<value type="java.lang.Long">30value>
property>
bean>
package com.sinotrans.bms.webServlet.bean;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* 初始化线程池
* @Date 2016年12月05日
*/
public class ThreadPoolBean {
private static ThreadPoolExecutor threadPool = null;
private static int COREPOOLSIZE;
private static int MAXIMUMPOOLSIZE;
private static Long KEEPALIVETIME;
private final static ArrayBlockingQueue
private ThreadPoolBean(){}//单利不要被NEW 来NEW去的 一会服务器炸了
/*
* 构造线程池
*/
public void initThreadPool(){
if(threadPool == null){//防止被多次初始化
//DiscardOldestPolicy设置策略为挤掉最旧的 CallerRunsPolicy谁调用的任务谁执行,主线程调用主线程执行
ThreadPoolExecutor thread = new ThreadPoolExecutor(COREPOOLSIZE,
MAXIMUMPOOLSIZE, KEEPALIVETIME, TimeUnit.SECONDS,
BLOCKINGQUEUE, new ThreadPoolExecutor.CallerRunsPolicy());
threadPool = thread;
}
System.out.println("Thread pool init success.");
}
/*
* 或得线程池对象
*/
public static ThreadPoolExecutor getThreadPool(){
return threadPool;
}
public void setCOREPOOLSIZE(int corepoolsize) {
COREPOOLSIZE = corepoolsize;
}
public void setMAXIMUMPOOLSIZE(int maximumpoolsize) {
MAXIMUMPOOLSIZE = maximumpoolsize;
}
public void setKEEPALIVETIME(Long keepalivetime) {
KEEPALIVETIME = keepalivetime;
}
}
初始化方式很多,也可以使用Spring调用构造方法来初始化。
Servlet加入WEB.xml调用servlet的时候初始化。
或者使用拦截器,都可以。比较偏向于Spring。
ThreadPoolExecutor threadPool = ThreadPoolBean.getThreadPool();
InvokingOfNewThreadManager iontModel = (InvokingOfNewThreadManager)BaseServlet.getBean("invokingOfNewThreadManager");//我这是写在servlet里的一个服务所以需要从Spring拿注入的bean
iontModel.setBatchNumber(batchNumber);
iontModel.setFlag(false);//发票为true
iontModel.setIsAudit(isAudut);//补收补付审核
iontModel.setEscoCompanyNo(escoCompanyNo);
iontModel.setProName("CG_LOAD_REAL_TIME_EFEP_A");
threadPool.execute(iontModel);
System.out.println(threadPool.getPoolSize()+"/"+threadPool.getQueue().size());//打印线程数与队列大小
我的写法就是mananger继承runnable使之有事物:
@Service
public class InvokingOfNewThreadManagerImpl extends BaseManagerImpl implements
Runnable, InvokingOfNewThreadManager {
@Autowired
private EoInformationMainManager eoInformationMainManager;
//属性都是业务需要的
private Long batchNumber;
private boolean flag;
private String proName;
private String isAudit;
private String escoCompanyNo;
public void run() {
//业务逻辑
try {//模拟业务逻辑的处理
Thread.sleep(30000);
System.out.println("test");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
IgnoreLoginController.virtualLogout();
}
public void setBatchNumber(Long batchNumber) {
this.batchNumber = batchNumber;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
public void setProName(String proName) {
this.proName = proName;
}
public void setIsAudit(String isAudit) {
this.isAudit = isAudit;
}
public void setEscoCompanyNo(String escoCompanyNo) {
this.escoCompanyNo = escoCompanyNo;
}
当然了,我们这个服务不会和抢票一样的并发量,到并发高的时候也就几百并发每个调用几万数据量。目前可以抗住6000并发多测无益(业务就没那么多并发)就没再测试,后期会提升数据量进行测试。
毕设,或者demo可以直接用,并不用修改什么。
还是会继续把笔记写进来。争取做自己的百度。