在平常開發工做中,多線程開發能夠說是必備技能,好的程序員是必定要對線程這塊有深刻了解的,我是Java程序員,而且Java語言自己對於線程開發的支持是很是成熟的,因此今天咱們就來入個門,學一下Java怎麼建立線程。程序員
Java建立線程主要有三種方式:bash
一、繼承Thread類多線程
二、實現Runnable接口ide
三、使用Callable和Future建立線程ui
下面分別討論這三種方法的實現方式,以及它們之間的對比。this
步驟:編碼
一、建立一個線程子類繼承Thread類spa
二、重寫run() 方法,把須要線程執行的程序放入run方法,線程啓動後方法裏的程序就會運行線程
二、建立該類的實例,並調用對象的start()方法啓動線程code
示例代碼以下:
public class ThreadDemo extends Thread{
@Override
public void run() {
super.run();
System.out.println("須要運行的程序。。。。。。。。");
}
public static void main(String[] args) {
Thread thread = new ThreadDemo();
thread.start();
}
}
複製代碼
當運行main方法後,程序就會執行run()方法裏面的內容,執行完以後,線程也就隨之消亡,爲何必定要重寫run()方法呢?
點擊方法的源碼後,發現Thread的run()方法其實什麼都沒有作
public void run() {
if (target != null) {
target.run();
}
}
public abstract void run();
複製代碼
若是run()裏沒有須要運行的程序,那麼線程啓動後就直接消亡了。想讓線程作點什麼就必須重寫run()方法。同時,還須要注意的是,線程啓動須要調用start()方法,但直接調用run() 方法也能編譯經過,也能正常運行:
public static void main(String[] args) {
Thread thread = new ThreadDemo();
thread.run();
}
複製代碼
只是這樣是普通的方法調用,並無新起一個線程,也就失去了線程自己的意義。
一、定義一個線程類實現Runnable接口,並重寫該接口的run()方法,方法中依然是包含指定執行的程序。
二、建立一個Runnable實現類實例,將其做爲target參數傳入,並建立Thread類實例。
三、調用Thread類實例的start()方法啓動線程。
public class RunnableDemo implements Runnable{
@Override
public void run() {
System.out.println("我是Runnable接口......");
}
public static void main(String[] args) {
RunnableDemo demo = new RunnableDemo();
Thread thread = new Thread(demo);
thread.start();
}
}
複製代碼
這是基於接口的方式,比起繼承Thread的方式要靈活不少,但須要多建立一個線程對象,打開源碼能夠發現,當把Runnable實現類的實例做爲參數target傳入後,賦值給當前線程類的target,而run()裏執行的程序就是賦值進去的target的run()方法。
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc) {
...........這裏省略部分源碼..........
this.target = target;
setPriority(priority);
if (parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
/* Stash the specified stack size in case the VM cares */
this.stackSize = stackSize;
/* Set thread ID */
tid = nextThreadID();
}
@Override
public void run() {
if (target != null) {
target.run();
}
}
複製代碼
使用Callable建立線程和Runnable接口方式建立線程比較類似,不一樣的是,Callable接口提供了一個call() 方法做爲線程執行體,而Runnable接口提供的是run()方法,同時,call()方法能夠有返回值,並且須要用FutureTask類來包裝Callable對象。
public interface Callable<V> {
V call() throws Exception;
}
複製代碼
步驟:
一、建立Callable接口的實現類,實現call() 方法
二、建立Callable實現類實例,經過FutureTask類來包裝Callable對象,該對象封裝了Callable對象的call()方法的返回值。
三、將建立的FutureTask對象做爲target參數傳入,建立Thread線程實例並啓動新線程。
四、調用FutureTask對象的get方法獲取返回值。
public class CallableDemo implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int i = 1;
return i;
}
public static void main(String[] args) {
CallableDemo demo = new CallableDemo();
FutureTask<Integer> task = new FutureTask<Integer>(demo);
new Thread(task).start();
try {
System.out.println("task 返回值爲:" + task.get());
} catch (Exception e) {
e.printStackTrace();
}
}
}
複製代碼
執行main方法後,程序輸出以下結果:
task 返回值爲:1
複製代碼
說明,task.get()確實返回了call() 方法的結果。那麼其內部是怎麼實現的呢。先打開FutureTask的構造方法,能夠看到其內部是將Callable對象做爲參數傳遞給當前實例的Callable成員,
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
複製代碼
同時,將成員變量state置爲NEW,當啓動task後,其run方法就會執行Callable的call()方法,
public void run() {
if (state != NEW ||
!UNSAFE.compareAndSwapObject(this, runnerOffset,
null, Thread.currentThread()))
return;
try {
Callable<V> c = callable;
if (c != null && state == NEW) {
V result;
boolean ran;
try {
//把call()的返回結果複製給result
result = c.call();
ran = true;
} catch (Throwable ex) {
result = null;
ran = false;
setException(ex);
}
if (ran)
//將結果設置給其餘變量
set(result);
}
} finally {
// runner must be non-null until state is settled to
// prevent concurrent calls to run()
runner = null;
// state must be re-read after nulling runner to prevent
// leaked interrupts
int s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
}
protected void set(V v) {
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
//把傳過來的值賦值給outcome成員
outcome = v;
UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
finishCompletion();
}
}
複製代碼
run()方法中通過一系列的程序運行後,把call()的返回結果賦值給了outcome,而後當調用task.get()方法裏獲取的就是outcome的值了,這樣一來,也就瓜熟蒂落的獲得了返回結果。
public V get() throws InterruptedException, ExecutionException {
int s = state;
if (s <= COMPLETING)
s = awaitDone(false, 0L);
return report(s);
}
private V report(int s) throws ExecutionException {
Object x = outcome;
if (s == NORMAL)
//返回outcome的值
return (V)x;
if (s >= CANCELLED)
throw new CancellationException();
throw new ExecutionException((Throwable)x);
}
複製代碼
能夠看出,源碼的運行邏輯仍是比較清晰的,代碼也比較容易理解,因此,我比較建議讀者們有空能夠多看看Java底層的源碼,這樣能幫助咱們深刻的理解功能是怎麼實現的。
好了,建立線程的三種方式實例都說完了,接下來講下他們的對比。
從實現方式來講,使用Runnable接口和Callable接口的方式基本相同,區分的只是Callable實現的方法體能夠有返回值,而繼承Thread類是使用繼承方式,因此,其實三種方法歸爲兩類來分析便可。
一、使用繼承Thread類的方式:
二、使用Runnable接口和Callable接口的方式:
比較靈活,線程只是實現接口,還能夠繼承其餘父類。
這種方式下,多個線程能夠共享一個target對象,很是適合多線程處理同一份資源的情形。
Callable接口的方式還能獲取返回值。
編碼稍微複雜了點,須要建立更多對象。
若是想訪問當前線程,須要用Thread.currentThread()方法。
總的來講,兩種分類都有各自的優劣勢,但其實後者的劣勢相對優點來講不值一提,通常狀況下,仍是建議直接用接口的方式來建立線程,畢竟單一繼承的劣勢仍是比較大的。