作为初学Java的小白,个人认为在学习之余通过博客做知识的输出与总结是另一种很好的学习方式。Java线程是Java中的重头戏,因此特地写一系列专门的基础知识总结。如有偏颇,请不吝赐教!共同进步……
话不多说,咱们直入主题!
首先罗列出创建线程的三种方式:
- 通过继承Thread类,覆写run()方法创建线程类
- 通过实现Runnable接口,实现run()方法创建类,作为Thread的constructor的target参数
- 通过实现Callable接口(实现其方法call())及Future接口创建类,同样的作为Thread的constructor的target参数
下面一种一种来讲解:
1.通过直接继承Thread类创建线程类
public class Test {
public static void main(String[] args) {
MyThread mt = new MyThread("Mt-Thread");
mt.start();
//当然,还可以创建匿名类的Thread对象来创建线程实例
Thread t = new Thread("Anonymous-Thread") {
@Override
public void run() {
System.out.println(getName() + " entering run()");
try {
Thread.sleep(1000);
} catch(InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName() +" leaving run()");
}
};
}
}
class MyThread extends Thread {
@Override//加上这个annotation,避免犯低级错误
public void run() {
System.out.println(getName() +" entering run()");
//暂时不用理会下面的try...catch代码
try {
Thread.sleep(1000);
} catch(InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName() + " leaving thread");
}
}
通过上面的代码可知,通过继承Thread类来创建自定义线程类是很简单的。但是,也恰恰是因为继承,从而限制了自定义类的扩展性。
2.通过实现Runnable接口创建线程
public class Test {
public static void main(String[] args) {
RunThread rth = new RunThread();
//作为Thread的target
Thread rt = new Thread(rth);
rt.start();
//同样的,可以使用匿名类对象
Thread anoRt = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " entering ");
}
},"Anonymous-Thread");
anoRt.start();
}
}
class RunThread implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " entering run()");
try {
Thread.sleep(1000);
} catch(InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " leaving run()");
}
}
通过实现接口Runnable后将实例作为Thread的target参数创建对象,启动线程。在start()后,若该线程获取了执行机会,线程对象则会调用Runnable中的run()方法进行线程的操作。
3.通过实现Callable接口的call()方法及Future接口创建线程
public class Test {
public static void main(String[] args) {
//使用Lambda表达式(Java8新增的特性)来实现Callable接口的call()方法(也就是说,Callable接口是一个函数式接口)
FutureTask<Integer> task = new FutureTask<Integer>((Callable<Integer>)() -> {
int num = 10;
System.out.println(Thread.currentThread().getName() + " --- " + "entering Lambda Function and output number : " + num);
//Callable的call()方法是允许有返回值的
return num;
});
//启动线程
Thread fTh = new Thread(task,"fTh");
fTh.start();
//取得call()方法返回的值
try {
System.out.println("Get the return value of call() : " + task.get());
} catch(Exception e) {
e.printStackTrace();
}
}
}
对于这种创建线程的方式,除了能获取返回值外,与实现Runnable接口并没有多大差别。
下面对三种创建线程的方式进行比较分析
其实三类可以归为两类来说:即通过继承Thread类及通过实现接口(Runnable接口或者Callable接口和Future接口)来创建线程
通过继承Thread创建线程
优点:编写代码简单,获取当前线程对象方便。只需要使用this即可获取当前线程对象
缺点:由于继承了Thread类,所以不能再继承其他的父类
通过实现接口创建线程
优点:仅仅是实现了Runnable或Callable接口,还可以继承其他类;另外,在这种方式下,非常适合多线程处理同一个任务或同一份资源的情况(将实现了Runnable接口或Callable接口的类实例看作是一个任务或者一份资源)。
缺点:如若需要访问当前线程,需要使用静态类方法Thread.currentThread()
总结:创建线程时更推荐用实现接口的方式