【Java面试题】线程创建的三种方式及区别? 您所在的位置:网站首页 创建线程的两种方法优缺点是什么 【Java面试题】线程创建的三种方式及区别?

【Java面试题】线程创建的三种方式及区别?

2024-07-08 20:43| 来源: 网络整理| 查看: 265

三种线程创建方式 继承Thread类,子类重写run()方法,调用子类的strat()启动线程。实现Runnable接口,实现run()方法,调用对象start()启动线程。实现Callable接口,实现call()方法,用FutureTask()封装实现类。使用FutureTask对象作为Thread对象调用start()启动线程,调用FutureTask对象的get()方法获取返回值()。 三种方式的优缺点 

采用继承Thread类方式:

优点:编写简单。缺点:因为线程类已经继承了Thread类,所以不能再继承其他的父类。

采用实现Runnable接口方式:

优点:线程类只是实现了Runable接口,还可以继承其他的类。缺点:编程稍微复杂,如果需要访问当前线程,必须使用Thread.currentThread()方法。

Runnable和Callable的区别:

Callable规定的方法是call(),Runnable规定的方法是run()。Callable的任务执行后可返回值,而Runnable的任务是不能返回值得。Call方法可以抛出异常,run方法不可以,因为run方法本身没有抛出异常,所以自定义的线程类在重写run的时候也无法抛出异常运行Callable任务可以拿到一个Future对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果。

总结:Runnable和Callable功能一样的,都是构造线程执行的任务;其区别可以简单理解为有无返回值的区别,通常Callable使用的比较多

继承Thread类

继承Thread类的话,必须重写run方法,在run方法中定义需要执行的任务。

class MyThread extends Thread{ private static int num = 0; public MyThread(){ num++; } @Override public void run() { System.out.println("主动创建的第"+num+"个线程"); } }

创建好了自己的线程类之后,就可以创建线程对象了,然后通过start()方法去启动线程。注意,不是调用run()方法启动线程,run方法中只是定义需要执行的任务,如果调用run方法,即相当于在主线程中执行run方法,跟普通的方法调用没有任何区别,此时并不会创建一个新的线程来执行定义的任务。

public class Test { public static void main(String[] args) { MyThread thread = new MyThread(); thread.start(); } } class MyThread extends Thread{ private static int num = 0; public MyThread(){ num++; } @Override public void run() { System.out.println("主动创建的第"+num+"个线程"); } }

在上面代码中,通过调用start()方法,就会创建一个新的线程了。为了分清start()方法调用和run()方法调用的区别,请看下面一个例子:

public class Test { public static void main(String[] args) { System.out.println("主线程ID:"+Thread.currentThread().getId()); MyThread thread1 = new MyThread("thread1"); thread1.start(); MyThread thread2 = new MyThread("thread2"); thread2.run(); } } class MyThread extends Thread{ private String name; public MyThread(String name){ this.name = name; } @Override public void run() { System.out.println("name:"+name+" 子线程ID:"+Thread.currentThread().getId()); } }

运行结果:

主线程ID:1 name:thread2 子线程ID:1 name:thread1 子线程ID:8

从输出结果可以得出以下结论:

1)thread1和thread2的线程ID不同,thread2和主线程ID相同,说明通过run方法调用并不会创建新的线程,而是在主线程中直接运行run方法,跟普通的方法调用没有任何区别;

2)虽然thread1的start方法调用在thread2的run方法前面调用,但是先输出的是thread2的run方法调用的相关信息,说明新线程创建的过程不会阻塞主线程的后续执行。

2.实现Runnable接口  定义runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。 创建 Runnable实现类的实例,并依此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。 调用线程对象的start()方法来启动该线程。 package Thread; import java.util.concurrent.*; //测试类 public class TestThread { public static void main(String[] args) throws Exception { testImplents(); } public static void testImplents() throws Exception { MyThreadImplements myThreadImplements = new MyThreadImplements(); Thread t1 = new Thread(myThreadImplements); Thread t2 = new Thread(myThreadImplements, "my thread -2"); t1.start(); t2.start(); } } //线程类 class MyThreadImplements implements Runnable { @Override public void run() { System.out.println("通过实现Runable,线程号:" + Thread.currentThread().getName()); } } 3. 使用Callable接口

和Runnable接口不一样,Callable接口提供了一个call()方法作为线程执行体,call()方法比run()方法功能要强大。

创建并启动有返回值的线程的步骤如下:

创建Callable接口的实现类,并实现call()方法,然后创建该实现类的实例(从java8开始可以直接使用Lambda表达式创建Callable对象)。使用FutureTask类来包装Callable对象,该FutureTask对象封装了Callable对象的call()方法的返回值使用FutureTask对象作为Thread对象的target创建并启动线程(因为FutureTask实现了Runnable接口)调用FutureTask对象的get()方法来获得子线程执行结束后的返回值

下面是一个例子:

public class Main {   public static void main(String[] args){    MyThread3 th=new MyThread3();    //使用Lambda表达式创建Callable对象    //使用FutureTask类来包装Callable对象    FutureTask future=new FutureTask(     (Callable)()->{       return 5;     }    );    new Thread(task,"有返回值的线程").start();//实质上还是以Callable对象来创建并启动线程    try{     System.out.println("子线程的返回值:"+future.get());//get()方法会阻塞,直到子线程执行结束才返回    }catch(Exception e){     ex.printStackTrace();    }   } }

start()和run()的区别?

(具体区别可以看【Java面试题】线程中start方法和run方法的区别?)

start()方法用来,开启线程,但是线程开启后并没有立即执行,他需要获取cpu的执行权才可以执行run()方法是由jvm创建完本地操作系统级线程后回调的方法,不可以手动调用(否则就是普通方法)


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有