java多线程Thread类与Runnable 接口使用哪个更好

最近看到一个问题:

java多线程Thread类与Runnable 接口使用哪个更好?

 

作为一个面试题,他需要有答案,如下

实现Runnable 接口比继承Thread 类的方式更好:

(1)可以避免由于Java单继承带来的局限性;

(2)可以实现业务执行逻辑和数据资源的分离;

(3)可以与线程池配合使用,从而管理线程的生命周期;、

 

1、避免由于Java单继承带来的局限性
如果异步逻辑所在类已经继承了一个基类,就没有办法再继承Thread类。比如,当一个Dog类继承了Pet类,再要继承Thread类就不行了。所以在已经存在继承关系的情况下,只能使用实现Runnable接口的方式。

1 public class ThreadTask extends Thread {
2     // 线程的执行体
3     @Override
4     public void run() {
5         System.out.println("线程执行的任务");
6     }
7 }
1 public class RunnableTask implements Runnable {
2     // 线程的执行体
3     @Override
4     public void run() {
5         System.out.println("线程执行的任务");
6     }
7 }

 

2、可以实现业务执行逻辑和数据资源的分离,更方便的实现数据共享

逻辑和数据更好分离。通过实现Runnable接口的方法创建多线程更加适合同一个资源被多段业务逻辑并行处理的场景。在同一个资源被多个线程逻辑异步、并行处理的场景中,通过实现Runnable接口的方式设计多个target执行目标类可以更加方便、清晰地将执行逻辑和数据存储分离,更好地体现了面向对象的设计思想。

注意:并不是继承Thread类不能实现资源共享,而是没有实现Runnable接口更方便,更清晰,他们两的主要区别就是类和接口的区别,但是我们一般多用Runnable。

(1) 通过继承Thread类的方式实现多线程,数据资源和业务执行逻辑是耦合在一起的, 多个线程并发地完成各自的任务,访问各自的数据资源,而不是共享一份数据资源:

 1 public class ThreadDemo extends Thread {
 2     
 3     // 数据资源
 4     private int ticket = 3;
 5 
 6     // 业务执行逻辑
 7     @Override
 8     public void run() {
 9         for(int i=0;i<3;i++){
10             if(ticket>0){
11                 System.out.println(Thread.currentThread().getName()+" 卖票--->"+ ticket--);
12                 try {
13                     Thread.sleep(1000);
14                 } catch (InterruptedException e) {
15                     e.printStackTrace();
16                 }
17             }
18         }
19         System.out.println(Thread.currentThread().getName()+" 线程运行结束");
20     }
21 
22     public static void main(String[] args) throws InterruptedException {
23         // 创建2个线程,分别去执行线程体中的业务逻辑
24         Thread thread1 = new ThreadDemo();
25         thread1.start();
26         Thread thread2 = new ThreadDemo();
27         thread2.start();
28 
29         Thread.sleep(1000);
30         System.out.println("main线程运行结束");
31     }
32 }

多个线程并发地完成各自的任务,访问各自的数据资源:

1 Thread-0 卖票--->3
2 Thread-1 卖票--->3
3 main线程运行结束
4 Thread-0 卖票--->2
5 Thread-1 卖票--->2
6 Thread-1 卖票--->1
7 Thread-0 卖票--->1
8 Thread-0 线程运行结束
9 Thread-1 线程运行结束

(2) 通过继承Thread类可以实现资源共享:

 1 public class ThreadTask {
 2 
 3     private int ticket = 3;
 4 
 5     public synchronized void saleTicket(){
 6         for(int i=0;i<3;i++){
 7             if(ticket>0){
 8                 System.out.println(Thread.currentThread().getName()+" 卖票--->"+ticket--);
 9                 try {
10                     Thread.sleep(1000);
11                 } catch (InterruptedException e) {
12                     e.printStackTrace();
13                 }
14             }
15         }
16         System.out.println(Thread.currentThread().getName()+" 线程运行结束");
17     }
18 }
 1 public class ThreadA extends Thread {
 2 
 3     ThreadTask threadTask ;
 4 
 5     public ThreadA(ThreadTask threadTask){
 6         super();
 7         this.threadTask = threadTask;
 8     }
 9 
10     @Override
11     public void run() {
12         threadTask.saleTicket();
13     }
14 }
 1 public class ThreadB extends Thread {
 2     ThreadTask threadTask ;
 3 
 4     public ThreadB(ThreadTask threadTask){
 5         super();
 6         this.threadTask = threadTask;
 7     }
 8 
 9     @Override
10     public void run() {
11         threadTask.saleTicket();
12     }
13 }
1 public class Main {
2     public static void main(String[] args) throws InterruptedException {
3         ThreadTask threadTask = new ThreadTask();
4         ThreadA t1 = new ThreadA(threadTask);
5         ThreadB t2 = new ThreadB(threadTask);
6         t1.start();
7         t2.start();
8     }
9 }

