三种线程创建方式
- 继承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++;}@Overridepublic 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++;}@Overridepublic 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;}@Overridepublic 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 {@Overridepublic 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<Integer> future=new FutureTask<Integer>((Callable<Integer>)()->{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创建完本地操作系统级线程后回调的方法,不可以手动调用(否则就是普通方法)