三种线程创建的方式

本篇内容为线程专题 -- 线程的创建,状态,工作过程中的线程创建方式的部分内容。

JAVA中有三种线程创建的方式:

(1)实现Runnable接口的run方法。

(2)继承Thread类并重写run的方法。

(3)使用FutureTask方式(实现Callable接口的方式)。

说明:在《Java并发编程之美》书中第(3)种创建方式描述为:使用FutureTask方式

 继承Thread类的方式的实现

 1 /**
 2  * 创建线程方式--继承Thread类的方式的实现
 3  * @author JustJavaIt
 4  */
 5 public class ThreadTest {
 6 
 7     public static void main(String[] args) {
 8         //创建线程
 9         MyThread thread = new MyThread();
10         //启动线程
11         thread.start();
12         System.out.println("I am main thread");
13     }
14 
15     /**
16      * 继承Thread类,并重写run方法
17      */
18     public static class MyThread extends Thread{
19         @Override
20         public void run() {
21             System.out.println("I am a child thread");
22         }
23     }
24 }
View Code

运行结果如下:

  如上代码中的MyThread类继承了Thread类,并重写了run()方法。在main函数里面创建了一个MyThread的实例,然后调用该实例的start方法启动了线程。需要注意的是, 当创建完thread对象后该线程并没有被启动执行,直到调用了start方法后才真正启动了线程

  其实调用start方法后线程并没有马上执行而是处于就绪状态,这个就绪状态是指该线程已经获取了除CPU资源外的其他资源,等待获取CPU资源后才会真正处于运行状态。 一旦run方法执行完毕,该线程就处于终止状态

  使用继承方式的好处是,在run()方法内获取当前线程直接使用this就可以了,无须使用Thread.currentThread()方法;不好的地方是Java不支持多继承,如果继承了Thread类, 那么就不能再继承其他类。另外任务与代码没有分离,当多个线程执行一样的任务时需要 多份任务代码,而Runable则没有这个限制。

实现Runnable接口的run方法方式

 1 /**
 2  * 创建线程方式--实现Runnable接口的run方法。
 3  * @author JustJavaIt
 4  * @date 2022/2/12 16:21
 5  */
 6 public class RunableTest {
 7     public static void main(String[] args) {
 8         RunableTask task = new RunableTask();
 9         //任务和代码分离,当多个线程执行一样任务时不用多份任务代码。
10         new Thread(task).start();
11         new Thread(task).start();
12     }
13 
14     public static class RunableTask implements Runnable{
15 
16         @Override
17         public void run() {
18             System.out.println("I am a child thread");
19         }
20     }
21 }
View Code

运行结果如下:

如上代码所示,两个线程共用一个task代码逻辑,如果需要,可以给RunableTask添加参数进行任务区分。另外,RunableTask可以继承其他类。但是上面介绍的两种方式都有一个缺点,就是任务没有返回值

使用FutureTask方式(实现Callable接口的方式)

  通过实现callable接口的方式,可以创建一个线程,需要重写其中的call方式。启动线程时,需要创建一个Callable的实例,再用FutureTask实例包装它,最终,再包装成Thread实例,调用start()启动,并且,可以通过FutureTask的get方法来获取返回值。

FutureTask介绍

  在Java并发程序中FutureTask表示一个可以取消的异步运算。它有启动和取消运算、查询运算是否完成和取回运算结果等方法。只有当运算完成的时候结果才能取回,如果运算尚未完成get方法将会阻塞。一个FutureTask对象可以对调用了Callable和Runnable的对象进行包装,由于FutureTask也是调用了Runnable接口所以它可以提交给Executor来执行。

 1 /**
 2  * 创建线程方式--使用FutureTask方式 Page21
 3  * @author JustJavaIt
 4  */
 5 public class FutureTasktest {
 6     public static void main(String[] args) {
 7         CallerTask callerTask = new CallerTask();
 8         //创建异步任务
 9         FutureTask<String> futureTask = new FutureTask<>(callerTask);
10         //启动线程
11         new Thread(futureTask).start();
12         String result;
13         try {
14             //等待任务执行完毕,并返回结果。
15             result = futureTask.get();
16             System.out.println("result:"+ result);
17         } catch (InterruptedException e) {
18             e.printStackTrace();
19         } catch (ExecutionException e) {
20             e.printStackTrace();
21         }
22     }
23 
24     /**
25      * 创建任务类,类似Runable
26      */
27     public static class CallerTask implements Callable<String>{
28 
29         @Override
30         public String call() throws Exception {
31             return "hello";
32         }
33     }
34 }
View Code

