说起java多线程编程,大家都不陌生,下面我就总结下java里实现多线程的集中方法:
1、继承Thread类
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 |
package com.goomoon.JavaDemo; /** * 通过继承 Thread 并重写 run()方法 来创建线程类 * @author kevin */ public class ThreadTest extends Thread { /** * 重写run() 方法 ,run()方法的方法体就是线程执行体 */ @Override public void run() { for(int i =0;i<100;i++){ /** * 使用getName() 获取当前线程的名称 */ System.out.println(getName()+":"+i); } } public static void main(String[] args) { for(int i = 0;i<100;i++){ //调用Thread类的currentThread()方法获取当前线程 System.out.println(Thread.currentThread().getName() +":"+i); if(i == 20){ //创建并启动第1个线程 new ThreadTest().start(); //创建并启动第2个线程 new ThreadTest().start(); } } } } |
这里有一个小知识点,我觉得挺有意思,那就是:
第一种方法:
1 2 |
ThreadTest instance = new ThreadTest(); instance.start(); |
第二种方式:
1 2 |
ThreadTest instance = new ThreadTest(); instance.run(); |
这两种方式有什么区别呢??
第一种方式,是另外起一个线程,至于这个线程具体的执行时间需要靠JVM内部线程调度器进行调度来决定何时进行轮询执行,而第二种方式呢,就和一个普通Java类一样,并没有新起一个线程,而是作为一个普通的类对象执行方法,并且是立即执行
2、实现Runnable接口
方法:实现Runnable接口并重写Run()方法,创建Runnable实例,并以此实例作为Thread类的target参数创建Thread对象,调用Thread对象的start()启动线程
代码如下:
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 |
package com.goomoon.JavaDemo; //通过实现Runnable接口来创建线程类 public class RunnableTest implements Runnable { /** * run()方法同样是线程执行体 */ @Override public void run() { for(int i = 0;i<100;i++){ /** * 实现Runnable接口的方法体里,只能通过Thread.currentThread()来获取当前的线程 */ System.out.println(Thread.currentThread().getName()+":"+i); } } public static void main(String[] args) { //实例化Runnable类,创建一个任务 Runnable instance = new RunnableTest(); //用Thread类来创建目标线程,并start()启动线程 new Thread(instance).start(); //当前主线程继续执行 for(int i = 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来创建并启动线程
代码如下:
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 |
package com.goomoon.JavaDemo; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; //使用Callable接口和Future来创建线程 public class ThreadFuture { //抛出异常 public static void main(String[] args) throws InterruptedException, ExecutionException { //创建FutureTask对象,包装 Callable接口实例 FutureTask<Integer> task = new FutureTask<Integer>((Callable<Integer>)()->{ int sum = 0; for(int i = 0;i<100;i++){ System.out.println(Thread.currentThread().getName()+":"+i); sum += i; } //注意看这里有返回值 return sum; }); //使用task作为 Thread类的target 来创建一个线程 Thread instance = new Thread(task); //启动线程 instance.start(); //sleep一段时间,让上面的线程执行完毕 Thread.sleep(1000); //这里可以调用task.get() 获取上面的那个线程的返回值 System.out.println("线程返回值:"+task.get()); } } |
如上面代码所示,在后面可以通过
1 |
task.get() |
来获取线程的返回值,这也是线程之间通信的一种方式呢。上面有一段代码:
1 2 3 4 5 6 7 8 9 |
FutureTask<Integer> task = new FutureTask<Integer>((Callable<Integer>)()->{ int sum = 0; for(int i = 0;i<100;i++){ System.out.println(Thread.currentThread().getName()+":"+i); sum += i; } //注意看这里有返回值 return sum; }); |
咋一看,好像是在FutureTask的构造函数里声明了一个匿名内部类,仔细看呢又不太像,这一种写法其实是Java8里的一种新特性:Lambda表达式,使用Lambda表达式直接创建函数式接口的实例。其实和下面一种写法一致:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
Callable<Integer> call = new Callable<Integer>(){ @Override public Integer call() throws Exception { int sum = 0; for(int i = 0;i<100;i++){ System.out.println(Thread.currentThread().getName()+":"+i); sum += i; } //注意看这里有返回值 return sum; } }; FutureTask<Integer> task = new FutureTask<Integer>(call); |
总结:根据上面三种方式呢都可以实现多线程,不过Runnable接口和Callable接口的方式基本相同(实现Runnable接口和Callable接口仅仅是创建了一个任务,但是仍然需要Thread类来创建线程并启动),他俩唯一区别只是Callable接口实现方法有返回值,并且也可以声明抛出异常而已,因此②和③可以归为一类,下面主要讨论和①中方法的区别和优缺点:
1、方法②和③,不仅实现了Runnable接口和Callable接口,还可以继承其他类,可拓展性比较好
2、多个线程共享一个target,所以更适合多线程共享统一资源。
3、第①中方法,实现起来比较简单,但是不太适合多个线程共享一个资源的情况。
4、还有一个小小的区别,就是在线程执行体里调用当前线程的方式不一样,在Thread子类的run()方法里可以直接使用this获取当前线程,而在Runnable和Callable的run()实现里,只能通过Thread.currentThread()来获取。
鉴于上面的分析,因此一般推荐采用Runnable接口或者Callable接口来实现多线程。
转载请注明:刘召考的博客 » Java实现多线程的几种方法和区别