线程池学习使用

基于项目的学习使用

ThreadPoolTaskExecutor

项目中使用这个线程池是Spring提供的,支持异步和定时.当前内部持有一个Java的线程池对象ThreadPoolExecutor

//这个线程池继承了AsyncListenableTaskExecutor,SchedulingTaskExecutor 所以支持异步和定时
public class ThreadPoolTaskExecutor extends ExecutorConfigurationSupport
		implements AsyncListenableTaskExecutor, SchedulingTaskExecutor {
	//加锁对象
	private final Object poolSizeMonitor = new Object();
	//核心线程数,默认值为1
	private int corePoolSize = 1;
	//最大线程数
	private int maxPoolSize = Integer.MAX_VALUE;
	//非核心线程存活时间,默认60s
	private int keepAliveSeconds = 60;
	//阻塞队列容量
	private int queueCapacity = Integer.MAX_VALUE;
	//是否允许线程超时
	private boolean allowCoreThreadTimeOut = false;
	
	//TaskDecorator是一个执行回调方法的装饰器,主要应用于传递上下文,或者提供任务的监控/统计信息。
	@Nullable
	private TaskDecorator taskDecorator;
	
	//真正的线程池
	@Nullable
	private ThreadPoolExecutor threadPoolExecutor;

	//存储运行任务集合
	private final Map<Runnable, Object> decoratedTaskMap =
			new ConcurrentReferenceHashMap<>(16, ConcurrentReferenceHashMap.ReferenceType.WEAK);
}

配置线程池

/**
 * 这里配置线程池,主要用来执行异步/定时任务
 */
@Configuration
public class AsyncThreadPoolTaskConfig {
    private static int cpuNums = Runtime.getRuntime().availableProcessors();
    private static final int COREPOOLSIZE = cpuNums;             // 核心线程数(默认线程数)
    private static final int MAXPOOLSIZE = cpuNums * 5;             // 最大线程数
    private static final int KEEPALIVETIME = 5;            // 允许线程空闲时间(单位:默认为秒)
    private static final int QUEUECAPACITY = cpuNums * 100;           // 缓冲队列数
    private static final String THREADNAMEPREFIX = "AsyncTask-"; // 线程池名前缀

    @Bean("taskExecutor") // bean的名称,默认为首字母小写的方法名
    public ThreadPoolTaskExecutor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(COREPOOLSIZE);
        executor.setMaxPoolSize(MAXPOOLSIZE);
        executor.setQueueCapacity(QUEUECAPACITY);
        executor.setKeepAliveSeconds(KEEPALIVETIME);
        executor.setThreadNamePrefix(THREADNAMEPREFIX);

        // 线程池对拒绝任务的处理策略
        //这里使用的拒绝策略是CallerRunsPolicy,使用调用者的线程执行异步任务
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        // 初始化
        executor.initialize();
        return executor;
    }
}

使用

普通的定时任务

//定时清理90天之前的历史 热点关键词数据
@Component
@Transactional
public class HotKeywordScheduler extends JpaHibernateRepository {

    @Autowired
    private HotSearchKeyWordRepository repository;
    
    @Async("taskExecutor")//使用线程池 启动
    @Scheduled(cron="0 0 0 * * ?")
    public void deletePastHotKeyword(){
        repository.deletePastHotKeyword();
    }
}

 public void deletePastHotKeyword() {
        Map<String,Object> params=new HashMap<String,Object>();
        Date date=CalendarUtils.patchDateToDayStart(CalendarUtils.offsetMonths(new Date(), -3));
        String sql = "delete from t_hot_search_keyword where create_time<= :dateTime";
        params.put("dateTime", date);
        this.createSQLQueryByMapParams(sql, params).executeUpdate();
}

加锁执行定时任务

private String lockType_clearSysMsg="clearSysMsg";
    @Async("taskExecutor")//使用线程池 启动
    @Scheduled(cron = "0 30 0,12 * * ?")
    public void clearSysMsg() {
        logger.info("---------定时清理系统消息begin------------");
        boolean added=lockService.addLock(lockType_clearSysMsg,60);
        if(!added) return;
        try {
            jpaBaseQueryService.createSqlQuery(
                "delete from t_sys_msg where create_time<:comDate",
                Utils.buildMap("comDate",CalendarUtils.offsetMinutes(new Date(),-30))
            )
            .executeUpdate()
            ;
        }catch (Exception e) {
            logger.error("---------定时清理系统消息出错------------",e);
        }finally {
            lockService.deleteLock(lockType_clearSysMsg);
        }
        logger.info("---------定时清理系统消息end------------");
    }

锁实体模型

@Entity
@Table(name = "t_locker")
public class ExcludeLock {
    @Id
    @Column(length = 200)
    private String lockType;

