多线程之线程池的简单理解

多线程之线程池

Author cherryc

Date 2018/3/28

Desc 多线程理解及线程池使用

进程与线程

  • 进程:进程就是正在执行的程序。
  • 线程:是程序执行的一条路径, 一个进程中可以包含多条线程。
    通俗理解:例如你打开微信就是打开一个进程,在微信里面和好友视频聊天就是开启了一条线程。
  • 两者之间的关系
    一个进程里面可以有多条线程,至少有一条线程。
    一条线程一定会在一个进程里面。

单线程与多线程

单线程程序:即,若有多个任务只能依次执行。当上一个任务执行结束后,下一个任务开始执行。如,去网吧上网,网吧只能让一个人上网,当这个人下机后,下一个人才能上网。

多线程程序:即,若有多个任务可以同时执行。如,去网吧上网,网吧能够让多个人同时上网。

image.png

创建线程的方式

既然大概了解了什么是进程,什么是线程,什么是多线程,那我们接下来就要使用多线程,那首先就是怎么创建线程?

创建线程有三种方式:

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

接下来我们就通过代码示例详细介绍这三种创建线程的方法,以及如何使用多线程:

1)继承Thread类

Thread类的主要方法:

  • Thread() :分配thread对象
  • Thread(String name) :分配thread对象,并指定线程名称
  • start() :启动线程
  • run() : 线程要执行的方法,全部放在run方法内
  • sleep(long millis) : 让当前正在执行的线程休眠的多少毫秒(主要是暂停线程运行)
1、定义一个类MyThread继承Thread,并重写run方法。
2、将要执行的代码写在run方法中。
3、创建该类的实例,并调用start()方法开启线程。
public class main {
    public static void main(String[] args){
        MyThread myThread = new MyThread();
        myThread.start();
    }

    static class MyThread extends Thread {
        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName()  + "  i=" + i);
            }
        }
    }
}

image.png

这里说一下线程对象调用run方法和start方法的区别:

线程对象调用run方法和一般的对象调用方法一样,只是执行方法并不开启线程;线程对象比较特殊,需要调用start方法,并且让jvm调用run,在开启的线程中执行run方法。

2)实现Runnable接口

我们都知道 java 支持单继承,多实现。实现 Runnable 接口还可以继承其他类,而使用继承 Thread 就不能继承其他类了。所以当你想创建一个线程又希望继承其他类的时候就该选择实现 Runnable 接口的方式。

1、定义一个类MyRunnable实现Runnable接口,并重写run方法。
2、将要执行的代码写在run方法中。
3、创建Thread对象, 传入MyRunnable的实例,并调用start()方法开启线程。
public class main {
    public static void main(String[] args){
        MyRunnable myRunnable = new MyRunnable();
        Thread thread = new Thread(myRunnable);
        thread.start();
    }

    static class MyRunnable implements Runnable{
        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName()  + "  i=" + i);
            }
        }
    }
}
3)实现Callable接口

Callable 是类似于 Runnable 的接口,实现 Callable 接口的类和实现 Runnable 的类都是可被其它线程执行的任务。Callable 执行的方法是 call() ,而 Runnable 执行的方法是 run()。call() 方法有返回值还能抛出异常, run() 方法则没有没有返回值,也不能抛出异常。所以如果需要获得返回值时就可以使用Callable接口。

1、自定义一个类 MyCallable 实现 Callable 接口,并重写call()方法
2、将要执行的代码写在call()方法中
3、创建线程池对象,调用submit()方法执行MyCallable任务,并返回Future对象
4、调用Future对象的get()方法获取call()方法执行完后的值
public class main {
    public static void main(String[] args){
        MyCallable myCallable = new MyCallable();
        //创建线程池对象,调用submit()方法执行MyCallable任务,并返回Future对象
        ExecutorService pool = Executors.newSingleThreadExecutor();
        Future<Integer> sum = pool.submit(myCallable);
        try {
            //用Future对象的get()方法获取call()方法执行完后的值
            System.out.println("sum="+sum.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        //注意关闭线程池
        //shutdown并不是直接关闭线程池,而是不再接受新的任务…如果线程池内有任务,那么把这些任务执行完毕后,关闭线程池….
        //shutdownNow()
        //这个方法表示不再接受新的任务,并把任务队列中的任务直接移出掉,如果有正在执行的,尝试进行停止…
        pool.shutdown();
    }
    static class MyCallable implements Callable<Integer>{

        @Override
        public Integer call() throws Exception {
            int sum = 0;
            for(int i=0;i<10;i++){
                sum += i;
            }
            return sum;
        }
    }
}

注意:submit方法调用结束后,程序并不终止,是因为线程池控制了线程的关闭,将使用完的线程又归还到了线程池中。所以需要手动关闭线程池。

线程池ThreadPoolExecutor

Callable接口使用到了线程池,接下来介绍下什么是线程池以及线程池的用法:

线程池 即对象池的一种(池中的对象为线程 Thread),类似的还有 数据库连接池(池中对象为数据库连接 Connection)。合理利用线程池能够带来三个好处:

  1. 降低资源消耗,通过重复利用已创建的线程,降低线程创建和销毁时造成的时间和内存上的消耗;
  2. 提升响应速度,当任务到达时,直接使用线程池中的线程来运行任务,使得任务可以不需要等到线程创建就能立即执行;
  3. 提高线程的可管理性,线程是开销很大的对象,如果无限制的创建线程,不仅会快速消耗系统资源,还会降低系统的稳定性;而使用线程池可以对线程进行统一的分配和调控。能更好的控制线程的开启与回收,并且能定时执行任务。

具体操作过程如下图:

image.png

线程池的基础架构如下:

image.png

  • Executor:java中线程池的顶级接口,可以称它为一个执行器,通过查看源码也知道,他只有一个简单的方法execute(Runnable command),就是用来执行提交的任务。源码如下:
public interface Executor {
    void execute(Runnable command);
}
  • ExecutorService:Executor的子类,也是真正的线程池接口,继承了 Executor 接口,ExecutorService 是所有线程池的基础接口。调用submit方法提交任务还可以返回一个Future对象,利用该对象可以了解任务执行情况,获得任务的执行结果或取消任务。主要方法有:

    image.png

  • ThreadPoolExecutor:ExecutorService的默认实现,Executors创建各种线程池的时候内部其实就是调用了ThreadPoolExecutor的构造方法。下面通过查看源码验证。例如随便创建一个线程池:

ExecutorService pool = Executors.newSingleThreadExecutor();

点击newSingleThreadExecutor()进去,里面确实调用了ThreadPoolExecutor的构造方法,如下:

public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }
  • ThreadPoolExecutor构造函数参数说明
    public ThreadPoolExecutor(int corePoolSize,
                                int maximumPoolSize,
                                long keepAliveTime,
                                TimeUnit unit,
                                BlockingQueue<Runnable> workQueue,
                                ThreadFactory threadFactory,
                                RejectedExecutionHandler handler) {
      }
    • corePoolSize:核心线程数,如果运行的线程数少于corePoolSize,当有新的任务过来时会创建新的线程来执行这个任务,即使线程池中有其他空闲的线程。

