java concurrent 包之 ScheduledThreadPoolExecutor

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) {
//    處理消息   
    }

}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章