对于所有语言来说,多线程的编程是绝不可少的。同样的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线程以及线程池的知识。