    • maximumPoolSize:线程池中允许的最大线程数。

    • keepAliveTime:如果线程数多于核心线程数,那么这些多出来的线程如果空闲时间超过keepAliveTime将会被终止。

    • unit:keepAliveTime参数的时间单位。

    • workQueue:任务队列,通过线程池的execute方法会将任务Runnable存储在队列中。

    • threadFactory:线程工厂,用来创建新线程。

    • handler:添加任务出错时的策略捕获器,默认是ThreadPoolExecutor.AbortPolicy ,即添加任务出错就直接抛出异常 。

  • Executors:由于线程池配置比较复杂,自己配置的线程池可能性能不是最好的。Executors就是用来方便创建各种常用线程池的工具类。这个工具类为我们提供了一些预先定义好的线程池:

    • newFixedThreadPool

    创建固定大小的线程池,这样可以控制线程最大并发数,超出的线程会在队列中等待。如果线程池中的某个线程由于异常而结束,线程池则会再补充一条新线程。

    image.png

    • newSingleThreadExecutor

    创建一个单线程的线程池,即这个线程池永远只有一个线程在运行,这样能保证所有任务按指定顺序来执行。如果这个线程异常结束,那么会有一个新的线程来替代它。

    image.png

    • newCachedThreadPool

    创建带有缓存的线程池,在执行新的任务时,当线程池中有之前创建的可用线程就重用之前的线程,否则就新建一条线程。如果线程池中的线程在60秒未被使用,就会将它从线程池中移除。所以这个线程池表现得就像缓存,缓存的资源为线程,缓存的超时时间为 60 秒。

    • newScheduledThreadPool

    创建定时和周期性执行的线程池

shutdown :作用是向线程池发送关闭的指令。一旦在线程池上调用 shutdown 方法之后,线程池便不能再接受新的任务;如果此时还向线程池提交任务,那么将会抛出 RejectedExecutionException 异常。之后线程池不会立刻关闭,直到之前已经提交到线程池中的所有任务(包括正在运行的任务和在队列中等待的任务)都已经处理完成,才会关闭。

shutdownNow :与 shutdown 不同,shutdownNow 会立即关闭线程池 —— 当前在线程池中运行的任务会全部被取消,然后返回线程池中所有正在等待的任务。(值得注意的是,我们 必须显式的关闭线程池,否则线程池不会自己关闭)

awaitTermination :可以用来判断线程池是否已经关闭。调用 awaitTermination 之后,在 timeout 时间内,如果线程池没有关闭,则阻塞当前线程,否则返回 true;当超过 timeout 的时间后,若线程池已经关闭则返回 true,否则返回 false。该方法一般这样使用:

  • 任务全部提交完毕之后,我们调用 shutdown 方法向线程池发送关闭的指令;
  • 然后我们通过 awaitTermination 来检测到线程池是否已经关闭,可以得知线程池中所有的任务是否已经执行完毕;
  • 线程池执行完已经提交的所有任务,并将自己关闭;
    调用 awaitTermination 方法的线程停止阻塞,并返回 true;

isShutdown() :如果线程池已经调用 shutdown 或者 shutdownNow,则返回 true,否则返回 false;

isTerminated() :如果线程池已经调用 shutdown 并且线程池中所有的任务已经执行完毕,或者线程池调用了 shutdownNow,则返回 true,否则返回 false。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值