说起java多线程编程,大家都不陌生,下面我就总结下java里实现多线程的集中方法:
1、继承Thread类
方法:继承Thread类,重写run()方法,实例化线程类,调用start()方法启动线程。
代码如下:
通过继承Thread类创建线程类
Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
packagecom.goomoon.JavaDemo;
/**
* 通过继承 Thread 并重写 run()方法 来创建线程类
* @author kevin
*/
publicclassThreadTestextendsThread{
/**
* 重写run() 方法 ,run()方法的方法体就是线程执行体
*/
@Override
publicvoidrun(){
for(inti=0;i<100;i++){
/**
* 使用getName() 获取当前线程的名称
*/
System.out.println(getName()+":"+i);
}
}
publicstaticvoidmain(String[]args){
for(inti=0;i<100;i++){
//调用Thread类的currentThread()方法获取当前线程
System.out.println(Thread.currentThread().getName()+":"+i);
if(i==20){
//创建并启动第1个线程
newThreadTest().start();
//创建并启动第2个线程
newThreadTest().start();
}
}
}
}
这里有一个小知识点,我觉得挺有意思,那就是:
第一种方法:
Java
1
2
ThreadTestinstance=newThreadTest();
instance.start();
第二种方式:
Java
1
2
ThreadTestinstance=newThreadTest();
instance.run();
这两种方式有什么区别呢??
第一种方式,是另外起一个线程,至于这个线程具体的执行时间需要靠JVM内部线程调度器进行调度来决定何时进行轮询执行,而第二种方式呢,就和一个普通Java类一样,并没有新起一个线程,而是作为一个普通的类对象执行方法,并且是立即执行
2、实现Runnable接口
方法:实现Runnable接口并重写Run()方法,创建Runnable实例,并以此实例作为Thread类的target参数创建Thread对象,调用Thread对象的start()启动线程
代码如下:
实现Runnable接口
Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
packagecom.goomoon.JavaDemo;
//通过实现Runnable接口来创建线程类
publicclassRunnableTestimplementsRunnable{
/**
* run()方法同样是线程执行体
*/
@Override
publicvoidrun(){
for(inti=0;i<100;i++){
/**
* 实现Runnable接口的方法体里,只能通过Thread.currentThread()来获取当前的线程
*/
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
publicstaticvoidmain(String[]args){
//实例化Runnable类,创建一个任务
Runnableinstance=newRunnableTest();
//用Thread类来创建目标线程,并start()启动线程
newThread(instance).start();
//当前主线程继续执行
for(inti=0;i<100;i++){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
其实 这两种实现方式,是java里最典型的两种实现方式。除了这两种方法呢,Java里还提供了第三种方式:Callable接口,看起来很像Runnable接口的增强版,不过其提供了一个call()方法作为线程执行体。但是call()比run()方法更加强大,因为最重要的点:call()可以有返回值。下面就仔细看下怎么用Callable接口来创建线程类。
3、使用Callable接口和Future来创建线程
Callable 不是 Runnable接口的子接口,不能直接作为Thread类的target参数。所以呢??看Java API可以了解,Java里有个FutureTask类实现了Future接口,更重要的是FutureTask类还实现了Runnable接口,这样就可以作为Thread类的target被执行了。
方法:实现Callable接口,并实现call()方法,并创建Callable实例,然后使用FutureTask对象来包装Callable实例,使用FutureTask对象作为Thread的target来创建并启动线程
代码如下:
使用Callable接口和Future来创建线程
Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
packagecom.goomoon.JavaDemo;
importjava.util.concurrent.Callable;
importjava.util.concurrent.ExecutionException;
importjava.util.concurrent.FutureTask;
//使用Callable接口和Future来创建线程
publicclassThreadFuture{
//抛出异常
publicstaticvoidmain(String[]args)throwsInterruptedException,ExecutionException{
//创建FutureTask对象,包装 Callable接口实例
FutureTasktask=newFutureTask((Callable)()->{
intsum=0;
for(inti=0;i<100;i++){
System.out.println(Thread.currentThread().getName()+":"+i);
sum+=i;
}
//注意看这里有返回值
returnsum;
});
//使用task作为 Thread类的target 来创建一个线程
Threadinstance=newThread(task);
//启动线程
instance.start();
//sleep一段时间,让上面的线程执行完毕
Thread.sleep(1000);
//这里可以调用task.get() 获取上面的那个线程的返回值
System.out.println("线程返回值:"+task.get());
}
}
如上面代码所示,在后面可以通过
使用Callable接口和Future来创建线程
Java
1
task.get()
来获取线程的返回值,这也是线程之间通信的一种方式呢。上面有一段代码:
Java
1
2
3
4
5
6
7
8
9
FutureTasktask=newFutureTask((Callable)()->{
intsum=0;
for(inti=0;i<100;i++){
System.out.println(Thread.currentThread().getName()+":"+i);
sum+=i;
}
//注意看这里有返回值
returnsum;
});
咋一看,好像是在FutureTask的构造函数里声明了一个匿名内部类,仔细看呢又不太像,这一种写法其实是Java8里的一种新特性:Lambda表达式,使用Lambda表达式直接创建函数式接口的实例。其实和下面一种写法一致:
Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Callablecall=newCallable(){
@Override
publicIntegercall()throwsException{
intsum=0;
for(inti=0;i<100;i++){
System.out.println(Thread.currentThread().getName()+":"+i);
sum+=i;
}
//注意看这里有返回值
returnsum;
}
};
FutureTasktask=newFutureTask(call);
总结:根据上面三种方式呢都可以实现多线程,不过Runnable接口和Callable接口的方式基本相同(实现Runnable接口和Callable接口仅仅是创建了一个任务,但是仍然需要Thread类来创建线程并启动),他俩唯一区别只是Callable接口实现方法有返回值,并且也可以声明抛出异常而已,因此②和③可以归为一类,下面主要讨论和①中方法的区别和优缺点:
1、方法②和③,不仅实现了Runnable接口和Callable接口,还可以继承其他类,可拓展性比较好
2、多个线程共享一个target,所以更适合多线程共享统一资源。
3、第①中方法,实现起来比较简单,但是不太适合多个线程共享一个资源的情况。
4、还有一个小小的区别,就是在线程执行体里调用当前线程的方式不一样,在Thread子类的run()方法里可以直接使用this获取当前线程,而在Runnable和Callable的run()实现里,只能通过Thread.currentThread()来获取。
鉴于上面的分析,因此一般推荐采用Runnable接口或者Callable接口来实现多线程。