1.继承Thread类
一个类继承Thread类,重写run()方法,调用Thread类中的start()方法启动线程。
2.实现Runnable类
自定义类实现Runnable接口,重写run()方法;用实现Runnable接口的对象作为参数实例化一个Thread对象,调用Thread类的start()方法来启动线程
实现Runnable接口比继承Thread类所具有的优势:
1. 适合多个相同的程序代码的线程去共享同一个资源。 2. 可以避免java中的单继承的局限性。 3. 增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立。 4. 线程池只能放入实现Runable或Callable类线程,不能直接放入继承Thread的类。
注意:
Thread类本质上也是实现了Runnable接口,启动线程的唯一方法就是通过Thread类的start()方法,run方法只是一个方法体。
start()方法是一个native(本地)方法,它将启动一个新线程,并执行run()方法(Thread类中提供的run()方法是一个空方法)。
一个Native Method就是一个Java调用非Java代码的接口。我们把这类接口称为JNI(Java Native Interface),它提供了若干的API实现了Java和其他语言的通信(主要是C&C++),它允许Java代码和其他语言写的代码进行交互,这里主要是与操作系统交互。
在JAVA程序执行的过程之中考虑到对于不同层次开发者的需求,所以其支持有本地的操作系统函数调用,而这项技术就被称为JNI(Java Native Inteface)技术,但是JAVA开发过程之中并不推荐这样使用,利用这项技术可以使用一些操作系统提供的底层函数进行一些特殊的处理,而在Thread类里面提供了start()就表示需要将此方法依赖于不同的操作系统实现,可以运行在不同的操作系统中。
因为线程的启动需要本地操作系统来调度,所以start方法要为native方法。
发现start()方法里面会抛出一个"IllegalThreadStateException()"异常类对象,但整个程序并没有进行try…catch处理,因为该异常一定是RuntimeException的子类,每一个线程类的对象只允许启动一次,如果重复启动就会抛出异常
多线程的执行顺序问题:
由图可以看出,输出的值是交替的无顺序的,原因分析:
调用start()方法后并不是立即执行多线程代码,而是使得该线程变为可运行状态,什么时候调度运行由操作系统决定的。
3.实现Callable接口
实现Callable接口,重写call()方法来实现一个线程。Callable接口是JDK1.5之后提出的一个新的线程实现接口juc(java.util.concurrent)包下的一个泛型接口,只有一个call()方法。
Callable接口与Runnable接口的功能类似,但提供了比Runnable更强大的功能,主要表现为以下三点:
(1)Callable可以在任务结束后提供一个返回值,Runnable无法提供这个功能;
(2)Callable接口中的call()方法可以抛出异常,而Runnable的run()方法不能抛出异常,必须在run()捕获;
(3)运行Callable可以拿到一个Future对象,Future对象表示异步计算的结果,它提供了检查计算是否完成的方法。由于线程属于异步计算模型,因此无法从别的线程中得到函数的返回值,在这种情况下,就可以使用Future来监视目标线程调用call()方法的情况,当调用Future的get方法以获取结果时,当前线程就会阻塞,直到call()方法结束返回结果。
实现了callable接口的两种使用方法
① 通过futuerTask包装,然后放进Thread,调用start()方法执行,启动一条线程去执行任务,返回一个futuer对象。
② callable放入线程池,返回一个futuer对象。callable也可以通过futuerTask包装,然后放进线程池。
4.通过线程池创建线程
总结:
前面两种可以归结为一类:无返回值,原因很简单,通过重写run方法,run方式的返回值是void,所以没有办法返回结果
后面两种可以归结成一类:有返回值,通过Callable接口,就要实现call方法,这个方法的返回值是Object,所以返回的结果可以放在Object对象中
参考链接: