2018-06-14: Java 定時任務執行緒池 ExecutorService 使用總結

lynn_發表於2018-06-14

1、背景:
重構了一個專案,將以前散亂的多執行緒和定時任務執行緒做統一管理,減少程式碼量並提升程式碼可讀性。

2、直接上程式碼 - 可直接COPY使用 ----------------------------

-、新建一個執行緒池構造工廠類 ExecutorServiceFactory:

package com.aaa.bbb.ccc.ddd;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;

/**
* @Function : 執行緒池構造工廠
* @Author & @Date : lynn_ - 2018年6月14日
*/
public class ExecutorServiceFactory {

// 例項化ExecutorServiceFactory
private static ExecutorServiceFactory executorServiceFactory = new ExecutorServiceFactory();

// 定時任務執行緒池
private ExecutorService executorService;

/**
 * 預設無參構造
 */
private ExecutorServiceFactory() { }

/**
 * @Function: 獲取ExecutorServiceFactory
 */
public static ExecutorServiceFactory getInstance() {
	return executorServiceFactory;
}

/**
 * @Function: 建立一個定長的執行緒池 - 它可安排在給定延遲後執行命令或者定期地執行
 */
public ExecutorService createScheduledThreadPool() {
	// CPU個數
	int availableProcessors = Runtime.getRuntime().availableProcessors();
	// 建立
	executorService = Executors.newScheduledThreadPool(availableProcessors * 10, getThreadFactory());
	return executorService;
}

/**
 * @Function: 建立一個單執行緒化的Executor,即只建立唯一的工作者執行緒來執行任務,它只會用唯一的工作執行緒來執行任務,保證所有任務按照指定順序(FIFO, LIFO, 優先順序)執行
 *            Executor,以無界佇列方式來執行該執行緒。(注意,如果因為在關閉前的執行期間出現失敗而終止了此單個執行緒,
 *            那麼如果需要,一個新執行緒將代替它執行後續的任務 )。可保證順序地執行各個任務,
 *            並且在任意給定的時間不會有多個執行緒是活動的。與其他等效的 newFixedThreadPool(1)
 *            不同,可保證無需重新配置此方法所返回的執行程式即可使用其他的執行緒。
 */
public ExecutorService createSingleThreadExecutor() {
	// 建立
	executorService = Executors.newSingleThreadExecutor(getThreadFactory());
	return executorService;
}

/**
 * @Function: 建立一個可快取執行緒池,如果執行緒池長度超過處理需要,可靈活回收空閒執行緒,若無可回收,則新建執行緒
 *            對於執行很多短期非同步任務的程式而言,這些執行緒池通常可提高程式效能。呼叫 execute將重用以前構造的執行緒(如果執行緒可用)。
 *            如果現有執行緒沒有可用的,則建立一個新執行緒並新增到池中。 終止並從快取中移除那些已有 60 秒鐘未被使用的執行緒。
 *            因此,長時間保持空閒的執行緒池不會使用任何資源。注意,可以使用 ThreadPoolExecutor
 *            構造方法建立具有類似屬性但細節不同(例如超時引數)的執行緒池。
 */
public ExecutorService createCachedThreadPool() {
	// 建立
	executorService = Executors.newCachedThreadPool(getThreadFactory());
	return executorService;
}

/**
 * @Function: 建立一個指定工作執行緒數量的執行緒池。每當提交一個任務就建立一個工作執行緒,
 *            如果工作執行緒數量達到執行緒池初始的最大數,則將提交的任務存入到池佇列中。
 *            可重用固定執行緒數的執行緒池,以共享的無界佇列方式來執行這些執行緒。在任意點,在大多數 nThreads
 *            執行緒會處於處理任務的活動狀態。如果在所有執行緒處於活動狀態時提交附加任務,則在有可用執行緒之前,附加任務將在佇列中等待。
 *            如果在關閉前的執行期間由於失敗而導致任何執行緒終止,那麼一個新執行緒將代替它執行後續的任務(如果需要)。
 *            在某個執行緒被顯式地關閉之前,池中的執行緒將一直存在。
 */
public ExecutorService createFixedThreadPool(int count) {
	// 建立
	executorService = Executors.newFixedThreadPool(count, getThreadFactory());
	return executorService;
}

/**
 * @Function: 獲取執行緒池工廠
 */
private ThreadFactory getThreadFactory() {
    return new ThreadFactory() {
        // AtomicInteger是一個提供原子操作的Integer類,通過執行緒安全的方式操作加減
        AtomicInteger sn = new AtomicInteger();

    	@Override
    	public Thread newThread(Runnable r) {
    		// 安全管理器
    		SecurityManager s = System.getSecurityManager();
    		// 所有執行緒都隸屬於一個執行緒組
    		ThreadGroup group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
    		Thread t = new Thread(group, r);
    		t.setName("任務執行緒 - " + sn.incrementAndGet());
    		return t;
    	}
	};
}
}
複製程式碼

