對於所有語言來說,多線程的編程是絕不可少的。同樣的Java語言也包含了多線程的開發。首先,我們先來了解一下Java語言的多線程實現方式。
一、Java 多線程實現方式
java中實現多線程的方式有三種,接下來我將會逐個進行介紹。
1.繼承Thread類
繼承Thread類是Java中比較常見,也是很基礎的一種實現Java多線程的方式。實現的方式也比較簡單,只要將需要實現多線程的Java類繼承java.lang.Thread類即可。
class
MyThread
extends
Thread{
private
String
name
;
public
MyThread(String
name
){
this
.
name
=
name
;
}
@Override
public
void
run() {
Thread.
currentThread
().setName(
name
);
System.out.println("I am Thread :" +name);
}
}
如上代碼片段則是通過繼承Thread類實現多線程的方式之一。那么多線程該如何調用呢?請接着閱讀下邊的代碼。
public
class
threadLearn {
public
static
void
main(String[]
args
) {
//實例化繼承了Thread的類
MyThread
thread1
=
new
MyThread(
"Thread1"
);
//通過從Thread類中所繼承的start()方法啟動線程;
thread1
.start();
}
}
運行上邊的代碼,可以得到結果:
到這里,一個簡單的線程已經被啟動了。下邊我們接着介紹另外兩種多線程的實現方式。
2.實現Runable接口
接下來請看這樣一種場景,DogRobot是一個用來看門的Dog,現在需要多個Dog分別把守不同的位置,但DogRobot一定要繼承Robot父類,此時應該如何實現多線程呢?
在這種場景下,可以通過實現Runable接口的方式來實現一個類的多線程。
我們知道,在Java中,類的繼承是單一的,即一個子類僅可以繼承一個父類(一個兒子只有一個爹,符合自然規律),但可以實現多個接口。那么,通過Runable接口來實現多線程的好處自然不言而喻。
接下來看一下實現方式:
public
class
threadLearn {
public
static
void
main(String[]
args
) {
//實例化繼承了Thread的類
MyThread
thread1
=
new
MyThread(
"Thread1"
);
//通過從Thread類中所繼承的start()方法啟動線程;
thread1
.start();
/*
* 實現Runnable的類,需要將其放在一個Thread實例對象中,
* 由Thread實例對象進行線程的啟動及控制。
*/
Thread
threadDog
=
new
Thread(
new
DogRobot(
"kiki"
));
threadDog
.start();
}
}
class
DogRobot
extends
Robot
implements
Runnable{
private
String
name
;
public
DogRobot(String
name
){
super
(
name
);
this
.
name
=
name
;
}
@Override
public
void
run() {
Thread.
currentThread
().setName(
name
);
System.
out
.println(
"I am DogRobot :"
+
name
);
}
}
class
Robot {
private
String
Name
;
public
Robot(String
name
)
{
this
.
Name
=
name
;
}
}
下邊接着講解第三種多線程的實現方式。
3.
使用Executor框架實現多線程
在介紹Excutor實現多線程之前,我們先來學習另外一種多線程的實現方式,即繼承Callable接口實現多線程的方式。
首先我們來看一下Callable的接口:
可以看到,Callable的接口只有一個方法,那么我們在實現這個接口的時候也僅需要實現這個方法即可。
/*
*
Callable
接口實現多線程Demo
*/
class
MyCallable<V>
implements
Callable<V>
{
@Override
public
V call()
throws
Exception {
//
TODO
Auto-generated method stub
System.
out
.println(
"I am Callable thread : "
+Thread.
currentThread
().getName());
return
null
;
}
}
如何調用這個線程,使其執行任務呢?那么,是需要通過FutureTask<V>這個類的實例去調度,我們首先來看一下FutureTask類的結構:
public
class
FutureTask<V>
implements
RunnableFuture<V>
這是FutureTask的實現方式,我們可以看到FutrueTask是實現了RunnableFuture的方法,那么RunnableFuture又是做什么的呢?我們接着跟進去看看結構:
public
interface
RunnableFuture<V>
extends
Runnable, Future<V> {
/**
* Sets this Future to the result of its computation
* unless it has been cancelled.
*/
void
run();
}
以上您所看到的,正是RunnableFuture接口的結構,我們可以看到,這個接口,是繼承了Runnable接口,和Future接口的一個接口,至於Future接口是什么,我們后續會講到。
如果您看到了Runnable接口,那么我想應該已經明了了,實現了Runnable接口的類,可以通過Thread實例對象來驅動,從而運行一個獨立的線程。這是我們前邊所講到的。
到這里,還有一個問題,FutureTask和Callable接口有什么關系呢?
那么我們接着看FutureTask的構造方法:
/**
* Creates a {@code FutureTask} that will, upon running, execute the
* given {@code Callable}.
*
*
@param
callable the callable task
*
@throws
NullPointerException if the callable is null
*/
public
FutureTask
(Callable<V>
callable
) {
if
(
callable
==
null
)
throw
new
NullPointerException();
this
.
callable
=
callable
;
this
.
state
=
NEW
;
// ensure visibility of callable
}
可以看到,FutureTask的構造方法之一所需要的參數,就是Callable的實例對象。為什么說之一呢,因為還有一個構造方法,是通過Runnable接口實現的,如何實現的呢:
public
FutureTask(Runnable
runnable
, V
result
) {
this
.
callable
= Executors.
callable
(
runnable
,
result
);
this
.
state
=
NEW
;
// ensure visibility of callable
}
其實我們繼續跟入進去可以發現,其實這個方法本質上還是生成了一個Callable的對象,最后賦予自己內部的this.callable;感興趣的同學可以自己試一試這種方式。
回到我們的話題,繼續實現調用:
public
static
void
main(String[]
args
) {
/*
* 使用
Callable
來創建線程
*/
Callable <Integer>
aCallable
=
new
MyCallable<Integer>();
FutureTask<Integer>
aTask
=
new
FutureTask<Integer>(
aCallable
);
Thread
aThread
=
new
Thread(
aTask
);
aThread
.start();
}
我們運行一下程序,可以看到,運行的結果如下圖所示:
到這里,一個通過Callable實現的接口便是成功了。
那么這里會有一個問題,按照這樣的實現方式,那和Runnable接口有什么區別???
其實我們應該注意到了,Callable接口里的call方法,是一個有返回值的方法;
且FutureTask類的實現方式中,針對Runnable的實現方式,也是攜帶有一個參數result,由result和Runnable實例去合並成一個Callable的實例。
小本本拿出來划重點了。
實現了Callable接口的線程,是具有返回值的。而對於一些對線程要求有返回值的場景,是非常適用的
。
接下來,我們就看一下,如何獲通過Callable接口獲取線程的返回值。
獲取Callable接口的返回值,需要使用Future接口的實例化對象通過get的方式獲取出來。
Mark -- 2018 04 24
今天接着學習多線程。昨天說到通過實現Callable接口實現多線程,今天我們來看如何通過Callable線程獲取到線程的返回值。
首先我們先來認識幾個接口及一個工廠類
ExecutorService 接口
public
interface
ExecutorService
extends
Executor
ExecutorService接口是實現線程池經常使用的接口。通常我們使用的線程池、定時任務線程池都是他的實現類。
Executors工廠類
public
class
Executors
Executors是一個工廠類,通過調用工廠類的方法,可以返回各種線程池的實現類。比如我們接下來要用到的:
public
static
ExecutorService newFixedThreadPool(
int
nThreads
) {
return
new
ThreadPoolExecutor(
nThreads
,
nThreads
,
0L, TimeUnit.
MILLISECONDS
,
new
LinkedBlockingQueue<Runnable>());
}
我們可以看到,返回值的類型是一個
ExecutorService 而實際上返回的返回值,則是一個
ThreadPoolExecutor ,我們接着跟進去看下ThreadPoolExecutor是什么:
public class ThreadPoolExecutor extends AbstractExecutorService
到這里,我們可以看到,ThreadPoolExecutor是一個繼承了
AbstractExecutorService 的實現類。
這個線程池類里定義了各種我們需要對線程池進行操作的方法。后續有時間我們再研究一下源碼。
有了上述的這些基本了解,我們則可以進行線程池的創建:
int
taskSize
= 5;
ExecutorService
exPool
= Executors.
newFixedThreadPool
(
taskSize
);
如上邊這樣的代碼,我們就可以創建一個具有5個線程容量的線程池。
那么如何將Callabe接口添加到線程池中運行呢?
ExcutorService提供了一個方法供我們調用:
<T> Future<T> submit(Callable<T>
task
);
我們看到,這個submit方法也只是接口里定義的一個方法,那么他的實現方法是什么呢,我們代碼里跟一下:
可以看到,有很多個實現的方法,那么,我們這里調用的是哪一個呢?
很顯然,我們調用的是AbstractExecutorService中的實現方法。因為用來調用submit的實例對象實際上正是ThreadPoolExecutor,剛才我們也提到了,ThreadPoolExecutor是繼承了AbstractExcutorService的。
那么結果應該相當的明顯了吧。
/**
*
@throws
RejectedExecutionException
{@inheritDoc}
*
@throws
NullPointerException
{@inheritDoc}
*/
public
<T> Future<T> submit(Callable<T>
task
) {
if
(
task
==
null
)
throw
new
NullPointerException();
RunnableFuture<T>
ftask
= newTaskFor(
task
);
execute(
ftask
);
return
ftask
;
}
看到這里是不是覺得有一絲絲的熟悉,正如我們第三小節所提到的 RunnableFuture ,從本質上,我們還是返回了一個RunnableFuture類型的實例。因此,我們可以通過該實例獲取到線程的返回值。
那么接下來,我們寫一個小demo來實踐一下,通過ExcutorService來加載Callable接口實現多線程,並且得到返回值。
首先,我們來寫一個Callable接口的實現類
/*
*
Callable
接口實現多線程Demo
*/
class
MyCallable
implements
Callable<Object>
{
private
int
task_id
;
MyCallable(
int
task_id
)
{
this
.
task_id
=
task_id
;
}
@Override
public
Object call()
throws
Exception {
//
TODO
Auto-generated method stub
System.
out
.println(
"I am Callable thread : "
+Thread.
currentThread
().getName());
Random
rd
=
new
Random();
int
leng
=
rd
.nextInt(9)+1;
int
sum
= 0 ;
for
(
int
i
= 0 ;
i
<=
leng
;
i
++ )
{
Thread.
sleep
(1000);
sum
+=
i
;
}
System.
out
.println(
"I am Callable thread : "
+Thread.
currentThread
().getName()
+
"Thread name is : ["
+
this
.
task_id
+
"]"
+
" I worked done at ["
+
new
Date().getTime()+
"]"
+
" Random Num = "
+
leng
);
return
"The task ["
+
this
.
task_id
+
"] get result :【 "
+
sum
+
" 】"
;
}
}
這個線程會返回1-10以內的隨機數的自增合。我們來通過線程池調度一下:
/*
*使用
Excutor
來執行線程,並獲取返回值
*/
int
taskSize
= 5;
ExecutorService
exPool
= Executors.
newFixedThreadPool
(
taskSize
);
//使用Future來獲取線程的結果
List<
Future
>
list
=
new
ArrayList<
Future
>();
for
(
int
i
= 0;
i
<
taskSize
;
i
++) {
Callable
c
=
new
MyCallable(
i
);
// 執行任務並獲取Future對象
Future
f
=
exPool
.submit(
c
);
list
.add(
f
);
}
exPool
.shutdown();
System.
out
.println(
"The time we shutDown The Executor Pool : 【"
+
new
Date().getTime()+
"】"
);
for
(
Future
f
:
list
) {
// 從Future對象上獲取任務的返回值,並輸出到控制台
try
{
System.
out
.println(
">>>"
+
f
.get().toString());
}
catch
(InterruptedException
e
) {
//
TODO
Auto-generated catch block
e
.printStackTrace();
}
catch
(ExecutionException
e
) {
//
TODO
Auto-generated catch block
e
.printStackTrace();
}
}
我們創建了一個List用來存放每個線程的執行結果。
shutdown()方法並不是終止線程池,而是在執行shutdown()方法之后,線程池則不會再接收新加入的線程。
我們運行一下 ,下邊的是運行的結果:
從這個結果上,我們可以看到,線程4抽到的隨機數是1,那么他執行的時間是最短的,只sleep 1S, 其他的線程2/3/1分別sleep了3/4/7S,線程5Sleep了9S,有意思的事情發生了。
我們這條長長的輸出實在線程執行完自己的計算之后輸出的,還沒有返回值,此時其他的幾個線程的輸出結果已經答應出來了,線程5在執行完畢之后,主線程才打印出了線程5的結果。
到這里,關於Java線程的創建就告一段落。隨后我們將會繼續深入學習Java線程以及線程池的知識。
