前言

今天做了一家公司的笔试题,结果感觉不太好。题目大多数是基础题,很少涉及到框架相关的内容,果然对实习生来说还是基础知识重要点= =。最后两道是手写编程题,一道要求手写单例模式、另一道jvm内存回收。设计模式前几天刚复习过,最后写出来感觉还行,但第二道就难受了,jvm虚拟机部分几乎没怎么涉及过,就空了交卷。接下来要好好复习基础。回到正题,今天要说的是线程的创建方式和线程池的使用。

线程的三种创建方式

  • 实现Runnable接口
  • 实现Callable接口
  • 继承Thread类

实现 Runnable 和 Callable 接口的类只能当做一个可以在线程中运行的任务,不是真正意义上的线程,因此最后还需要通过 Thread 来调用。可以理解为任务是通过线程驱动从而执行的。

  1. 实现Runnable接口并实现run()方法
    public class RunnableImpl implements Runnable{
    @Override
    public void run() {
    System.out.println("runnable:run");
    }
    }
  2. 实现Callable接口并实现call()方法
    //call返回值通过FutureTask封装,使用FutureTask.get()方法获得返回值
    public class CallableImpl implements Callable<String> {
    @Override
    public String call() {
    return "callable: call";
    }
    }
  3. 继承Thread类并重写run()方法
    public class ThreadExtend extends Thread{
    @Override
    public void run() {
    super.run();
    System.out.println("thread: run");
    }
    }
  4. 三种创建线程的方式一一通过start()执行
    @Test
    public void TestCreateThread() throws ExecutionException, InterruptedException {
    //实现runnable接口
    Runnable runnable=new RunnableImpl();
    Thread thread1=new Thread(runnable);
    thread1.start();

    //实现callable接口
    CallableImpl callable = new CallableImpl();
    FutureTask<String> futureTask = new FutureTask<>(callable);
    Thread thread2=new Thread(futureTask);
    thread2.start();
    System.out.println(futureTask.get());

    //thread继承
    Thread thread3=new ThreadExtend();
    thread3.start();
    }
    创建线程结果
    注意,线程开启要使用start()方法,而不是使用run()start()是启动一个线程,此时的线程处于就绪状态,等时间片到即开始运行。run()并没有开辟一个线程,仅仅是在当前的线程中执行run()方法。

线程池的使用

线程池实现类的顶层接口是Executor,顶层接口Executor提供了一种思想:将任务提交和任务执行进行解耦。开发者无需关注如何创建线程,如何调度线程来执行任务,开发者只需提供Runnable对象,将任务的运行逻辑提交到执行器Executor中,由Executor框架完成线程的调配和任务的执行部分。
Executor继承关系类图

  1. ExecutorService接口增加了一些能力:(1)扩充执行任务的能力,补充可以为一个或一批异步任务生成Future的方法;(2)提供了管控线程池的方法,比如停止线程池的运行。
  2. AbstractExecutorService则是上层的抽象类,将执行任务的流程串联了起来,保证下层的实现只需关注一个执行任务的方法即可。
  3. 最下层的实现类ThreadPoolExecutor实现最复杂的运行部分,ThreadPoolExecutor将会一方面维护自身的生命周期,另一方面同时管理线程和任务,使两者良好的结合从而执行并行任务。

主要的Executor有三种:CachedThreadPoolFixedThreadPoolSingleThreadExecutor,都可通过Executors获得,返回值是ExecutorService

  • CachedThreadPool:一个任务创建一个线程;
  • FixedThreadPool:所有任务只能使用固定大小的线程;
  • SingleThreadExecutor:相当于大小为 1 的 FixedThreadPool。

线程池的实现:

@Test
public void TestExecutor(){
//创建大小为2的线程池
ExecutorService threadPool=Executors.newFixedThreadPool(2);
for(int i=0;i<5;i++){
final int tmp=i;
//执行线程任务
threadPool.execute(new Runnable() {
@Override
public void run() {
System.out.println(tmp);
}
});
}
//关闭线程池
threadPool.shutdown();
}

创建线程池结果

通常我们使用多线程都是进行一些并发的操作,如下载等。那么我们想要记录一下线程执行结果可以通过ExecutorServicesubmit()提交任务后的返回值来获得,或者通过CompletionService来对任务执行结果进行处理。CompletionServicetake()可获得Future对象,根据其get()方法我们可以获得线程返回值。take()方法返回的是最早执行完毕的任务结果,所以在下载记录进度的这种情况下我们使用CompletionService会更加的方便

使用CompletionService对线程池返回值进行管理:

@Test
public void TestThreads() {
int count=0;
int times=10;
//通常有三种线程池,1、固定线程数量FixedThreadPool 2、只提供单个线程SingleThreadExecutor 3、一个任务创一个线程CachedThreadPool
//大小为5的线程池
ExecutorService threadPool= Executors.newFixedThreadPool(5);
//管理每个线程返回值
CompletionService<Integer> cs=new ExecutorCompletionService<Integer>(threadPool);
//提交线程
for(int i=0;i<times;i++){
final Integer tmp=i;
//用CompletionService提交
cs.submit(new Callable<Integer>() {
@Override
public Integer call() {
try {
Thread.sleep(500);
return tmp;
} catch (InterruptedException e) {
e.printStackTrace();
return null;
}
}
});
}
while(threadPool.isShutdown()==false){
Integer integer=null;
try {
//线程执行结果
integer = cs.take().get();
if(integer!=null){
count++;
}
}catch (Exception e){
e.printStackTrace();
}finally {
if(count==10){
//执行完毕关闭线程池:已提交的会继续执行到完毕,不再接收新的线程
threadPool.shutdown();
System.out.println("当前线程执行结果返回值:"+integer+"-"+count+"/10");
System.out.println("finish!");
}else {
System.out.println("当前线程执行结果返回值:"+integer+"-"+count+"/10");
}
}
}
}

使用CompletionService对线程池进行管理结果