多线程: 指的是这个程序 (一个进程) 运行时产生了不止一个线程
并行与并发:
并行: 多个 CPU 实例或者多台机器同时执行一段处理逻辑, 是真正的同时.
并发: 通过 CPU 调度算法, 让用户看上去同时执行, 实际上从 CPU 操作层面不是真正的同时. 并发往往在场景中有公用的资源, 那么针对这个公用的资源往往产生瓶颈, 我们会用 TPS 或者 QPS 来反应这个系统的处理能力.
线程状态:
新建状态(New): 当线程对象对创建后, 即进入了新建状态, 如: Thread t = new MyThread();
就绪状态 (Runnable): 当调用线程对象的 start() 方法 (t.start();), 线程即进入就绪状态. 处于就绪状态的线程, 只是说明此线程已经做好了准备, 随时等待 CPU 调度执行, 并不是说执行了 t.start() 此线程立即就会执行;
运行状态(Running): 当 CPU 开始调度处于就绪状态的线程时, 此时线程才得以真正执行, 即进入到运行状态. 注: 就 绪状态是进入到运行状态的唯一入口, 也就是说, 线程要想进入运行状态执行, 首先必须处于就绪状态中;
阻塞状态(Blocked): 处于运行状态中的线程由于某种原因, 暂时放弃对 CPU 的使用权, 停止执行, 此时进入阻塞状态, 直到其进入到就绪状态, 才 有机会再次被 CPU 调用以进入到运行状态. 根据阻塞产生的原因不同, 阻塞状态又可以分为三种:
1. 等待阻塞: 运行状态中的线程执行 wait()方法, 使本线程进入到等待阻塞状态;
2. 同步阻塞: 线程在获取 synchronized 同步锁失败(因为锁被其它线程所占用), 它会进入同步阻塞状态;
3. 其他阻塞: 通过调用线程的 sleep()或 join()或发出了 I/O 请求时, 线程会进入到阻塞状态. 当 sleep()状态超时, join()等待线程终止或者超时, 或者 I/O 处理完毕时, 线程重新转入就绪状态.
死亡状态 (Dead): 线程执行完了或者因异常退出了 run() 方法, 该线程结束生命周期.
Java 多线程的创建及启动
1. 继承 Thread 类, 重写该类的 run()方法.
通过重写 run()方法定义了一个新的线程类 MyThread, 其中 run()方法的方法体代表了线程需要完成的任务, 称之为线程执行体. 当创建此线程类对象时一个新的线程得以创建, 并进入到线程新建状态. 通过调用线程对象引用的 start()方法, 使得该线程进入到就绪状态, 此时此线程并不一定会马上得以执行, 这取决于 CPU 调度时机.
2. 实现 Runnable 接口, 并重写该接口的 run()方法, 该 run()方法同样是线程执行体, 创建 Runnable 实现类的实例, 并以此实例作为 Thread 类的 target 来创建 Thread 对象, 该 Thread 对象才是真正的线程对象.
Thread 类本身也是实现了 Runnable 接口, 而 run()方法最先是在 Runnable 接口中定义的方法.
Thread 类中对 Runnable 接口中 run()方法的实现:
- @Override
- public void run() {
- if (target != null) {
- target.run();
- }
- }
也就是说, 当执行到 Thread 类中的 run()方法时, 会首先判断 target 是否存在, 存在则执行 target 中的 run()方法, 也就是实现了 Runnable 接口并重写了 run()方法的类中的 run()方法.
3. 使用 Callable 和 Future 接口创建线程. 具体是创建 Callable 接口的实现类, 并实现 call()方法. 并使用 FutureTask 类来包装 Callable 实现类的对象, 且以此 FutureTask 对象作为 Thread 对象的 target 来创建线程.
在实现 Callable 接口中, 此时不再是 run()方法了, 而是 call()方法, 此 call()方法作为线程执行体, 同时还具有返回值! 在创建新的线程时, 是通过 FutureTask 来包装 MyCallable 对象, 同时作为了 Thread 对象的 target.
FutureTask 类实际上是同时实现了 Runnable 和 Future 接口, 由此才使得其具有 Future 和 Runnable 双重特性. 通过 Runnable 特性, 可以作为 Thread 对象的 target, 而 Future 特性, 使得其可以取得新创建线程中的 call()方法的返回值.
Java 多线程的就绪, 运行和死亡状态
就绪状态转换为运行状态: 当此线程得到处理器资源;
运行状态转换为就绪状态: 当此线程主动调用 yield()方法或在运行过程中失去处理器资源.
运行状态转换为死亡状态: 当此线程线程执行体执行完毕或发生了异常.
此处需要特别注意的是: 当调用线程的 yield()方法时, 线程从运行状态转换为就绪状态, 但接下来 CPU 调度就绪状态中的哪个线程具有一定的随机性, 因此, 可能会出现 A 线程调用了 yield()方法后, 接下来 CPU 仍然调度了 A 线程的情况.
1.sleep()方法
在指定时间内让当前正在执行的线程暂停执行, 但不会释放 "锁标志". 不推荐使用.
sleep()使当前线程进入阻塞状态, 在指定时间内不会执行.
2.wait()方法
在其他线程调用对象的 notify 或 notifyAll 方法前, 导致当前线程等待. 线程会释放掉它所占有的 "锁标志", 从而使别的线程有机会抢占该锁.
当前线程必须拥有当前对象锁. 如果当前线程不是此锁的拥有者, 会抛出 IllegalMonitorStateException 异常.
唤醒当前对象锁的等待线程使用 notify 或 notifyAll 方法, 也必须拥有相同的对象锁, 否则也会抛出 IllegalMonitorStateException 异常.
waite()和 notify()必须在 synchronized 函数或 synchronized block 中进行调用. 如果在 non-synchronized 函数或 non-synchronized block 中进行调用, 虽然能编译通过, 但在运行时会发生 IllegalMonitorStateException 的异常.
3.yield 方法
暂停当前正在执行的线程对象.
yield()只是使当前线程重新回到可执行状态, 所以执行 yield()的线程有可能在进入到可执行状态后马上又被执行.
yield()只能使同优先级或更高优先级的线程有执行的机会.
4.join 方法
等待该线程终止.
等待调用 join 方法的线程结束, 再继续执行. 如: t.join();// 主要用于等待 t 线程运行结束, 若无此句, main 则会执行完毕, 导致结果不可预测.
Java 线程池
如果并发的线程数量很多, 并且每个线程都是执行一个时间很短的任务就结束了, 这样频繁创建线程就会大大降低系统的效率, 因为频繁创建线程和销毁线程需要时间. 那么有没有一种办法使得线程可以复用, 就是执行完一个任务, 并不被销毁, 而是可以继续执行其他的任务? 在 Java 中可以通过线程池来达到这样的效果.
线程池核心 ThreadPoolExecutor 类
ThreadPoolExecutor 类的几个参数:
corePoolSize: 核心池的大小, 这个参数跟后面讲述的线程池的实现原理有非常大的关系. 在创建了线程池后, 默认情况下, 线程池中并没有任何线程, 而是等待有任务到来才创建线程去执行任务, 除非调用了 prestartAllCoreThreads()或者 prestartCoreThread()方法, 从这 2 个方法的名字就可以看出, 是预创建线程的意思, 即在没有任务到来之前就创建 corePoolSize 个线程或者一个线程. 默认情况下, 在创建了线程池后, 线程池中的线程数为 0, 当有任务来之后, 就会创建一个线程去执行任务, 当线程池中的线程数目达到 corePoolSize 后, 就会把到达的任务放到缓存队列当中;
maximumPoolSize: 线程池最大线程数, 这个参数也是一个非常重要的参数, 它表示在线程池中最多能创建多少个线程;
keepAliveTime: 表示线程没有任务执行时最多保持多久时间会终止. 默认情况下, 只有当线程池中的线程数大于 corePoolSize 时, keepAliveTime 才会起作用, 直到线程池中的线程数不大于 corePoolSize, 即当线程池中的线程数大于 corePoolSize 时, 如果一个线程空闲的时间达到 keepAliveTime, 则会终止, 直到线程池中的线程数不超过 corePoolSize. 但是如果调用了 allowCoreThreadTimeOut(boolean)方法, 在线程池中的线程数不大于 corePoolSize 时, keepAliveTime 参数也会起作用, 直到线程池中的线程数为 0;
unit: 参数 keepAliveTime 的时间单位
workQueue: 一个阻塞队列, 用来存储等待执行的任务, 这个参数的选择也很重要, 会对线程池的运行过程产生重大影响, 一般来说, 这里的阻塞队列有以下几种选择:
- ArrayBlockingQueue;
- LinkedBlockingQueue;
- SynchronousQueue;
threadFactory: 线程工厂, 主要用来创建线程;
handler: 表示当拒绝处理任务时的策略, 有以下四种取值
ThreadPoolExecutor.AbortPolicy: 丢弃任务并抛出 RejectedExecutionException 异常.
ThreadPoolExecutor.DiscardPolicy: 也是丢弃任务, 但是不抛出异常.
ThreadPoolExecutor.DiscardOldestPolicy: 丢弃队列最前面的任务, 然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy: 由调用线程处理该任务
Executor 是一个顶层接口, 在它里面只声明了一个方法 execute(Runnable), 返回值为 void, 参数为 Runnable 类型, 从字面意思可以理解, 就是用来执行传进去的任务的;
然后 ExecutorService 接口继承了 Executor 接口, 并声明了一些方法: submit,invokeAll,invokeAny 以及 shutDown 等;
抽象类 AbstractExecutorService 实现了 ExecutorService 接口, 基本实现了 ExecutorService 中声明的所有方法; 然后 ThreadPoolExecutor 继承了类 AbstractExecutorService.
在 ThreadPoolExecutor 类中有几个非常重要的方法:
- execute();
- submit();
- shutdown();
- shutdownNow();
execute()方法实际上是 Executor 中声明的方法, 在 ThreadPoolExecutor 进行了具体的实现, 这个方法是 ThreadPoolExecutor 的核心方法, 通过这个方法可以向线程池提交一个任务, 交由线程池去执行.
submit()方法是在 ExecutorService 中声明的方法, 在 AbstractExecutorService 就已经有了具体的实现, 在 ThreadPoolExecutor 中并没有对其进行重写, 这个方法也是用来向线程池提交任务的, 但是它和 execute()方法不同, 它能够返回任务执行的结果, 去看 submit()方法的实现, 会发现它实际上还是调用的 execute()方法, 只不过它利用了 Future 来获取任务执行结果
shutdown()和 shutdownNow()是用来关闭线程池的.
runState 表示当前线程池的状态, 它是一个 volatile 变量用来保证线程之间的可见性;
下面的几个 static final 变量表示 runState 可能的几个取值.
当创建线程池后, 初始时, 线程池处于 RUNNING 状态;
如果调用了 shutdown()方法, 则线程池处于 SHUTDOWN 状态, 此时线程池不能够接受新的任务, 它会等待所有任务执行完毕;
如果调用了 shutdownNow()方法, 则线程池处于 STOP 状态, 此时线程池不能接受新的任务, 并且会去尝试终止正在执行的任务;
当线程池处于 SHUTDOWN 或 STOP 状态, 并且所有工作线程已经销毁, 任务缓存队列已经清空或执行结束后, 线程池被设置为 TERMINATED 状态.
任务提交给线程池之后的处理策略, 这里总结一下主要有 4 点:
如果当前线程池中的线程数目小于 corePoolSize, 则每来一个任务, 就会创建一个线程去执行这个任务;
如果当前线程池中的线程数目>=corePoolSize, 则每来一个任务, 会尝试将其添加到任务缓存队列当中, 若添加成功, 则该任务会等待空闲线程将其取出去执行; 若添加失败(一般来说是任务缓存队列已满), 则会尝试创建新的线程去执行这个任务;
如果当前线程池中的线程数目达到 maximumPoolSize, 则会采取任务拒绝策略进行处理;
如果线程池中的线程数量大于 corePoolSize 时, 如果某线程空闲时间超过 keepAliveTime, 线程将被终止, 直至线程池中的线程数目不大于 corePoolSize; 如果允许为核心池中的线程设置存活时间, 那么核心池中的线程空闲时间超过 keepAliveTime, 线程也会被终止.
线程池中的线程初始化
默认情况下, 创建线程池之后, 线程池中是没有线程的, 需要提交任务之后才会创建线程.
在实际中如果需要线程池创建之后立即创建线程, 可以通过以下两个方法办到:
prestartCoreThread(): 初始化一个核心线程;
prestartAllCoreThreads(): 初始化所有核心线程
任务缓存队列及排队策略
在前面我们多次提到了任务缓存队列, 即 workQueue, 它用来存放等待执行的任务.
workQueue 的类型为 BlockingQueue<Runnable>, 通常可以取下面三种类型:
1)ArrayBlockingQueue: 基于数组的先进先出队列, 此队列创建时必须指定大小;
2)LinkedBlockingQueue: 基于链表的先进先出队列, 如果创建时没有指定此队列大小, 则默认为 Integer.MAX_VALUE;
3)synchronousQueue: 这个队列比较特殊, 它不会保存提交的任务, 而是将直接新建一个线程来执行新来的任务.
线程池的关闭
ThreadPoolExecutor 提供了两个方法, 用于线程池的关闭, 分别是 shutdown()和 shutdownNow(), 其中:
shutdown(): 不会立即终止线程池, 而是要等所有任务缓存队列中的任务都执行完后才终止, 但再也不会接受新的任务
shutdownNow(): 立即终止线程池, 并尝试打断正在执行的任务, 并且清空任务缓存队列, 返回尚未执行的任务
三种定义好的线程池:
在 java doc 中, 并不提倡我们直接使用 ThreadPoolExecutor, 而是使用 Executors 类中提供的几个静态方法来创建线程池:
- Executors.newCachedThreadPool();// 创建一个缓冲池, 缓冲池容量大小为 Integer.MAX_VALUE
- Executors.newSingleThreadExecutor(); // 创建容量为 1 的缓冲池
- Executors.newFixedThreadPool(int);// 创建固定容量大小的缓冲池
下面是这三个静态方法的具体实现;
- public static ExecutorService newFixedThreadPool(int nThreads) {
- return new ThreadPoolExecutor(nThreads, nThreads,
- 0L, TimeUnit.MILLISECONDS,
- new LinkedBlockingQueue<Runnable>());
- }
- public static ExecutorService newSingleThreadExecutor() {
- return new FinalizableDelegatedExecutorService
- (new ThreadPoolExecutor(1, 1,
- 0L, TimeUnit.MILLISECONDS,
- new LinkedBlockingQueue<Runnable>()));
- }
- public static ExecutorService newCachedThreadPool() {
- return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
- 60L, TimeUnit.SECONDS,
- new SynchronousQueue<Runnable>());
- }
从它们的具体实现来看, 它们实际上也是调用了 ThreadPoolExecutor, 只不过参数都已配置好了.
1)newFixedThreadPool 创建的线程池 corePoolSize 和 maximumPoolSize 值是相等的, 它使用的 LinkedBlockingQueue;
2)newSingleThreadExecutor 将 corePoolSize 和 maximumPoolSize 都设置为 1, 也使用的 LinkedBlockingQueue;
3)newCachedThreadPool 将 corePoolSize 设置为 0, 将 maximumPoolSize 设置为 Integer.MAX_VALUE, 使用的 SynchronousQueue, 也就是说来了任务就创建线程运行, 当线程空闲超过 60 秒, 就销毁线程.
如何合理配置线程池的大小
一般需要根据任务的类型来配置线程池大小:
如果是 CPU 密集型任务, 就需要尽量压榨 CPU, 参考值可以设为 NCPU+1
如果是 IO 密集型任务, 参考值可以设置为 2*NCPU
来源: http://www.jianshu.com/p/9f18c8d5c5ef