    private Date createTime = new Date();

    private Date lastUpdate = new Date();

    public ExcludeLock(String lockType, Date createTime) {
        super();
        this.lockType = lockType;
        this.createTime = createTime;
    }

    public ExcludeLock() {
        super();
    }

    public String getLockType() {
        return lockType;
    }

    public Date getCreateTime() {
        return createTime;
    }

    public Date getLastUpdate() {
        return lastUpdate;
    }

    public void updateLastUpdate() {
        this.lastUpdate = new Date();
    }

}

锁操作服务

@Repository
@SuppressWarnings("unchecked")
public class ExcludeLockJpaHibernateRepository extends JpaHibernateRepository {

    public ExcludeLock excludeLock(String lockType) {
        // 先以数据库行锁排除并发问题
        ExcludeLock lock = null;
        List<ExcludeLock> locks = this.createHQLQueryByParams(
                "select o from " + ExcludeLock.class.getName()
                        + " o where o.lockType=?0", lockType).list();
        if (locks.isEmpty()) {
            lock = new ExcludeLock(lockType, new Date());

            this.getSession().save(lock);
            this.getSession().flush();
        } else {
            lock = locks.get(0);
            lock.updateLastUpdate(this);
            //BUG:只有在数据中已存在的情况下才需要refresh,新增不允许
            // 获得行级排他锁
            this.getSession().refresh(lock, LockOptions.UPGRADE);
        }


        return lock;
    }

    public void update(ExcludeLock excludeLock) {
        this.getSession().update(excludeLock);
        this.getSession().flush();

    }

    private boolean isExistLock(String lockType) {
        List<Object> rows = this.createSQLQueryByParams("select lock_type from t_locker where lock_type=?0 ",
                lockType).list();
        return rows.size() > 0 && rows.get(0) != null;
    }

    @Transactional
    public boolean createLock(String lockType, Integer minutes) {
        //删除  超时  为删除的锁  因为数据库锁  当时使用完就会删除  如果未删除 肯定有bug  此处强制清理超时1小时的锁
        this.cleanExpiredLock(lockType, minutes);
        if (this.isExistLock(lockType)) {
            return false;
        } else {
            try {
                ExcludeLock lock = new ExcludeLock(lockType, new Date());
                this.getSession().save(lock);
                this.getSession().flush();
                return true;
            } catch (Exception e) {
                logger.error("占用锁[" + lockType + "]时发生错误:" + e.getMessage());
                return false;
            }
        }

    }

    public void deleteLock(String lockType) {
        List<ExcludeLock> locks = this.createHQLQueryByParams(
                "select o from " + ExcludeLock.class.getName() + " o where o.lockType=?0", lockType
        ).list();
        if (CollectionUtils.isNotEmpty(locks)) {
            for (ExcludeLock lock : locks) {
                this.getSession().delete(lock);
            }
        } else {//做兼容 删除
            this.createSQLQueryByParams("delete from t_locker where lock_type=?0", lockType).executeUpdate();
        }
        this.getSession().flush();
    }

    public void cleanExpiredLock(String lockType, Integer minutes) {
        //删除 创建时间 超过hours小时的锁 默认24小时
        Date date = null;
        if (minutes == null) minutes = -24 * 60;
        if (minutes > 0) minutes = -minutes;
        date = CalendarUtils.offsetMinutes(new Date(), minutes);
        if (StringUtils.isBlank(lockType)) {
            List<ExcludeLock> locks = this.createHQLQueryByParams(
                    "select o from " + ExcludeLock.class.getName() +
                            " o where o.createTime,
                    date
            ).list();
            for (ExcludeLock lock : locks) {
                this.getSession().delete(lock);
            }
            //BUG:如果通过sql删除,再调用excludeLock,则会造成重复创建同一个lockType的锁对象
            //this.createSQLQueryByParams("delete from t_locker where create_time
        } else {
            List<ExcludeLock> locks = this.createHQLQueryByParams(
                    "select o from " + ExcludeLock.class.getName() +
                            " o where o.createTime,
                    date, lockType
            ).list();
            for (ExcludeLock lock : locks) {
                this.getSession().delete(lock);
            }
            //BUG:如果通过sql删除,再调用excludeLock,则会造成重复创建同一个lockType的锁对象
            //this.createSQLQueryByParams("delete from t_locker where create_time
        }
        this.getSession().flush();
    }
}

简介

线程池其实是一种池化技术的实现,池化技术的核心思想其实就是实现资源的一个复用,避免资源的重复创建和销毁带来的性能开销。在线程池中,线程池可以管理一堆线程,让线程执行完任务之后不会进行销毁,而是继续去处理其它线程已经提交的任务。

线程池带来的好处

降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统 的稳定性,使用线程池可以进行统一的分配,调优和监控。

