[TOC]
一、繼承Thread類
繼承Thread類,重寫run方法,調用Thread的start()方法啟動線程:
/**
* 實現線程方式:1、繼承Thread類
*/
@Slf4j
public static class ThreadTarget extends Thread {
@Override
public void run() {
while (true) {
log.info("Thread extentions: " + System.currentTimeMillis());
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
new一個Thread實例的時候,所有構造方法最終都是調用Thread的init方法, init方法中,設置線程屬性,校驗相關權限:
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc) {
/**設置線程名字,匿名線程,名字自動生成:Thread-nextThreadNum()**/
if (name == null) {
throw new NullPointerException("name cannot be null");
}
this.name = name;
/**設置線程組**/
Thread parent = currentThread();
SecurityManager security = System.getSecurityManager();
if (g == null) {
if (security != null) {
g = security.getThreadGroup();
}
if (g == null) {
g = parent.getThreadGroup();
}
}
g.checkAccess(); //檢查當前線程(parent)是否有權限修改線程組g
if (security != null) { //校驗是否thread子類的getContextClassLoader被覆蓋
if (isCCLOverridden(getClass())) { //并且擁有enableContextClassLoaderOverride權限
security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
}
}
g.addUnstarted(); //增加線程組的unstarted線程計數
this.group = g; //設置線程組
/**繼承當前線程(parant)的是否后臺線程、設置優先級的值**/
this.daemon = parent.isDaemon();
this.priority = parent.getPriority();
/**設置contextClassLoader和繼承accessControlContext**/
if (security == null || isCCLOverridden(parent.getClass()))
this.contextClassLoader = parent.getContextClassLoader();
else
this.contextClassLoader = parent.contextClassLoader;
this.inheritedAccessControlContext =
acc != null ? acc : AccessController.getContext();
/**設置Runnable接口的實例,作為target對象**/
this.target = target;
/**這里才是真正設置優先級的地方**/
setPriority(priority);
/**繼承當前線程(parent)的ThreadLocal.ThreadLocalMap**/
if (parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
this.stackSize = stackSize;
/**設置線程id, 這里設置的是tid,跟nextThreadNum()設置的threadInitNumber是兩個值**/
tid = nextThreadID();
}
啟動線程需要調用start方法,方法中調用native方法start0真正啟動線程,
虛擬機會在線程啟動過程中回調Thread的run方法:
public synchronized void start() {
/**threadStatus不等于0, 狀態異常,非NEW狀態的線程*/
if (threadStatus != 0)
throw new IllegalThreadStateException();
/**將線程加入到線程組,減線程組的unstarted計數器**/
group.add(this);
boolean started = false;
try {
/**native方法啟動線程**/
start0();
started = true;
} finally {
try {
if (!started) {
/**移出線程組,增加unstarted計數器**/
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/** do nothing. start0的異常會直接拋出 **/
}
}
}
/**啟動線程的native方法**/
private native void start0();
- Thread本身也實現了Runnable接口, 實現run方法,虛擬機啟動時,調用的就是該實現方法。
- 單純從target看,Thread和Runnable的關系看,這是簡單的模板方法模式的實現。
- 這也引出了第二種實現方式,實現Runnable接口或者實現/繼承Runnable接口的子接口/子類。
- 所以繼承Thread實現run和實現Runnable接口實現run方法是有本質區別的,Thread的run是被虛擬機調用的,Runnable的run是作為thread的target屬性的模板方法被調用的。
@Override
public void run() {
/**如果target不為空,調用target的run方法**/
if (target != null) {
target.run();
}
}
二、實現或繼承Runnable接口的子接口或實現類
實現多線程的另外方式是實現或繼承Runnable接口的子接口或實現類
1、繼承Runnable接口
直接實現Runnable接口的問題是,線程執行完成后,無法獲取到線程執行結果。
/**
* 實現線程方式: 2、實現Runnable接口
*/
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
log.info("Runnable implements 1: " + System.currentTimeMillis());
try {
Thread.sleep(200);
} catch (InterruptedException e) {
log.error(e.getMessage(), e);
}
}
}
});
thread2.start();
/**
* 實現線程方式: jdk1.8之后,可以更簡單的寫法
*/
Thread thread3 = new Thread(() -> {
while (true) {
log.info("Runnable implements 2: " + System.currentTimeMillis());
try {
Thread.sleep(200);
} catch (InterruptedException e) {
log.error(e.getMessage(), e);
}
}
});
thread3.start();
2、繼承FutureTask接口
如果需要獲取線程執行結果, 可以實現FutureTask接口, 并通過FutureTask的get方法獲取執行結果:
/**
* 實現線程方式: 3、實現Callable接口,配合FutureTask,獲取線程執行結果
*/
FutureTask<Integer> task = new FutureTask<>(new Fibonacci(10));
Thread thread4 = new Thread(task);
thread4.start();
try {
Integer result = task.get();
log.info("FutureTask result: {}" , result);
} catch (ExecutionException e) {
log.error(e.getMessage(), e);
}
FutureTask相關的源碼研究,后續分析。
3、繼承TimerTask接口
如果需要在主線程外執行一些任務的話,可以使用TimerTask接口,配合定時器工具Timer實現,Timer內部維護一個TimerThread線程,用于執行調度任務:
/**
* 實現線程方式:4、調度執行任務
*/
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
log.info("TimerTask execute: {}", System.currentTimeMillis());
}
}, 5000, 3000);
簡單的應用可以使用Timer,復雜需求應該引入框架。
三、Executor框架
JDK1.5引入Executor異步執行框架,靈活強大,支持多種任務執行策略,將任務提交和執行解耦,可以通過submit和execute提交任務給線程池執行。Executors提供一系列創建線程池的工廠方法。
后續對Executor框架進行分析
/**
* 實現多線程的方式: 5、java.util.concurrent包提供的Executor框架,創建線程池,執行任務
*/
ExecutorService executorService = Executors.newFixedThreadPool(5);
//通過execute執行實現Runnable接口的任務
for(int i = 0; i < 100; i++) {
executorService.execute(new Runnable() {
@Override
public void run() {
log.info("ThreadPool execute task: {} {}",
Thread.currentThread().getName(),
System.currentTimeMillis());
try {
Thread.sleep(100);
} catch (InterruptedException e) {
log.error(e.getMessage(), e);
}
}
});
}
//也可以通過submit實現Callable接口的任務,以便獲取線程執行結果
Future<Integer> fiboResult = executorService.submit(new Fibonacci(10));
try {
log.info("ThreadPool submit task: {}",fiboResult.get());
} catch (ExecutionException e) {
log.error(e.getMessage(), e);
}
四、ForkJoin框架
如果大任務可以分解成小任務并行計算,可以實現RecursiveTask接口,提交到ForJoin框架執行。
public static void useForkJoinFramework() {
long start = System.currentTimeMillis();
ForkJoinPool pool = new ForkJoinPool();
Long result = pool.invoke(createNewTask(0L, 10000000000L, 1000000000L));
log.info("ForkJoinPool execute result: {} {}", result , (System.currentTimeMillis() - start));
start = System.currentTimeMillis();
long sum = 0;
for(long i = 0;i <= 10000000000L; i++) {
sum += i;
}
log.info("Simple sum: {} {}", sum, (System.currentTimeMillis() - start));
}
@SuppressWarnings("serial")
public static RecursiveTask<Long> createNewTask(final Long start, final Long end, final Long critical){
return new RecursiveTask<Long>() {
@Override
protected Long compute() {
if(end - start <= critical) {
long sum = 0L;
for(long l = start; l <= end; l++) {
sum += l;
}
return sum;
}else {
Long middle = (end + start) / 2;
RecursiveTask<Long> left = createNewTask(start, middle, critical);
RecursiveTask<Long> right = createNewTask(middle+1, end, critical);
left.fork();
right.fork();
return left.join()+right.join();
}
}
};
}
RecursiveTask接口繼承自ForkJoinTask接口, ForkJoinTask繼承Future接口。
五、總結
Java中多線程編程主要分兩類:
- 通過創建Thread實例創建線程和start()方法啟動線程,自己管理線程,執行任務;
- 通過Executor框架創建線程池,或者實現相關接口,通過線程池管理管理線程執行任務;
- 通過ForkJoin框架并行執行任務。
不管是通過哪種方式, 都有支持Runnable接口提交任務和支持Callable接口的方式,jdk1.8之后,還可以通過lambda表達式提交任務,
編程上弱化了Runnable和Callable的區別,所以選用哪種方式去創建線程和管理線程,取決于:
- 場景是否足夠簡單,只需簡單管理線程,還是需要強大的線程管理功能?
- 是否需要線程執行結果,可以Future相關的API?
- 是否可以并行執行?