Android 多任务并发一(Java 多线程应用及线程池原理)

468 阅读4分钟

何为线程

线程是操作系统能够进行运算调度的最小方式,它包含在进程之中,一个进程可以包涵多条线程。进程内的每一条线程代表一个单一的顺序控制流。

Java 中线程的使用方式

1. 实现Runnable接口,重写run方法

//启动线程
new Thread(new MyRunnable()).start();
//实现Runnable,重写run()
static class MyRunnable implements Runnable{
        @Override
        public void run() {
            System.out.println("run");
        }
    }

2. 实现Callable接口,重写MyCallable接口

ExecutorService service= Executors.newFixedThreadPool(100);
Future<String> result = service.submit(new MyCallable());
try {
    String str= result.get();
} catch (ExecutionException | InterruptedException e) {
    e.printStackTrace();
}

//实现MyCallable接口  
static class MyCallable implements Callable<String>{
    @Override
    public String call() throws Exception {
        return "hello";
    }
}

3. 继承Thread,实现run方法


new MyThread().start();

static class MyThread extends Thread{
    @Override
    public void run() {
        System.out.println("run");
    }
}

线程池

什么是线程池

线程的开启和销毁是需要消耗性能的,频繁的开启和销毁线程有可能会造成系统创建大量的同类线程并导致资源的耗尽,所以在开发时,我们往往都会使用线程池进行线程管理。

线程池的主要是以“池化”的思想来管理我们的线程,线程池中维护多个线程,当我们向线程池中请求执行任务的时候,线程池会结合系统内核数、最大线程数,任务队列中的情况去选择怎样处理请求的任务,使用线程池可以达到以下目的:

  1. 方便管理: 线程的执行与创建完全分开
  2. 复用: 可以给其他的任务进行复用
  3. 避免频繁创建线程
  4. 可提供一些拓展功能: 比如延时定时线程池ScheduledThreadPoolExecutor,就允许任务延期执行或定期执行

Java中的常用线程池

Java标准库提供了ExecutorService接口表示线程池,并提供了几种比较常用的线程池,他们的实现类都是ThreadPoolExecutor,通过不一样的参数来生成不一样的线程池策略,通过Executors类的方法可以创建一下三种类型的线程池:

  1. FixedThreadPool:线程数固定的线程池
  2. CachedThreadPool:线程数根据任务动态调整的线程池
  3. SingleThreadExecutor:仅单线程执行的线程池

线程池的使用方法,以FixedThreadPool为例:

ExecutorService service2 = Executors.newFixedThreadPool(100);
service2.execute(new Runnable() {
    @Override
    public void run() {
        System.out.println("线程任务执行");
    }
});

线程池原理

1、ThreadPoolExecutor类

Java线程池的核心实现类为ThreadPoolExecutor,其顶层接口为Executor,ThreadPoolExecutor的继承关系图为:

  1. Executor: 定义了任务提交的方法。
  2. ExecutorService: 丰富了执行任务的能力,增加了Future的功能,补充了管理线程池的方法,比如停止线程池的运行。
  3. AbstractExecutorService: 是上层的抽象类,将执行任务的流程串联了起来,保证下层的实现只需关注一个执行任务的方法即可
  4. ThreadPoolExecuotr: 维护自身的生命周期,另一方面同时管理线程和任务
2、线程池的状态管理

ThreadPoolExecuotr内部维持着线程池的状态,也可以说是线程池的生命周期,其状态主要有:

  1. RUNNIBNG: 可接受新任务并处理排队的任务
  2. SHUTDOWN: 不接受新任务,但处理排队的任务
  3. STOP: 不接受新任务,不处理排队的任务,中断正在进行中的任务
  4. TIDYING: 所有任务都已终止,workerCount为零,转换到TIDYING的线程将运行terminated()方法
  5. TERMINATED: terminated() 执行完进入该状态

ThreadPoolExecuotr的状态值,交由内部属性ctl进行管理,这是一个AtomicInteger类型的值,ctl内部由一个参数分别封装了状态值以及线程池内部当前有效线程数量。

线程池的初始状态为 RUNNING

private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

当执行shutdown的时候时候会进入状态SHUTDOWN。

    public void shutdown() {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            checkShutdownAccess();
            //设置当前线程池状态为SHUTDOWN
            advanceRunState(SHUTDOWN);
            interruptIdleWorkers();
            onShutdown(); // hook for ScheduledThreadPoolExecutor
        } finally {
            mainLock.unlock();
        }
        tryTerminate();
    }

当执行shutdownNow的时候,会进入STOP状态,并且在这个方法当中,将中断所有的线程,清空任务队列。

    public List<Runnable> shutdownNow() {
        List<Runnable> tasks;
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            checkShutdownAccess();
            advanceRunState(STOP);
            //中断所有线程,即使是活动的
            interruptWorkers();
            //排空任务队列
            tasks = drainQueue();
        } finally {
            mainLock.unlock();
        }
        tryTerminate();
        return tasks;
    }

shutdown以及shutdownNow在方法末尾都调用了tryTerminate(),tryTerminate会判断当前状态为SHUTDOWN或STOP且工作线程为0的情况下,将线程池状态更改为TIDYING。之后立即执行terminated()方法,再进入TERMINATED状态。

final void tryTerminate() {
        for (;;) {
            int c = ctl.get();
            if (isRunning(c) ||
                runStateAtLeast(c, TIDYING) ||
                (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
                return;
            if (workerCountOf(c) != 0) { // Eligible to terminate
                interruptIdleWorkers(ONLY_ONE);
                return;
            }

            final ReentrantLock mainLock = this.mainLock;
            mainLock.lock();
            try {
                if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
                    try {
                        terminated();
                    } finally {
                        ctl.set(ctlOf(TERMINATED, 0));
                        termination.signalAll();
                    }
                    return;
                }
            } finally {
                mainLock.unlock();
            }
            // else retry on failed CAS
        }
    }
2、任务管理

exexute()方法作为ThreadPoolExecutor任务提交的入口,主要完成了任务的调度工作,当一个Runnable提交过来以后,有以下几个关键的判断:

  1. 当状态为RUNNING且当前工作线程小于corePoolSize时直接添加线程执行
  2. 当状态为RUNNING且当前工作线程大于corePoolSize,任务队列未满的情况下,将Runnable加入到任务队列中。
  3. 当Runnable添加到工作队列后,此时工作线程=0,则触发addWorker(),
3、线程管理

总结