原理

  1. 刚创建出来的线程池是没有线程,只有一个阻塞队列而已,如果想在执行任务之前就存在已创建好的线程,可以调用prestartAllCoreThreads()方法。
  2. 提交任务的时候,首先会去判断当前线程池的线程数是否小于核心线程数,也就是线程池构造时传入的参数corePoolSize,如果小于,那么就直接通过ThreadFactory创建一个线程来执行这个任务,当任务执行完之后,线程不会退出,而是会去从阻塞队列中获取任务,如果有任务则重复上面的步骤。
  3. 这里有个细节,就是提交任务的时候,就算有线程池里的线程从阻塞队列中获取不到任务,如果线程池里的线程数还是小于核心线程数,那么依然会继续创建线程,而不是复用已有的线程
  4. 如果线程池里的线程数不再小于核心线程数,那么此时就会尝试将任务放入阻塞队列中,这样在阻塞的线程就可以获取到任务了,但是,随着任务越来越多,此时就会判断当前线程池里的线程数是否小于最大线程数,也就是入参时的maximumPoolSize参数。如果小于最大线程数,那么也会创建非核心线程来执行提交的任务,所以,从这里可以发现,就算队列中有任务,新创建的线程还是优先处理这个提交的任务,而不是从队列中获取已有的任务执行,从这可以看出,先提交的任务不一定先执行。
  5. 如果线程数已经达到了最大线程数量。此时就会执行拒绝策略,也就是构造线程池的时候,传入的RejectedExecutionHandler对象,来处理这个任务。
  6. 线程池创建的时候,如果不指定拒绝策略就默认是AbortPolicy策略。当然,你也可以自己实现RejectedExecutionHandler接口,比如将任务存在数据库或者缓存中,这样就数据库或者缓存中获取到被拒绝掉的任务了。

线程池中的线程重复利用原理

线程在线程池内部被封装成了Worker对象

private final class Worker
        extends AbstractQueuedSynchronizer
        implements Runnable{
        public void run() {
            runWorker(this);
        }
}
final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;
        try {
        	//重复利用的原因是因为这里使用while循环,只要有任务就会一直执行下去
            while (task != null || (task = getTask()) != null) {
                w.lock();
                // If pool is stopping, ensure thread is interrupted;
                // if not, ensure thread is not interrupted.  This
                // requires a recheck in second case to deal with
                // shutdownNow race while clearing interrupt
                if ((runStateAtLeast(ctl.get(), STOP) ||
                     (Thread.interrupted() &&
                      runStateAtLeast(ctl.get(), STOP))) &&
                    !wt.isInterrupted())
                    wt.interrupt();
                try {
                    beforeExecute(wt, task);
                    Throwable thrown = null;
                    try {
                        task.run();
                    } catch (RuntimeException x) {
                        thrown = x; throw x;
                    } catch (Error x) {
                        thrown = x; throw x;
                    } catch (Throwable x) {
                        thrown = x; throw new Error(x);
                    } finally {
                        afterExecute(task, thrown);
                    }
                } finally {
                    task = null;
                    w.completedTasks++;
                    w.unlock();
                }
            }
            completedAbruptly = false;
        } finally {
        	//如果获取不到任务则退回
            processWorkerExit(w, completedAbruptly);
        }
    }

线程是如何获取任务的以及如何实现超时的

private Runnable getTask() {
        boolean timedOut = false; // Did the last poll() time out?

        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);

            // Check if queue empty only if necessary.
            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
                decrementWorkerCount();
                return null;
            }

            int wc = workerCountOf(c);

            // 是否超时判断
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

            if ((wc > maximumPoolSize || (timed && timedOut))
                && (wc > 1 || workQueue.isEmpty())) {
                if (compareAndDecrementWorkerCount(c))
                    return null;
                continue;
            }

            try {
            	//如果超时则调用poll方法,否则调用take方法
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
                if (r != null)
                    return r;
                timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }

细节

就是判断当前获取任务的线程是否可以超时退出的时候,如果将allowCoreThreadTimeOut设置为true,那么所有线程走到这个timed都是true,那么所有的线程,包括核心线程都可以做到超时退出。如果你的线程池需要将核心线程超时退出,那么可以通过allowCoreThreadTimeOut方法将allowCoreThreadTimeOut变量设置为true。

线程池的状态

 	private static final int RUNNING    = -1 << COUNT_BITS;
	private static final int SHUTDOWN   =  0 << COUNT_BITS;
    private static final int STOP       =  1 << COUNT_BITS;
    private static final int TIDYING    =  2 << COUNT_BITS;
    private static final int TERMINATED =  3 << COUNT_BITS;
    //存储状态的变量
    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

你可能感兴趣的:(项目,java)