进程:进行中的程序,执行中的程序。在使用电脑时,我们通常会打开多个应用程序,如浏览器,网易云音乐等,而这些打开着的应用程序就称之为进程。
线程:一个进程可以同时执行多个任务,如一个视频播放器可以同时缓存多集电视剧(VIP用户),一个浏览器可以同时下载多张图片等,通常我们将这一个个任务称为线程。
Java中Thread类是线程操作的核心类。而通过下图我们可以发现Thread类其实是Runnable接口的实现类,即Thread类实现了Runnable接口的抽象方法->run()方法。
Runnable接口:
Thread类是Runnable接口的实现类:
Thread类覆写了Runnable接口的方法:
那么我们自然而然可以想到第一种创建线程的方法就是直接继承Thread类,然后通过覆写父类,即Thread类的run方法方法来实现自己的功能。
但是直接调用run方法是否可行呢?我们先通过一个简单的例子来看一下:
public class MyThread extends Thread {//直接继承Thread类创建线程
private String title;
public MyThread(String title) {
this.title = title;
}
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(this.title + ",i = " + i);
}
}
public static void main(String[] args) {
MyThread myThread1 = new MyThread("myThread1");
MyThread myThread2 = new MyThread("myThread2");
MyThread myThread3 = new MyThread("myThread3");
myThread1.run();
myThread2.run();
myThread3.run();
}
}
附上上面程序执行的结果:
通过观察该运行结果我们发现,此时只是按照这三个线程调用run方法的先后次序顺序打印了结果而已。而我们知道多线程是异步执行,并发执行,所以我们不能直接通过run方法启动线程。那么我们该通过什么方法启动线程呢-> start()方法
public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0)
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
通过观察start()方法的源码我们可以看到在threadStatus为0的情况下,start()调用了start0()方法,并将started变量从false置为了true,标志线程已经被启动。那么start0()又是什么方法呢?
private native void start0();
start0()是一个被native 修饰的本地方法,需要依赖平台实现。而Thread类中有一个静态代码块,此代码块中存放了一个方法->registerNatives()方法,该方法主要就是注册一些本地方法供Thread类使用。其中就包括start0()方法,而start0()又会调用JVM_StartThread方法,该方法则会调用run方法。
public
class Thread implements Runnable {
/* Make sure registerNatives is the first thing <clinit> does. */
private static native void registerNatives();
static {
registerNatives();
}
所以,我们可以得出的结论是,start()方法中虽然没有run方法,但是它会间接调用run方法:start() -> start0() -> JVM_StartThread -> run()。所以,我们可以通过覆写run方法来实现我们想要的功能,并调用start方法启动线程去执行。
第一种创建线程的方式:直接继承Thread类
步骤:
- 创建一个子类(该子类直接继承Thread类),该子类通过覆写Thread类的run()方法来实现自己的逻辑操作。
- 实例化这个子类
- 通过这个子类的实例化对象调用start()方法启动线程
public class MyThread extends Thread {
private String title;
public MyThread(String title) {
this.title = title;
}
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(this.title + ",i = " + i);
}
}
public static void main(String[] args) {
MyThread myThread1 = new MyThread("myThread1");
MyThread myThread2 = new MyThread("myThread2");
MyThread myThread3 = new MyThread("myThread3");
myThread1.start();
myThread2.start();
myThread3.start();
}
}
该程序某一次运行结果(交替执行)如下:
虽然直接继承Thread类创建线程很简单方便,但是我们知道Java只允许单继承,而不允许多继承,即一个类只能继承一个父类,而不允许同时继承多个父类。所以
1. 如果只为了创建线程而直接继承Thread类的话,该类就不能再继承并接收其他类的功能了。
2. 如果一个类已经继承了别的类,那么就不能再继承Thread类了,即通过第一种方式创建线程便不可行了。
为了解决直接继承Thread类的单继承局限,看来我们得另寻他法了。而通过上面第一张图我们可以知道Runnable接口里有抽象的run()方法,再结合Thread类有一个形式参数为Runnable类型的构造方法(如下图所示),那么我们是不是可以通过将实现了Runnable接口的类的实例化对象作为参数传给Thread类的构造方法呢?答案是可以的。
第二种创建线程的方式:通过实现Runnable接口
步骤:
- 创建一个Runnable接口的实现类,通过实现Runnable接口的抽象方法来实现自己的功能。
- 实例化这个实现类
- 将这个实现类的实例化对象作为target传入如上图所示的Thread类的构造方法中,并调用start()方法启动线程
public class MyThread implements Runnable {
private String title;
public MyThread(String title) {
this.title = title;
}
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(this.title + ",i = " + i);
}
}
public static void main(String[] args) {
Runnable myThread1=new MyThread("myThread1");
Runnable myThread2=new MyThread("myThread2");
Runnable myThread3=new MyThread("myThread3");
new Thread(myThread1).start();
new Thread(myThread2).start();
new Thread(myThread3).start();
}
}
该程序某一次运行结果(交替执行)如下:
此时通过实现Runnable接口来创建线程虽然解决了单继承局限,但是我们发现通过此方法创建并执行线程run()方法并没有返回任何信息,即没有返回值。但在有些场景下我们是需要知道线程的执行情况的,那么有没有什么方法可以取得线程的执行结果呢?答案是有的。
此时引入另外一个接口:Callable接口
通过上图我们可以知道Callable接口是一个泛型接口,即此时我们并不知道需要返回什么类型的值,只有通过子类实现此接口的时候指明返回值类型。所以目前为止我们可以利用Callable接口的call()方法来取得返回值。但是由于Thread类的构造方法只接收Runnable接口的实现类的实例化对象,所以我们还得找一个既能接收Callable接口的实现类的实例化对象,又实现了Runnable接口的类-->FutureTask类
查看源码我们可以发现FutureTask是RunnableFuture接口的实现类,而RunnableFuture接口又同时实现了Runnable接口和Future接口。
RunnableFuture接口同时实现了Runnable接口和Future接口:
FutureTask实现了RunnableFuture接口:
再次查看FutureTask类的源码,我们可以发现如下构造方法:
通过上图我们发现FutureTask类的构造方法可以接收Callable接口实现类的实例化对象,而FutureTask类同时又实现了Runnable接口,所以我们可以通过此种方式来创建线程并取得返回结果。但是到目前为止我们好像并不知道如何取得返回结果,所以接下来就来介绍一下Future接口中的5个抽象方法(如下图所示):
public interface Future<V> {
boolean cancel(boolean mayInterruptIfRunning);
boolean isCancelled();
boolean isDone();
V get() throws InterruptedException, ExecutionException;
V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
}
1. cancel方法:用来取消任务,如果任务取消成功则返回true,否则返回false。参数mayInterruptIfRunning表示是否允许取消正在执行的任务,如果为true,表示允许取消正在执行的任务,否则表示不允许。由于此参数针对的是是否取消正在执行的任务,所以如果一个任务已经执行完毕了,那么无论参数mayInterruptIfRunning置为true还是false,cancel方法都会返回false,因为任务已经完成了,无法执行取消任务操作。如果任务处于执行状态,那么当参数mayInterruptIfRunning置为true时cancel方法返回true,否则cancel方法返回false。
2. isCancelled方法:表示任务是否被取消成功,如果任务在正常执行期间被取消成功则返回true。
3. isDone方法:表示任务是否已经执行完毕,如果已经执行完毕则返回true。
4. get方法:用来获取任务的执行结果,该方法会一直阻塞,等到任务执行完毕才返回结果。
5. get(long timeout,TimeUnit unit)方法: 用来获取任务的执行结果,但是如果阻塞到timeout时间任务还没有完成就抛出TimeoutException()异常。
介绍完Future接口里的5个抽象方法我们可以知道Future接口针对Callable或Runnable任务的执行可以实现如下功能:
- 是否取消相应任务
- 任务是否被取消成功
- 查询任务是否执行完成
- 返回任务执行结果
那么到这里对于如何获取Callable任务的执行结果的疑惑就可以解答了-->调用get方法。
总结一下为什么使用FutureTask类的实例化对象作为Thread类构造方法的target:
首先,Callable接口中的抽象方法call方法执行后有返回值,而FutureTask类的构造方法中恰好有可以接收Callable接口实现类的实例化对象的。
其次,为了取得Callable任务的执行结果,我们需要用到Future接口的抽象方法get方法。但是此时并不能结束,因为Thread类的构造方法中只接收Runnable接口的实现类的实例化对象。
最后,由于FutureTask类实现了RunnableFuture接口,而RunnableFuture接口又同时实现了Runnable接口和Future接口,所以通过实例化FutureTask类,并将其实例化对象作为Thread类构造方法的target参数,调用start()方法启动线程后最后通过get方法便可以得到线程的执行结果。
第三种创建线程的方式:实现Callable接口
步骤:
- 创建一个Callable接口的实现类(指明返回结果的类型),并覆写Callable接口的call方法
- 实例化Callable接口的实现类,并将其作为FutureTask类的构造方法的参数实例化FutureTask类
- 将FutureTask类的实例化对象作为Thread类中构造方法的target,实例化Thread类,同时调用start()方法启动线程
- 通过调用FutureTask类的实例化对象的get方法获取线程执行的结果
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class MyThread implements Callable<String> {
private String title;
public MyThread(String title) {
this.title = title;
}
public String call() {
for (int i = 0; i < 5; i++) {
System.out.println(this.title + ",i = " + i);
}
return this.title+"执行完毕";
}
public static void main(String[] args) {
Callable<String> myThread1=new MyThread("myThread1");
Callable<String> myThread2=new MyThread("myThread2");
Callable<String> myThread3=new MyThread("myThread3");
FutureTask<String> task1=new FutureTask<>(myThread1);
FutureTask<String> task2=new FutureTask<>(myThread2);
FutureTask<String> task3=new FutureTask<>(myThread3);
new Thread(task1).start();
new Thread(task2).start();
new Thread(task3).start();
try {
System.out.println(task1.get());
System.out.println(task2.get());
System.out.println(task3.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
该程序某一次运行结果(交替执行)如下: