实现线程的正确方法:2种
- 实现Runnable接口创建线程
/**
* 实现Runnable接口创建线程
*/
public class MyThread implements Runnable {
@Override
public void run() {
System.out.println("实现Runnable接口创建线程");
}
public static void main(String[] args) {
new Thread(new MyThread()).start();
}
}
- 继承Thread类创建线程
/**
* 继承Thread类创建线程
*/
public class MyThread extends Thread {
@Override
public void run() {
System.out.println("继承Thread类创建线程");
}
public static void main(String[] args) {
new Thread(new MyThread()).start();
}
}
实现Runnable接口创建线程的方法更好
- 解耦:具体执行的任务,也就是run方法应该与线程的创建、运行、销毁是不相关的;
- 资源节约:如果用继承Thread这种方法,每次新建个任务只能新建个线程,线程的创建、运行和销毁的消耗是比较大的;如果使用实现Runnable接口的方法,后续我们可以使用线程池等工具,不用再去新建线程,带来的损耗会大大降低;
- 扩展性:Java是不支持多继承的,如果采用继承Thread类的方法,那么子类就无法再去继承其他类,大大的限制了子类的扩展性
两种方法的本质对比
让我们看看Thread类里面的run()是怎么写的:
@Override
public void run() {
if (target != null) {
target.run();
}
}
- 代码非常的简单,意思是有Runnable类型的target对象,那么就执行target对象里面的run();
- 使用实现Runnable接口方法时,因为传入了Runnable对象,所以新建线程对象里面执行了target.run();
- 使用继承Thread类方法时,因为子类重写了Thread类的run(),所以最终执行子类的run();
同时使用两种方法会怎么样?
public class MyThread {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("实现Runnable接口创建线程");
}
}) {
@Override
public void run() {
System.out.println("继承Thread类创建线程");
}
}.start();
}
}
继承Thread类创建线程
上述代码虽然传入了Runnable对象,但是子类重写了Thread类的run(),导致该方法不再调用target.run(),所以同时使用两种方法,只有继承Thread类方法才生效!
最精准的描述
- 通常可以分为两类:实现Runnable接口、继承Thread类重写run()
- 准确的讲,创建线程只有一种方式:创建Thread对象,而在Thread类里面,实现run()有两种不同的情况:
- 实现Runnable接口,并把实例对象传给Thread类去执行run();
- 继承Thread类,直接重写了Thread类的run();
典型的错误观点
- “线程池创建线程”
public class MyThread {
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 100; i++) {
executorService.submit(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
}
});
}
}
}
pool-1-thread-14
pool-1-thread-9
pool-1-thread-8
pool-1-thread-13
pool-1-thread-15
pool-1-thread-16
......
结果表明,线程池确实创建了新的线程,但是这只是表现,其内部是通过实现ThreadFactory接口来创建线程的,如下所示:
public interface ThreadFactory {
/**
* Constructs a new {@code Thread}. Implementations may also initialize
* priority, name, daemon status, {@code ThreadGroup}, etc.
*
* @param r a runnable to be executed by new thread instance
* @return constructed thread, or {@code null} if the request to
* create a thread is rejected
*/
Thread newThread(Runnable r);
}
- “通过Callable和FutureTask创建线程,也算一种新的创建线程的方式”
public class CallableAndFutureTask {
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask futureTask = new FutureTask(new Callable() {
@Override
public Object call() throws Exception {
return Thread.currentThread().getName();
}
});
new Thread(futureTask).start();
System.out.println(futureTask.get());
}
}
Thread-0
实现Callable接口并创建FutureTask对象的方法确实也可以创建线程,但是和线程池一样,这只是对创建Thread对象的一种包装,如下图所示:
public class FutureTask<V> implements RunnableFuture<V>
public interface RunnableFuture<V> extends Runnable, Future<V> {
/**
* Sets this Future to the result of its computation
* unless it has been cancelled.
*/
void run();
}
FutureTask实现了RunnableFuture接口,而RunnableFuture继承与Runnable接口,根据这种传递关系,相当于FutureTask实现了Runnable接口,在实现的run()方法中调用了Callable接口的call方法来获取返回值,并把返回值set到result中,最后通过get()获取到返回值,如下图所示:
public void run() {
.......
Callable<V> c = callable;
result = c.call();
ran = true;
if (ran) set(result);
}
......
}
以上两种说法,只是对于实现Runnable接口和继承Thread类方法的封装,本质上不算新的方法!