Java並發編程:Java創建線程的三種方式
目錄
引言
在日常開發工作中,多線程開發可以說是必備技能,好的程序員是一定要對線程這塊有深入了解的,我是Java程序員,並且Java語言本身對於線程開發的支持是非常成熟的,所以今天我們就來入個門,學一下Java怎么創建線程。
創建線程的三種方式
Java創建線程主要有三種方式:
1、繼承Thread類
2、實現Runnable接口
3、使用Callable和Future創建線程
下面分別討論這三種方法的實現方式,以及它們之間的對比。
一、繼承Thread類
步驟:
1、創建一個線程子類繼承Thread類
2、重寫run() 方法,把需要線程執行的程序放入run方法,線程啟動后方法里的程序就會運行
2、創建該類的實例,並調用對象的start()方法啟動線程
示例代碼如下:
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接口
1、定義一個線程類實現Runnable接口,並重寫該接口的run()方法,方法中依然是包含指定執行的程序。
2、創建一個Runnable實現類實例,將其作為target參數傳入,並創建Thread類實例。
3、調用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和Future創建線程
使用Callable創建線程和Runnable接口方式創建線程比較相似,不同的是,Callable接口提供了一個call() 方法作為線程執行體,而Runnable接口提供的是run()方法,同時,call()方法可以有返回值,而且需要用FutureTask類來包裝Callable對象。
public interface Callable<V> {
V call() throws Exception;
}
步驟:
1、創建Callable接口的實現類,實現call() 方法
2、創建Callable實現類實例,通過FutureTask類來包裝Callable對象,該對象封裝了Callable對象的call()方法的返回值。
3、將創建的FutureTask對象作為target參數傳入,創建Thread線程實例並啟動新線程。
4、調用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類是使用繼承方式,所以,其實三種方法歸為兩類來分析即可。
1、使用繼承Thread類的方式:
- 優勢:編碼簡單,並且,當需要獲取當前線程,可以直接用this
- 劣勢:由於Java支持單繼承,所以繼承Thread后就不能繼承其他父類
2、使用Runnable接口和Callable接口的方式:
- 優勢:
比較靈活,線程只是實現接口,還可以繼承其他父類。
這種方式下,多個線程可以共享一個target對象,非常適合多線程處理同一份資源的情形。
Callable接口的方式還能獲取返回值。
- 劣勢:
編碼稍微復雜了點,需要創建更多對象。
如果想訪問當前線程,需要用Thread.currentThread()方法。
總的來說,兩種分類都有各自的優劣勢,但其實后者的劣勢相對優勢來說不值一提,一般情況下,還是建議直接用接口的方式來創建線程,畢竟單一繼承的劣勢還是比較大的。