执行结果

1 Thread-0 卖票--->3
2 Thread-1 卖票--->2
3 Thread-1 卖票--->1
4 Thread-0 线程运行结束
5 Thread-1 线程运行结束

(3) 通过实现Runnable接口实现多线程,能更好地做到多个线程并发地完成同一个任务,访问同一份数据资源。多个线程的代码逻辑可以方便地访问和处理同一个共享数据资源 ,这样可以将线程逻辑和业务数据进行有效的分离,更好地体现了面向对象的设计思想。

 1 public class RunnableDemo{
 2     public static class RunnableTask  implements Runnable{
 3         // 数据资源
 4         private int ticket = 3;
 5 
 6         // 线程执行体
 7         @Override
 8         public synchronized void run() {
 9             for(int i=0;i<3;i++){
10                 if(ticket>0){
11                     System.out.println(Thread.currentThread().getName()+" 卖票--->"+ticket--);
12                     try {
13                         Thread.sleep(1000);
14                     } catch (InterruptedException e) {
15                         e.printStackTrace();
16                     }
17                 }
18             }
19             System.out.println(Thread.currentThread().getName()+" 线程运行结束");
20         }
21     }
22 
23     public static void main(String[] args) {
24         // 将这一个target作为参数传给两个线程,那么这两个线程执行的都是这个target的run()方法
25         Runnable target = new RunnableTask();
26 
27         // 创建两个线程执行target的线程体
28         Thread thread1 = new Thread(target,"thread1");
29         thread1.start();
30         Thread thread2 = new Thread(target,"thread2");
31         thread2.start();
32         System.out.println("main线程运行结束");
33     }
34 }

多个线程并发地完成同一个任务,访问同一份数据资源:

1 main线程运行结束
2 thread1 卖票--->3
3 thread1 卖票--->2
4 thread1 卖票--->1
5 thread1 线程运行结束
6 thread2 线程运行结束

 

3、 可以与线程池配合使用,从而管理线程的生命周期
实现Runnable接口来实现线程,执行目标类,更容易和线程池配合使用,异步执行任务在大多数情况下是通过线程池去提交的,而很少通过创建一个新的线程去提交,所以更多的做法是,通过实现Runnable接口创建异步执行任务,而不是继承Thread去创建异步执行任务。

 

 

思考

参考:Java多线程,Thread和Runnable究竟该用哪个

 

抛开面试题来说,网上有人说,

“Runnable可以用来在多线程间共享对象,而Thread不能共享对象”,纯属无稽之谈,Thread本身就实现了Runnable,不会导致能不能共享对象这种区别。两者最大(甚至可以说唯一,因为其他差异对新人来说无关紧要)的区别是Thread是类而Runnable是接口,至于用类还是用接口,取决于继承上的实际需要。

最后回答标题的问题:Thread和Runnable究竟该用哪个?我的建议是都不用。因为Java这门语言发展到今天,在语言层面提供的多线程机制已经比较丰富且高级,完全不用在线程层面操作。直接使用Thread和Runnable这样的“裸线程”元素比较容易出错,还需要额外关注线程数等问题。建议:

  • 简单的多线程程序,使用Executor。
  • 简单的多线程,但不想关注线程层面因素,又熟悉Java8的:使用Java8的并行流,它底层基于ForkJoinPool,还能享受函数式编程的快捷。
  • 复杂的多线程程序,使用一个Actor库,首推Akka。

 

posted @ 2022-04-07 22:51  r1-12king  阅读(194)  评论(0编辑  收藏  举报