java 自帶定時任務的另一種實現
使用示例:
//package com.ws.com.util;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* 定時執行 util
*/
public class TimerUtil {
/**
* 可以看作強化後的Timer ,且不用繼承Timer類
*/
// 定義的線程池容量
private static ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(1000);
/**
* 設置定時執行
* @param period 執行間隔
* @param unit 時間單位
* SECONDS
*/
public static void setTimer(Runnable command,long period, TimeUnit unit){
// scheduleWithFixedDelay 在...之後執行
// scheduleAtFixedRate 再...之前執行
// command 線程實現類
// initialDelay 初始延遲,第一次執行在程序啓動多少秒後執行
// period 執行的時間間隔,主要設置參數
// unit 時間的單位(初始延遲和時間間隔的時間單位,TimeUnit 的枚舉值
executor.scheduleAtFixedRate(command,6,period, unit);
}
}
簡單說明:
可以看作示Java 對Timer 類的複雜封裝,引入了線程池的實現,
主要通過
scheduleAtFixedRate
scheduleWithFixedDelay
這兩個方法來定義定時任務,需要傳入一個實現 Runnable 接口的線程實現類,每個線程在定義的時間到了,就會去執行實現類的事務,但是有報錯就停止執行,且沒有報錯打印,後來測試了一下發現再線程實現類,run 方法裏面將需要處理的代碼用try{}catch(){} 處理就可以,這個地方因爲run 方法是實現Runnable 接口的,所以不能拋出,這裏就算報錯也會繼續執行下去
問題:
如果執行任務的過程中出現異常或者其他問題,定時任務會停止執行,但是大部分時候都不會拋出異常,也不知道原因,這個待後面有空驗證和查找解決辦法
使用場景:
我們知道現在在大量的定時任務的時候,
一般項目首選的就是quartz ,引入和設置cron 表達式的方式,這個是一個重實現, 重實現設置導入複雜,有時候項目中沒有配置定時任務,啓動還會莫名報錯,但是設置方便,操控性強,可配置在頁面,實現啓動,實現關門,實現修改定時任務的時間間隔,
或者通過spring 自帶的定時任務處理方式,輕實現, 但是輕實現雖然是spring 自帶的,也可以通過cron 表達式配置,配置簡單,但是操控性差, 出了問題不好監控和調整,一旦配置好了,除非修改項目重啓,不然不能調整定時任務的週期,啓動,關閉
但是Java 自帶的這種實現方式,方便我們在測試的時候,需要定時任務,但是沒必要去配置quartz 和spring 環境,方便測試的時候簡單實現測試需求,或者 處理一些高併發的需求的時候,可以設置大容量的線程和短的時間間隔,去調用,也可以簡單的實現需求,比如消息隊列的消息處理,當消息比較多的時候,單個線程處理,比如獲取消息並處理消息保存在數據庫或其他操作,可以通過先將消息放入 異步隊列中,然後可以設置 1000個線程, 每隔 50 微秒 ,就去隊列中取待獲取的消息,並處理
例子:
說明一個rabbitmq 的例子,由於涉及隱私,某些數據已經脫敏,僅供展示
連接工廠,創建連接
package mq.util;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* rabbitmq 連接工廠
*/
public class AmqpConnectFactory {
public static String username ="";
public static String password ="";
public static String inPlayPackageId = "";
public static String inPlayHost = "";
public static void main(String[] args) throws IOException, TimeoutException {
ClientUitl.startInPlayMq(); // 啓動mq
inPlay();//連接mq
}
/**
*
* @throws IOException
* @throws TimeoutException
*/
public static void inPlay() throws IOException, TimeoutException {
// createConnection(inPlayHost,inPlayPackageId,"C:\\Users\\admin\\Desktop\\newRecord\\");
ConnectionFactory connectioinFactory = getConnectioinFactory(inPlayHost);
Connection connection = getConnection(connectioinFactory);
Channel channel = getChannel(connection,inPlayPackageId,"C:\\Users\\admin\\Desktop\\newRecord\\"); // 開啓一個通道並設置 相關的消費者等信息
}
/**
* 得到連接工廠
* @param host
* @return
* @throws IOException
* @throws TimeoutException
*/
private static ConnectionFactory getConnectioinFactory(String host) throws IOException, TimeoutException {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost(host);
connectionFactory.setPort(5672);
connectionFactory.setUsername(username);
connectionFactory.setPassword(password);
connectionFactory.setAutomaticRecoveryEnabled(true);
connectionFactory.setVirtualHost("Customers");
connectionFactory.setRequestedHeartbeat(580);
connectionFactory.setNetworkRecoveryInterval(1000);// 設置連接間隔
// 或者使用 url 進行連接
// factory.setUri(“ amqp:// userName:password @ hostName:portNumber / virtualHost”);
// connectionFactory.setUri("amqp://"+username+":"+password+"@"+ inPlayHost +":" + port +"/" + virtualHost);
// final Connection conn = connectionFactory.newConnection(); // 新建一個連接
return connectionFactory;
}
/**
* 得到一個新的連接
* @param factory
* @return
* @throws IOException
* @throws TimeoutException
*/
public static Connection getConnection(ConnectionFactory factory) throws IOException, TimeoutException {
Connection connection = factory.newConnection();
return connection;
}
/**
* 得到一個新的通道
* @param conn
* @param packageId
* @return
* @throws IOException
*/
public static Channel getChannel(Connection conn,String packageId,String path) throws IOException {
final Channel channel = conn.createChannel(); // 打開一個通道(同文檔中的模型)
// model.BasicQos(prefetchSize: 0, prefetchCount: 1000, global: false);
channel.basicQos(0, 1000, false);// 配置服務質量
// 訂閱主題, 設置消息自動確認,配置消費者
channel.basicConsume("_" + packageId +"_", true, new TestConsumer(channel,conn,packageId,path));
return channel;
}
@Override
protected void finalize() throws Throwable {
super.finalize();
}
}
消費者配置
說明: 該例實現了 Consumer 接口的handleConsumeOk() 方法,在消費者配置成功了,(調用定時任務)就調用上文代碼中的線程池去隊列中取消息, 具體消息處理方法handleDelivery () 方法不再處理具體事務,僅僅將消息放入異步隊列中
package mq.util;
import com.rabbitmq.client.*;
import com.ws.com.util.TimerUtil;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
/**
* mq 消費者
*
*/
public class TestConsumer implements Consumer { //extends DefaultConsumer {
private Channel channel;
private Connection connection;
private volatile String consumerTag;
private String packageId;
private String path;
/**
* Constructs a new instance and records its association to the passed-in channel.
*
* @param channel the channel to which this consumer is attached
*/
public TestConsumer(Channel channel,Connection connection,String packageId,String path) {
// super(channel);
this.channel = channel;
this.connection = connection;
this.packageId = packageId;
this.path = path;
}
/**
* 接收到此使用者的basic.deliver時調用
* @param consumerTag
* @param envelope
* @param properties
* @param body
* @throws IOException
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
// super.handleDelivery(consumerTag, envelope, properties, body);
String routingKey = envelope.getRoutingKey();
String contentType = properties.getContentType();
long deliveryTag = envelope.getDeliveryTag();
// 處理消息,將消息放入隊列中
CacheUtil.queue.offer(new String(body));
// handleMessage(body,path);
// 由於前面設置通道時已經設置了消息消費後自動確認,所以這裏不用再確認消息,否則 雙重確認會導致通道異常關閉
// channel.basicAck(deliveryTag, true);
//
}
//
/**
* :當通道或基礎連接被關閉時調用。
* 通道關閉的問題
* @param consumerTag
* @param sig
*/
@Override
public void handleShutdownSignal(String consumerTag, ShutdownSignalException sig) {
System.out.println("通道意外關閉,重新設置通道");
System.out.println("關閉原因"+sig.getMessage());
// this.chan
try {
Channel newChannel = connection.createChannel();
channel = newChannel;// 將綁定的通道設置爲新通道
channel.basicQos(0, 1000, false);// 配置服務質量
// 配置消費者
channel.basicConsume("_" + packageId +"_", true, this);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 當消費者由於除調用Channel.basicCancel之外的其他原因被取消時調用。
* @param consumerTag
* @throws IOException
*/
@Override
public void handleCancel(String consumerTag) throws IOException {
// super.handleCancel(consumerTag);
this.consumerTag = consumerTag;
}
/**
* 當消費者被Channel.basicCancel的調用取消時調用。
* @param consumerTag
*/
@Override
public void handleCancelOk(String consumerTag) {
// super.handleCancelOk(consumerTag);
}
/**
* 當使用者通過對任何通道的調用註冊時調用 Channel.basicConsume方法。
* @param consumerTag
*/
@Override
public void handleConsumeOk(String consumerTag) {
// super.handleConsumeOk(consumerTag);
System.out.println("註冊通過成功");
// 開啓線程池讀取消息,並保存, 每 500毫秒調用1次,拿取一個消息
TimerUtil.setTimer(new SaveRunner(path),50,TimeUnit.MICROSECONDS);
}
/**
* 當 basic.recover. 收到 回覆 basic.recover-ok 時調用
* @param consumerTag
*/
@Override
public void handleRecoverOk(String consumerTag) {
System.out.println("接收消息成功");
// super.handleRecoverOk(consumerTag);
}
// ==============================================================
public Channel getChannel() {
return channel;
}
public void setChannel(Channel channel) {
this.channel = channel;
}
public Connection getConnection() {
return connection;
}
public void setConnection(Connection connection) {
this.connection = connection;
}
public String getConsumerTag() {
return consumerTag;
}
// ================================================= 具體處理消息的邏輯 ==================================================================================================================
}
異步隊列
package mq.util;
import com.alibaba.dubbo.common.utils.ConcurrentHashSet;
import mq.event.MatchInfo;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.LinkedBlockingQueue;
/**
*
* 緩存信息map
*
*/
public class CacheUtil {
/**
* 這裏 存消息 offer,取消息 poll ,即使隊列中無值也不會報錯
*/
public static LinkedBlockingQueue<String> queue = new LinkedBlockingQueue<>();
}
具體處理消息的線程實現類
package mq.util;
import org.apache.commons.lang3.StringUtils;
/**
* 具體消息處理類
*/
public class SaveRunner implements Runnable {
// private volatile String message;
private String path;
public SaveRunner(String path) {
this.path = path;
}
@Override
public void run() {
// 從隊列取消息
try{
String message = CacheUtil.queue.poll();
if (StringUtils.isNotEmpty(message)) {
handleMessage(message, path);
}
}catch(Exception e) {
e.printStackTrace();
}
}
/**
* 保存到文件中,將消息放入隊列中
*
* @param s
* @param path
*/
private void handleMessage(String s, String path) {
// 處理消息
}
}