![](https://s5-media.51cto.com/ost/pc/static/noavatar.gif)
基础篇:高并发一瞥,线程和线程池的总结(上篇)
- 进程是执行程序的实体,系统的调度执行单元,拥有独属的进程空间(内存、磁盘等)。而线程是进程的一个执行流程,一个进程可包含多个线程,共享该进程的所有资源:代码段,数据段(全局变量和静态变量),堆存储;但每个线程拥有自己的执行栈和局部变量
- 进程创建要分配资源,进程切换既要保存当前进程环境,也要设置新进程环境,开销大;而线程共享进程的资源,共享部分不需重分配、切换,线程的创建切换是小于进程的。因此更偏向使用线程提升程序的并发性
- 线程又分内核态和用户态,内核态可被系统感知调度执行;用户态是用户程序级别的,系统不知线程的存在,线程调度由程序负责
1 JAVA线程的实现原理
- java的线程是基于操作系统原生的线程模型(非用户态),通过系统调用,将程序的线程交给系统调度执行
- java线程拥有属于自己的虚拟机栈,当JVM将栈、程序计数器、工作内存等准备好后,会分配一个系统原生线程来执行。Java线程结束,原生线程随之被回收
- 原生线程初始化完毕,会调Java线程的run方法。当JAVA线程结束时,则释放原生线程和Java线程的所有资源
- java方法的执行对应虚拟机栈的一个栈帧,用于存储局部变量、操作数栈、动态链接、方法出口等
2 JAVA线程的生命周期
3 JAVA线程的几种常用方法
- 「线程启动函数」
//Thread.java
//调用start启动线程,进入Runnable状态,等待系统调度执行
public synchronized void start(){//synchronized同步执行
if (threadStatus != 0) //0 代表new状态,非0则抛出错误
throw new IllegalThreadStateException();
...
start0(); //本地方法方法 private native void start0()
...
}
//Running状态,新线程执行的代码方法,可被子类重写
public void run(){
if (target != null) {
//target是Runnable,new Thread(Runnable)时传入
target.run();
}
}
- 「线程终止函数」
//Thread.java
@Deprecated public final void stop();
//中断线程
public void interrupt()
//判断的是当前线程是否处于中断状态
public static boolean interrupted()
- 用stop会强行终止线程,导致线程所持有的全部锁突然释放(不可控制),而被锁突同步的逻辑遭到破坏。不建议使用
- interrupt函数中断线程,但它不一定会让线程退出的。它比stop函数优雅,可控制
○ 当线程处于调用sleep、wait的阻塞状态时,会抛出InterruptedException,代码内部捕获,然后结束线程
○ 线程处于非阻塞状态,则需要程序自己调用interrupted()判断,再决定是否退出
- 其他常用方法
//Thread.java
//阻塞等待其他线程
public final synchronized void join( final long
//暂时让出CPU执行
public static native void yield();
//休眠一段时间
public static native void sleep( long throws;
- start与run方法的区别
○ start是Thread类的方法,从线程的生命周期来看,start的执行并不意味着新线程的执行,而是让JVM分配虚拟机栈,进入Runnable状态,start的执行还是在旧线程上
○ run则是新线程被系统调度,获取CPU时,执行的方法,必须是继承Thread或者是实现Runnable接口
- Thread.sleep与Object.wait区别
○ Thread.sleep需要指定休眠时间,时间一到继续运行;和锁机制无关,不能加锁也不用释放锁
○ Object.wait需要在synchronized中调用,否则报IllegalMonitorStateException错误。wait方法会释放锁,需要调用相同锁对象Object.notify来唤醒线程
4 线程池及其优点
- 线程的每次使用时创建,结束再销毁,是非常巨大的开销。若用缓存的策略(线程池),暂存曾经创建的线程,复用这些线程,可以减少程序的消耗,提高线程的利用率
- 降低资源消耗:重复利用线程可降低线程创建和销毁造成的消耗
- 提高响应速度:当任务到达时,不需要等待线程创建就能立即执行
- 提高线程的可管理性:使用线程池可以进行统一的分配,监控和调优
5 JDK封装的线程池
//ThreadPoolExecutor.java
public ThreadPoolExecutor(
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
- 1 corePoolSize:核心线程数,线程池维持的线程数量
- 2 maximumPoolSize:最大的线程数,当阻塞队列不可再接受任务时且maximumPoolSize大于corePoolSize则会创建非核心线程来执行。无任务执行时,会被销毁
- 3 keepAliveTime:非核心线程在闲暇间的存活时间
- 4 TimeUnit:和keepAliveTime配合使用,表示keepAliveTime参数的时间单位
- 5 workQueue:正在执行的任务数超过corePoolSize时,任务的等待阻塞队列
- 6 threadFactory:线程的创建工厂
- 7 handler:拒绝策略,线程数达到了maximumPoolSize,还有任务提交则使用拒绝策略处理
6 线程池原理之执行流程
//ThreadPoolExecutor.java
public void execute(Runnable command) {
...
if (workerCountOf(c) < corePoolSize) { //plan A
if (addWorker(command, true))
return;
c = ctl.get();
}
if (isRunning(c) && workQueue.offer(command)) { //plan B
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
//addWorker(command, false) false代表可创建非核心线程执行任务
else if (!addWorker(command, false)) //plan C
reject(command); // //plan D
}
- plan A:任务的execute,先判断核心线程数量达到上限;否,则创建核心线程来执行任务;是,则执行plan B
- plan B:当任务数大于核心数时,任务被加入阻塞队列,如果超过阻塞队列的容量上限,执行C
- plan C: 阻塞队列不能接受任务时,且设置的maximumPoolSize大于corePoolSize,创建新的非核心线程执行任务
- plan D:当plan A、B、C都无能为力时,使用拒绝策略处理
7 阻塞队列的简单了解
- 队列的阻塞插入:当队列满时,队列会阻塞插入元素的线程,直到队列不满
- 队列的阻塞移除:当队列为空时,获取元素的线程会等待队列变为非空
- BlockingQueue提供的方法如下,其中put和take是阻塞操作
操作方法 | 抛出异常 | 阻塞线程 | 返回特殊值 | 超时退出 |
插入元素 | add(e) | put(e) | offer(e) | offer(e, timeout, unit) |
移除元素 | remove() | take() | poll() | pull(timeout, unit) |
检查 | element() | peek() | 无 | 无 |
- 「ArrayBlockingQueue」
○ ArrayBlockingQueue是用数组实现的「有界阻塞队列」,必须指定队列大小,先进先出(FIFO)原则排队
- 「LinkedBlockingQueue」
○ 是用链表实现的「有界阻塞队列」,如果构造LinkedBlockingQueue时没有指定大小,则默认是Integer.MAX_VALUE,无限大
○ 该队列生产端和消费端使用独立的锁来控制数据操作,以此来提高队列的并发性
- 「PriorityBlockingQueue」
○ public PriorityBlockingQueue(int initialCapacity, Comparator<? super E> comparator)
○ 基于数组,元素具有优先级的「无界阻塞队列」,优先级由Comparator决定
○ PriorityBlockingQueue不会阻塞生产者,却会在没有可消费的任务时,阻塞消费者
- 「DelayQueue」
○ 支持延时获取元素的「无界阻塞队列」,基于PriorityQueue实现
○ 元素必须实现Delayed接口,指定多久才能从队列中获取该元素。
○ 可用于缓存系统的设计、定时任务调度等场景的使用
- 「SynchronousQueue」
○ SynchronousQueue是一种无缓冲的等待队列,「添加一个元素必须等待被取走后才能继续添加元素」
- 「LinkedTransferQueue」
○ 由链表组成的TransferQueue「无界阻塞队列」,相比其他队列多了tryTransfer和transfer函数
○ transfer:当前有消费者正在等待元素,则直接传给消费者,「否则存入队尾,并阻塞等待元素被消费才返回」
○ tryTransfer:试探传入的元素是否能直接传给消费者。如果没消费者等待消费元素,元素加入队尾,返回false
- 「LinkedBlockingDeque」
○ LinkedBlockingDeque是由链表构建的双向阻塞队列,多了一端可操作入队出队,少了一半的竞争,提高并发性
文章转载自公众号:潜行前行
![](https://s5-media.51cto.com/ost/pc/static/noavatar.gif)