在最近的几次面试中都被问到了线程的获取方式。我都是把基本的方法回答了一下。即实现Runnable接口或者继承Thread 这两种方法。而几次的回答面试官好像都不是太满意。今天特意梳理一下这个问题,JAVA线程的获取方式。JAVA 的线程获取方式主要有三种方式:
1.实现Runnable 接口
2.继承Thread 类
3.通过线程池来获取线程(四种线程池)
第一种和第二种方式都比较简单,这里主要来分析一线程池的必要性。
理解线程池最主要的是要区分任务和线程这两个概念,线程是用来处理任务的。任务的创建是通过Runnable 接口来创建的,而线程则是通过实例化Thread 这个类的对象来创建的。我们是通过实现Runnable接口提交任务。看下面的例子:
import java.util.ArrayList;
class ThreadTest {
public static void main(String []args){
Task task=new Task("TaskA");
for(int i=0;i<5;i++){
Thread thread=new Thread(task);//创建一个线程
thread.start();//在线程thread里执行任务task
}
}
}
class Task implements Runnable{
String taskName;
Task(String taskName){
this.taskName=taskName;
}
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("这是一个任务!"+this.taskName);
}
}
类Task 实现了Runnable接口并且实现里run()方法,run()方法里是任务执行的具体过程,在本例中任务是打印字符串“这是一个任务+任务的名字”。在ThreadTest中,首先实例化一个名为TaskA 的任务。之后通过五次循环来创建并启动五个线程,注意此处,虽然创建了5个线程,但是五个线程却执行同一个任务即TaskA,因此输出结果为:
这是一个任务!TaskA
这是一个任务!TaskA
这是一个任务!TaskA
这是一个任务!TaskA
这是一个任务!TaskA
对上例的类ThreadTest进行改进如下:
class ThreadTest {
public static void main(String []args){
for(int i=0;i<5;i++){
Task task=new Task("Task-->"+i);//创建一个任务
Thread thread=new Thread(task);//创建一个线程
thread.start();//在线程thread里执行任务task
}
}
}
执行结果:
这是一个任务!Task0
这是一个任务!Task2
这是一个任务!Task3
这是一个任务!Task1
这是一个任务!Task4
这个例子的意思是:在for循环中,每创建一个任务即对应一个线程,然后用此线程执行该任务。通过执行结果可以看出五个线程执行了五个不同的任务,且这几个线程的执行顺序是不确定。现在问题来了,当任务非常多甚至达到成千上万的规模时,如果仍然采用这种方式,那么假如有一百万个任务,我就要创建对应的一百万个线程来执行这些任务。而每个线程的创建、销毁是非常耗费系统资源的,试想一下创建一百万个线程,系统肯定会内存溢出。那么现在该怎么处理这个事呢?对每个任务都创建一个对应的线程来执行它是不现实的。试想,可不可让一个线程去执行多个任务呢?这样的话,一个线程就对应多个任务。而我们所需要的创建的线程数就会成倍的下降。显然这是可以的,这就是线程池的概念。线程池的出发点便是为了节省系统资源,让一个线程再执行完一个任务后继续执行另一个任务。(注意,在Thread 类的实例被创建并通过start方法启动后便不可以再次被启动了,所以通过Thread 方式不能实现一个线程对应多个任务,只能通过线程池来实现)。