Android---多线程:继承Thread 类和实现Runnable 接口的区别

时间:2020-11-27 17:34:58

参考:

Java线程中继承thread类与实现Runnable接口的区别
Android性能优化之使用线程池处理异步任务

Java中线程的创建有两种方式:

1、通过继承Thread类,重写Thread的run()方法,将线程运行的逻辑放在其中。多个线程分别完成自己的任务。
2、通过实现Runnable接口,实例化Thread类。多个线程共同完成一个任务。

有经验的程序员都会选择实现Runnable接口 ,其主要原因有以下两点:
1、java只能单继承,因此如果是采用继承Thread的方法,那么在以后进行代码重构的时候可能会遇到问题,因为你无法继承别的类了。
2、如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。

继承Thread 类和实现Runnable 接口主要区别在于:
在多线程访问同一资源的情况下,用Runnable接口创建的线程可以处理同一资源,而用Thread类创建的线程则各自独立处理,各自拥有自己的资源。

所以,在Java中大多数多线程程序都是通过实现Runnable来完成的,而对于Android来说也不例外,当涉及到需要开启线程去完成某件事时,我们都会这样写:

new Thread(new Runnable() {
@Override
public void run() {
//do sth .
}
}).start();

这段代码创建了一个线程并执行,它在任务结束后GC会自动回收该线程,一切看起来如此美妙,是的,它在线程并发不多的程序中确实不错,而假如这个程序有很多地方需要开启大量线程来处理任务,那么如果还是用上述的方式去创建线程处理的话,那么将导致系统的性能表现的非常糟糕,更别说在内存有限的移动设备上,

实现Runnable 接口主要的影响如下:

1、线程的创建和销毁都需要时间,当有大量的线程创建和销毁时,那么这些时间的消耗则比较明显,将导致性能上的缺失

2、大量的线程创建、执行和销毁是非常耗cpu和内存的,这样将直接影响系统的吞吐量,导致性能急剧下降,如果内存资源占用的比较多,还很可能造成OOM

3、大量的线程的创建和销毁很容易导致GC频繁的执行,从而发生内存抖动现象,而发生了内存抖动,对于移动端来说,最大的影响就是造成界面卡顿

而针对上述所描述的问题,解决的办法归根到底就是:重用已有的线程,从而减少线程的创建。

所以这就涉及到线程池(ExecutorService)的概念了,线程池的基本作用就是进行线程的复用。

使用线程池管理线程的优点:

1、线程的创建和销毁由线程池维护,一个线程在完成任务后并不会立即销毁,而是由后续的任务复用这个线程,从而减少线程的创建和销毁,节约系统的开销

2、线程池旨在线程的复用,这就可以节约我们用以往的方式创建线程和销毁所消耗的时间,减少线程频繁调度的开销,从而节约系统资源,提高系统吞吐量

3、在执行大量异步任务时提高了性能

4、Java内置的一套ExecutorService线程池相关的api,可以更方便的控制线程的最大并发数、线程的定时任务、单线程的顺序执行等

1、继承Thread

/**
* Created by Administrator on 2017/6/16.
*/

public class MyThread extends Thread {
private int ticket = 10;
private String name;

public MyThread(String name) {
this.name = name;
}

public void run() {
for (int i = 0; i < 500; i++) {
if (this.ticket > 0) {
System.out.println(this.name + "卖票---->" + (this.ticket--));
}
}
}

}
/**
* Created by Administrator on 2017/6/16.
*/

public class ThreadDemo {

public static void main(String[] args) {
MyThread mt1 = new MyThread("一号窗口");
MyThread mt2 = new MyThread("二号窗口");
MyThread mt3 = new MyThread("三号窗口");
mt1.start();
mt2.start();
mt3.start();
}

}

运行结果:

一号窗口卖票---->10
一号窗口卖票---->9
一号窗口卖票---->8
一号窗口卖票---->7
三号窗口卖票---->10
三号窗口卖票---->9
三号窗口卖票---->8
三号窗口卖票---->7
三号窗口卖票---->6
三号窗口卖票---->5
三号窗口卖票---->4
三号窗口卖票---->3
三号窗口卖票---->2
三号窗口卖票---->1
一号窗口卖票---->6
一号窗口卖票---->5
一号窗口卖票---->4
一号窗口卖票---->3
一号窗口卖票---->2
一号窗口卖票---->1
二号窗口卖票---->10
二号窗口卖票---->9
二号窗口卖票---->8
二号窗口卖票---->7
二号窗口卖票---->6
二号窗口卖票---->5
二号窗口卖票---->4
二号窗口卖票---->3
二号窗口卖票---->2
二号窗口卖票---->1

2、实现Runnable接口

/**
* Created by Administrator on 2017/6/16.
*/

public class MyRunnable implements Runnable {
private int ticket = 10;
private String name;

public void run() {
for (int i = 0; i < 500; i++) {
if (this.ticket > 0) {
System.out.println(Thread.currentThread().getName() + "卖票---->" + (this.ticket--));
}
}
}

}
/**
* Created by Administrator on 2017/6/16.
*/

public class RunnableDemo {

public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();

Thread t1 = new Thread(myRunnable, "一号窗口");
Thread t2 = new Thread(myRunnable, "二号窗口");
Thread t3 = new Thread(myRunnable, "三号窗口");
t1.start();
t2.start();
t3.start();
}
}

运行结果:

一号窗口卖票---->10
一号窗口卖票---->8
二号窗口卖票---->9
一号窗口卖票---->7
二号窗口卖票---->6
一号窗口卖票---->5
二号窗口卖票---->4
二号窗口卖票---->2
二号窗口卖票---->1
一号窗口卖票---->3

为什么会出现这种结果呐。我们不妨做个比喻,其实刚的程序,

继承Thread类的,我们相当于拿出三件事即三个卖票10张的任务分别分给三个窗口,他们各做各的事各卖各的票各完成各的任务,因为MyThread继承Thread类,所以在new MyThread的时候在创建三个对象的同时创建了三个线程;

实现Runnable的, 相当于是拿出一个卖票10张得任务给三个人去共同完成,new MyThread相当于创建一个任务,然后实例化三个Thread,创建三个线程即安排三个窗口去执行。

用图表示如下:

Android---多线程:继承Thread 类和实现Runnable 接口的区别

在我们刚接触的时候可能会迷糊继承Thread类和实现Runnable接口实现多线程,其实在接触后我们会发现这完全是两个不同的实现多线程,一个是多个线程分别完成自己的任务,一个是多个线程共同完成一个任务。

其实在实现一个任务用多个线程来做也可以用继承Thread类来实现只是比较麻烦,一般我们用实现Runnable接口来实现,简洁明了。

大多数情况下,如果只想重写 run() 方法,而不重写其他 Thread 方法,那么应使用 Runnable 接口。这很重要,因为除非程序员打算修改或增强类的基本行为,否则不应为该类(Thread)创建子类。