三种线程创建的方式
本篇内容为线程专题 -- 线程的创建,状态,工作过程中的线程创建方式的部分内容。
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 }
运行结果如下:
如上代码中的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 }
运行结果如下:
如上代码所示,两个线程共用一个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 }
运行结果如下:
如上代码中的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 }
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 }
运行结果都为:
三种方式的区别?
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>
⭐️希望本文章对您有帮助,您的「 转发、点赞 」是我创作的无限动力。
扫描下方二维码关注微信公众号,您会收到更多优质文章推送。