--、新建一個執行緒處理類 ExecutorProcessPool:

package com.aaa.bbb.ccc.ddd;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;

/** 
* @Function :  執行緒處理類
* @Author & @Date : lin.li  - 2018年6月14日 
*/ 
public class ExecutorProcessPool {

//執行緒池介面
private ExecutorService executor;
//例項化當前類
private static ExecutorProcessPool pool = new ExecutorProcessPool();

/**
 * Creates a new instance of ExecutorProcessPool
 */
private ExecutorProcessPool() {
    executor = ExecutorServiceFactory.getInstance().createCachedThreadPool();
}

/**
 * @Function: 獲取 ExecutorProcessPool
 */
public static ExecutorProcessPool getInstance() {
    return pool;
}

/**
 * @Function:  關閉執行緒池,這裡要說明的是:呼叫關閉執行緒池方法後,執行緒池會執行完佇列中的所有任務才退出
 */
public void shutdown(){
    executor.shutdown();
}

/**
 * @Function: 提交任務到執行緒池,可以接收執行緒返回值 - Future模式
 */
public Future<?> submit(Runnable task) {
    return executor.submit(task);
}
public Future<?> submit(Callable<?> task) {
    return executor.submit(task);
}

/**
 * @Function: 直接提交任務到執行緒池,無返回值
 */
public void execute(Runnable task){
    executor.execute(task);
}
}
複製程式碼

---、核心程式碼就這點了,下面是測試類 ExecutorTest:

package com.goldpac.ito.system.interceptor;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

/**
* @Function : 執行緒池測試類
* @Author & @Date : lin.li - 2018年6月11日
*/
public class ExecutorTest {

public static void main(String[] args) {
    //獲取例項
    ExecutorProcessPool pool = ExecutorProcessPool.getInstance();

    //for迴圈新增多個執行緒 - 
    for (int i = 0; i < 200; i++) {
        Future<?> future = pool.submit(new ExcuteTask1(i + ""));
        try {
            //如果接收執行緒返回值,future.get() 會阻塞,如果這樣寫就是一個執行緒一個執行緒執行。所以非特殊情況不建議使用接收返回值的。
            System.out.println(future.get());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    //沒有返回值。可以執行任務,但無法判斷任務是否成功完成。
    //execute(Runnable x);
	
    //for (int i = 0; i < 200; i++) {
    //  pool.execute(new ExcuteTask2(i + ""));
    //}

    // 關閉執行緒池,如果是需要長期執行的執行緒池,不用呼叫該方法。
    // 監聽程式退出的時候最好執行一下。
    pool.shutdown();
}

/**
 * 執行任務1,實現Callable方式
 */
static class ExcuteTask1 implements Callable<String> {
	private String taskName;

	public ExcuteTask1(String taskName) {
		this.taskName = taskName;
	}

	@Override
	public String call() throws Exception {
		try {
			// Java 6/7最佳的休眠方法為TimeUnit.MILLISECONDS.sleep(100);
			// 最好不要用 Thread.sleep(100);
			// 1000毫秒以內的隨機數,模擬業務邏輯處理
			TimeUnit.MILLISECONDS.sleep((int) (Math.random() * 1000));
		} catch (Exception e) {
			e.printStackTrace();
		}
		System.out.println("-------------這裡執行業務邏輯,Callable TaskName = " + taskName + "-------------");
		return ">>>>>>>>>>>>>執行緒返回值,Callable TaskName = " + taskName + "<<<<<<<<<<<<<<";
	}
}

/**
 * 執行任務2,實現Runable方式
 */
static class ExcuteTask2 implements Runnable {
	private String taskName;
	public ExcuteTask2(String taskName) {
		this.taskName = taskName;
	}
	@Override
	public void run() {
		try {
			// 1000毫秒以內的隨機數,模擬業務邏輯處理
			TimeUnit.MILLISECONDS.sleep((int) (Math.random() * 1000));
		} catch (Exception e) {
			e.printStackTrace();
		}
		System.out.println("-------------這裡執行業務邏輯,Runnable TaskName = " + taskName + "-------------");
	}
}
}複製程式碼

相關文章