运行结果如下:

  如上代码中的CallerTask类实现了Callable接口的call()方法。在main函数内首先创建了一个FutrueTask对象(构造函数为CallerTask的实例),然后使用创建的FutrueTask对象作为任务创建了一个线程并且启动它,最后通过futureTask.get()等待任务执行完毕并返回结果。

  小结:使用继承方式的好处是方便传参,你可以在子类里面添加成员变量,通过set方法设置参数或者通过构造函数进行传递,而如果使用Runnable方式,则只能使用主线程里面被声明为final的变量。不好的地方是Java不支持多继承,如果继承了Thread类, 那么子类不能再继承其他类,而Runable则没有这个限制。前两种方式都没办法拿到任务的返回结果,但是Futuretask方式可以

补充

FutureTask和Future示例

 FutureTask是Future和Callable的结合体和Callable的结合体

 1 /**
 2  * FutureTask示例
 3  * @author JustJavait
 4  * @date 2022/1/12 18:02
 5  */
 6 public class CallableFutureTask {
 7     public static void main(String[] args) {
 8         //第一种方式 线程池
 9         ExecutorService executor = Executors.newCachedThreadPool();
10         Task1 task1 = new Task1();
11         FutureTask<Integer> futureTask = new FutureTask<>(task1);
12         executor.submit(futureTask);
13         executor.shutdown();
14 
15         //第二种方式,注意这种方式和第一种方式效果是类似的,只不过一个使用的是ExecutorService,一个使用的是Thread
16        /* Task1 task1 = new Task1();
17         FutureTask<Integer> futureTask = new FutureTask<>(task1);
18         Thread thread = new Thread(futureTask);
19         thread.start();*/
20 
21         try {
22             Thread.sleep(1000);
23         } catch (InterruptedException e1) {
24             e1.printStackTrace();
25         }
26 
27         System.out.println("主线程在执行任务");
28 
29         try {
30             System.out.println("task运行结果" + futureTask.get());
31         } catch (InterruptedException e) {
32             e.printStackTrace();
33         } catch (ExecutionException e) {
34             e.printStackTrace();
35         }
36 
37         System.out.println("所有任务执行完毕");
38     }
39 }
40 
41 class Task1 implements Callable<Integer> {
42     @Override
43     public Integer call() throws Exception {
44         System.out.println("子线程在进行计算");
45         Thread.sleep(3000);
46         int sum = 0;
47         for (int i = 0; i < 100; i++) {
48             sum += i;
49         }
50         return sum;
51     }
52 }
View Code
 1 /**
 2  * Future示例
 3  * @author JustJavait
 4  * @date 2022/1/12 17:55
 5  */
 6 public class CallableFuture {
 7     public static void main(String[] args) {
 8         ExecutorService executor = Executors.newCachedThreadPool();
 9         Task task = new Task();
10         Future<Integer> result = executor.submit(task);
11         executor.shutdown();
12 
13         try {
14             Thread.sleep(1000);
15         } catch (InterruptedException e1) {
16             e1.printStackTrace();
17         }
18 
19         System.out.println("主线程在执行任务");
20 
21         try {
22             System.out.println("task运行结果" + result.get());
23         } catch (InterruptedException e) {
24             e.printStackTrace();
25         } catch (ExecutionException e) {
26             e.printStackTrace();
27         }
28 
29         System.out.println("所有任务执行完毕");
30     }
31 }
32 
33 class Task implements Callable<Integer> {
34     @Override
35     public Integer call() throws Exception {
36         System.out.println("子线程在进行计算");
37         Thread.sleep(3000);
38         int sum = 0;
39         for (int i = 0; i < 100; i++) {
40             sum += i;
41         }
42         return sum;
43     }
44 }
View Code

运行结果都为:

 三种方式的区别?

Java中,类仅支持单继承,如果一个类继承了Thread类,就无法再继承其它类,因此,如果一个类既要继承其它的类,又必须创建为一个线程,就可以使用实现Runable接口的方式。

使用实现Runable接口的方式创建的线程可以处理同一资源,实现资源的共享。

使用实现Callable接口的方式创建的线程,可以获取到线程执行的返回值、是否执行完成等信息。

Java中Runnable和Callable有什么不同?

Runnable和Callable都是创建线程的方式。Runnable从JDK1.0开始就有了,Callable是在JDK1.5增加的。

(1)实现Callable接口的任务线程能返回执行结果;而实现Runnable接口的任务线程不能返回结果;

(2)Callable接口的call()方法允许抛出异常;而Runnable接口的run()方法的异常只能在内部消化,不能继续上抛;

 <END>

⭐️希望本文章对您有帮助,您的 转发、点赞 是我创作的无限动力。

扫描下方二维码关注微信公众号,您会收到更多优质文章推送。

posted @ 2022-02-13 17:47  JustJavaIt  阅读(1127)  评论(0编辑  收藏  举报