文章目录
一,TimeUnit的使用
主要作用:1. 时间颗粒度转换 2. 延时
1.时间颗粒度转换
TimeUnit是枚举实例,指定颗粒度单位
常用的颗粒度
TimeUnit.DAYS; //天
Calendar.HOUR_OF_DAY //小时 是24小时制
Calendar.HOU //小时 是12小时制
TimeUnit.MINUTES //分钟
TimeUnit.SECONDS //秒
TimeUnit.MILLISECONDS //毫秒
转换:
System.out.println( TimeUnit.DAYS.toHours( 1 ) ); //将1天转化为小时
System.out.println( TimeUnit.HOURS.toSeconds( 1 ));//将1小时转化为秒,转换其他颗粒方法类似
2.延时
TimeUnit提供了可读性更好的线程暂停操作,通常用来替换Thread.sleep()。
记住TimeUnit.sleep()内部调用的Thread.sleep()也会抛出InterruptException。
TimeUnit写法:线程休眠4分钟
public static void main(String[] args) throws InterruptedException {
TimeUnit.MINUTES.sleep(4);
}
二,定时任务创建的几种方式
参考博客:Java并发学习之定时任务的几种玩法
不推荐使用 Thread.sleep的方式做定时任务
利用jdk实现定时任务,考虑Timer和ScheduledExecutorService
利用到了Spring,框架自带的定时任务
1.定时任务Timer和TimerTask方式
//TimerTask是一个抽象类,实现了Runnable接口,所以具备了多线程的能力。
TimerTask timerTask = new TimerTask() {
@Override
public void run() {
System.out.println("timerTask 定时执行!");
}
};
Timer timer = new Timer();
timer.schedule(timerTask, 5000);// 5秒后启动任务
timer.schedule(timerTask, 5000, 3000);// 5秒后启动任务,以后每隔3秒执行一次线程
Date date = new Date();
timer.schedule(timerTask, date, 3000);//以date为参数,指定某个时间点执行线程
2.线程池方式实现(推荐使用)
// 获取线程池
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
// 延迟3s后,执行定时任务
// scheduledExecutorService.schedule(new Runnable() {
// @Override
// public void run() {
// System.out.println("scheduledExecutorService 定时执行!");
// }
// }, 3, TimeUnit.SECONDS);
// 3s后,首次执行,然后每个3s执行一次
scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println("scheduledExecutorService 定时执行!");
}
}, 3, 3, TimeUnit.SECONDS);
3.线程池方式计算某个时间执行
public class Testb {
static ScheduledExecutorService executorService = Executors.newScheduledThreadPool(2);
static class Task extends TimerTask {
@Override
public void run() {
System.out.println("进行校验");
// 执行完毕,等待到6点发送报警
int min = Calendar.getInstance().get(Calendar.MINUTE);
int sec = Calendar.getInstance().get(Calendar.SECOND);
long delayTime = TimeUnit.MINUTES.toSeconds(59 - min) + (60 - sec);
executorService.schedule(new Runnable() {
@Override
public void run() {
System.out.println("报警!");
}
}, delayTime, TimeUnit.SECONDS);
}
}
public static void main(String[] args) throws InterruptedException {
//假设每天凌晨5点数据导入完毕
//在5:15分进行校验, 校验完成之后,45分钟后即六点将校验结果通知给owne
Calendar calendar = Calendar.getInstance();// 15:20 40s
// 计算sleep的小时数 先计算到5点整
int sleepHour = 4 - calendar.get(Calendar.HOUR_OF_DAY);//4 - 15 = -11
if (sleepHour < 0) {
sleepHour = 24 + sleepHour;
}
int minute = 59 - calendar.get(Calendar.MINUTE);//20
int second = 60 - calendar.get(Calendar.SECOND);//40s
long delay = TimeUnit.HOURS.toSeconds(sleepHour) + TimeUnit.MINUTES.toSeconds(minute) + second;
// 再加15分钟 延时到5:15
delay += TimeUnit.MINUTES.toSeconds(15);
executorService.scheduleAtFixedRate(new Task(), delay, TimeUnit.DAYS.toSeconds(1), TimeUnit.SECONDS);
}
}
三,线程池的创建和自定义线程池
1.线程池的创建
//newFixedThreadPool()固定大小的线程池,阻塞队列使用LinkedBlockingQueue
//newSingleThreadExecutor()单个线程线程池,阻塞队列使用LinkedBlockingQueue
//newCachedThreadPool()缓存线程池,阻塞队列使用SynchronousQueue
//newScheduledThreadPool定时线程池,该线程池可用于周期性地去执行任务,通常用于周期性的同步数据。
//采用Executors创建定时线程池
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
2.自定义线程池
参考博客:JAVA进阶----ThreadPoolExecutor机制
线程池的正确使用:
阿里编码规范:
线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。 说明:Executors各个方法的弊端:
1)newFixedThreadPool和newSingleThreadExecutor:
主要问题是堆积的请求处理队列可能会耗费非常大的内存,甚至OOM。
2)newCachedThreadPool和newScheduledThreadPool:
主要问题是线程数最大数是Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至OOM。
//阻塞队列数
//ArrayBlockingQueue 数组结构组成的有界阻塞队列,此队列按照先进先出(FIFO)的原则
//LinkedBlockingQueue 一个由链表结构组成的无界阻塞队列,此队列按照先出先进的原则进行排序
//SynchronousQueue 不存储元素的阻塞队列
定制属于自己的阻塞线程池:
public class CustomThreadPoolExecutor {
/**
* 自定义线程池
*/
private ThreadPoolExecutor pool = null;
/**
*
* 线程池初始化方法
* corePoolSize 核心线程池大小----1
* maximumPoolSize 最大线程池大小----3
* keepAliveTime 线程池中超过corePoolSize数目的空闲线程最大存活时间----30+单位TimeUnit
* TimeUnit keepAliveTime时间单位----TimeUnit.MINUTES
* workQueue 阻塞队列----new ArrayBlockingQueue<Runnable>(5)====5容量的阻塞队列
* threadFactory 新建线程工厂----new CustomThreadFactory()====定制的线程工厂
* rejectedExecutionHandler 当提交任务数超过maxmumPoolSize+workQueue之和时,
* 即当提交第41个任务时(前面线程都没有执行完,此测试方法中用sleep(100)),
* 任务会交给RejectedExecutionHandler来处理
*/
public void init() {
pool = new ThreadPoolExecutor(
1,
3,
30,
TimeUnit.MINUTES,
new ArrayBlockingQueue<Runnable>(5),
new CustomThreadFactory(),
new CustomRejectedExecutionHandler());
}
/**
* 线程池销毁方法
*/
public void destory() {
if(pool != null) {
pool.shutdownNow();
}
}
/**
* 获取自定义线程池
* @return
*/
public ExecutorService getCustomThreadPoolExecutor() {
return this.pool;
}
/**
*
* @Description: 定制的线程工厂
* @author: lys
*/
private class CustomThreadFactory implements ThreadFactory {
//Atomic包下提供具有原子性类型:如
//AtomicBoolean :原子更新布尔类型。
//AtomicLong :原子更新长整型。
//AtomicInteger :原子更新整型。
private AtomicInteger count = new AtomicInteger(0);
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
//addandget为先添加后获取,类似于++i
String threadName = CustomThreadPoolExecutor.class.getSimpleName() + count.addAndGet(1);
System.out.println(threadName);
t.setName(threadName);
return t;
}
}
/**
*
* @Description: 拒绝策略
* @author: lys
*/
private class CustomRejectedExecutionHandler implements RejectedExecutionHandler {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
try {
// 核心改造点,由blockingqueue的offer改成put阻塞方法
// offer方法当队列满,而且放入时间超过设定时间时,返回false;
// put方法当队列满时,会调用wait方法,put方法会等待一个空的位置出来,然后再执行insert
executor.getQueue().put(r);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
// 测试构造的线程池
public static void main(String[] args) {
CustomThreadPoolExecutor exec = new CustomThreadPoolExecutor();
// 1.初始化
exec.init();
// 获取自定义线程池
ExecutorService pool = exec.getCustomThreadPoolExecutor();
for(int i=1; i<100; i++) {
System.out.println("提交第" + i + "个任务!");
// 在线程池里创建线程执行任务
pool.execute(new Runnable() {
@Override
public void run() {
try {
System.out.println(">>>task is running=====");
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
// 2.销毁----此处不能销毁,因为任务没有提交执行完,如果销毁线程池,任务也就无法执行了
// exec.destory();
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}