java基础重铸——线程篇
- 线程池
- 线程池的意义:线程池是池化技术(提前保存热启动一定量的系统资源,在机器资源受限的情况下以减少资源获取切换的消耗来提升性能)在线程管理方面的应用。
- Java中线程接口&实现:
接口:java.lang.Runnable(实现接口) 区别:用该接口执行线程无返回接口
接口:java.util.concurrent.Callable(实现接口) 区别:用该接口实现的线程可以在需要获得线程处理的返回值
实现类:java.lang.Thread(继承) 区别:Java类时单继承,可以通过继承该线程实现类的方式实现线程的创建
- 线程执行的优先级问题:Java线程优先级1~10 默认优先级 5. 按照优先级高(10)低(1)顺序执行,同级别线程按照时间片轮转执行或独占方式来分配线程的执行时间(这个应该和操作系统的CPU分配机制有关系)【优先级高的线程在多线程竞争CPU的情况下一定会先执行(优先级高的线程先执行为高概率事件)】
- 线程的几种状态:
新建(NEW):新创建了一个线程对象
可运行/就绪(RUNNABLE):线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取cpu 的使用权
运行(RUNNING):可运行状态(runnable)的线程获得了cpu 时间片(timeslice) ,执行程序代码
阻塞(BLOCKED):阻塞状态是指线程因为某种原因放弃了cpu 使用权,也即让出了cpu timeslice,暂时停止运行。直到线程进入可运行(runnable)状态,才有机会再次获得cpu timeslice 转到运行(running)状态
死亡(DEAD):线程run()、main() 方法执行结束,或者因异常退出了run()方法,则该线程结束生命周期。死亡的线程不可再次复生 - 线程状态判断&切换:
isAlive():判断线程目前是否正在执行,可以用来判断线程是否执行完毕.已被启动并且未被终止(无法鉴别可运行&阻塞) - true;新创建的或已被终止的 - false
yield():将当前正在执行的线程暂停并放置就绪线程队列最后,让Java线程调度器重新调度处于就绪状态的线程,若队列为空(处于就绪状态的线程仅有一个),则yield方法无效【当前线程进入就绪状态后,所有就绪线程进入按照优先级的线程竞争状态(有可能该线程重新继续执行)】
join():等待调用join方法的线程执行完毕,再执行其他线程
interrupt():中断一个正在运行的线程对象
interrupted():判断一个线程对象是否处于中断状态,若处于中断状态则清除中断状态,它是类方法
isinterrupted():判断一个线程对象是否处于中断状态 - 线程的启动&执行:
start():该方法真正启动线程,而是将线程的状态置为就绪状态,而当线程状态为运行状态时,线程才是真正的开始运行。
run():Run方法只是Thread中的一个普通方法,还是在主线程中执行 - 线程的停止&等待&唤醒:
Object.wait():调用的本地方法,属于Object对象自带的。是线程之间通信常用的信号量,作用是让线程暂时停止运行,并等待其他其他线程使用notify()方法来唤醒。会释放锁标志
Object.notify()/notifyAll():本地方法,用于唤醒被wait()方法暂停的线程
Thread.join(long millis):该方法是基于wait()方法的同步方法(synchronized)可能存在阻塞时间长于millis时间
Thread.sleep():该方法指定等待的时间,让当前执行的线程在指定的时间内暂停执行,进入阻塞状态,方法优势:可以让同/高/低优先级的线程获得执行机会,但是不会释放锁标志 —> 其他线程仍不能访问共享数据
Thread.yield():yield()方法与sleep()方法类似,不会释放锁标志,区别在于没有参数。即只是是当前线程重新回到可运行状态,同时仅能让同/高优先级线程获得执行机会
小结:个人感觉yield和sleep这块比较好理解,一个是线程让出CPU空出一段时间,所以所有就绪线程都可能获得执行机会,另一个是重新回到竞争状态,所以只能同高优先级的获得执行机会;wait()/notify()/notifyAll() 只能在synchronized语句块中使用,但是如果使用的是ReenTrantLock实现同步,则是使用ReenTrantLock.newCondition()获取一个Condition类对象,然后Condition的await(),signal()以及signalAll()分别对应上面的三个方法;wait()/notify()/notifyAll()对象等待池中的所有线程都移动到该对象的锁标志等待池 - 并发:多个线程操作使用相同的资源,保证线程安全,
CPU -> 缓存 -> 内存 三级速度不匹配,导致CPU性能未完全发挥。通过并发操作提高CPU的使用效率
在多个线程使用共享数据时,会导致数据不准确(多线程竞争)
JVM栈与线程:Java栈时一块线程私有的内存空间,线程执行的基本单位就是函数调用,而每次函数调用的数据就会通过Java栈传递。Java栈与数据结构上的栈有着类似的含义,它是一块先进后出的数据结构,只支持出栈和入栈的两种操作。在Java栈中保存的主要内容为栈帧。每次调用一个函数,都会有一个对应的栈帧被压入Java栈。每一个函数调用结束,都会有一个栈帧被弹出Java栈。

局部变量表:栈帧的组成部分之一。它用于保存函数已经局部变量。局部变量表中的变量只有在当前的函数中调用有效,当调用函数结束以后,随着函数栈帧的销毁,局部变量表也随之销毁
操作数栈:栈帧的组成部分之一,它主要保存计算过程中的结果,同时作为计算过程临时变量的存储空间。Java的很多字节码指令都是通过操作数栈进行参数传递
帧数据区:栈帧中指向运行时常量池中该栈帧所属性方法的引用,持有这个引用是为了支持方法调用过程中的动态连接。在Class文件的常量池中存有大量的符号引用,字节码中的方法调用指令就以常量池中指向方法的符号引用为参数。这些符号引用一部分会在类加载阶段或第一次使用的时候转化为直接引用,这种转化称为静态解析。另外一部分将在每一次的运行期期间转化为直接引用,这部分称为动态连接
主内存&工作内存:
Java内存变量交互操作:

线程安全:线程有自己的工作内存,工作内存时线程私有的。线程对堆中的共享变量的修改对于其他线程而言时不可见的。线程再对共享变量进行修改时,通常会先从主内存中拷贝一份变量的副本到工作内存中。执行完修改后将副本值更新到主内存中。多线程同时执行时不会出现不确定或单线程条件下不一致的结果时称为线程安全。
三种条件之一满足即可认为线程安全:
final关键字修饰且已被赋值:修饰变量赋值后就不能再修改
volatile关键字修饰:当对volatile变量执行写操作后,JMM(Java内存模型)会把工作内存中的最新变量值强制刷新到主内存;写操作会导致其他线程中的缓存无效;volatile变量的禁止指令重排序
有锁保护(synchronized、ReentrantLock等):synchronized关键字的语义JMM有两个规定,保证其实现内存可见性:线程解锁前,必须把共享变量的最新值刷新到主内存中;线程加锁前,将清空工作内存中共享变量的值,从主内存中冲洗取值。
并发三大特性:原子性、可见性、有序性【volatile:可见性+有序性;synchronized:原子性+可见性+有序性】
- 线程的加锁&解锁&常见的死锁问题排查方案与问题原因
线程同步&加锁:
volatile:为访问提供一种变量免锁机制;告诉JVM变量可能被其他线程更新;使用标记变量时要重新计算,为不是使用寄存器中的值;不提供原子操作,不能修饰final类型的变量
synchronized:
同步方法:修饰方法时Java内置锁(this)保护方法;synchronized修饰静态方法时,调用方法会锁住整个类
同步代码块:使用synchronized(object)、synchronized(.class)修饰关键代码块
可重入锁java.util.concurrent.ReentrantLock:lock()/unlock()扩展synchronized能力;执行效率低于synchronized
ThreadLocal线程局部变量:使用ThreadLocal管理线程变量,使每一个使用该变量的线程都获得该变量的副本,且副本之间相互独立互不影响(空间换时间)
阻塞队列实现线程同步:java.util.concurrent.LinkedBlockingQueue
原子操作实现线程同步:java.util.concurrent.atomic.* 提供原子化操作
死锁:
产生原因:1、线程间竞争系统资源;2、进程顺序推进不当
必要条件:1、互斥条件;2、占有并等待(请求和保持);3、非抢占(不剥夺已被占有的资源);4、循环等待
解决死锁:1、加锁顺序(顺序加锁);2、加锁时限(限制长期持有资源);3、死锁检测
- 线程池的创建&管理:
java.util.concurrent.ThreadPoolExecutor 类负责Java线程池的创建(推荐),提供了两个接口
java.util.concurrent.Executors#newCachedThreadPool() 创建缓存线程池,线程充足时回收资源:核心线程数0,最大线程数Intageer.Max_Value,可能创建出最大数量的线程池,导致OOM
java.util.concurrent.Executors#newSingleThreadExecutor() 创建单例线程池:默认使用LinkedBlockingQueue无界队列默认构造函数,默认队列大小为Intageer.Max_Value,大量请求堆积,会产生OOM。
java.util.concurrent.Executors#newFixedThreadPool(int) 创建固定大小的线程池:默认使用LinkedBlockingQueue无界队列默认构造函数,默认队列大小为Intageer.Max_Value,大量请求堆积,会产生OOM。
java.util.concurrent.Executors#newScheduledThreadPool(int) 创建定时调度任务线程池::核心线程数0,最大线程数Intageer.Max_Value,可能创建出最大数量的线程池,导致OOM
|
ExecutorService |
真正的线程池接口。 |
|
ScheduledExecutorService |
能和Timer/TimerTask类似,解决那些需要任务重复执行的问题。 |
|
ThreadPoolExecutor |
ExecutorService的默认实现。 |
|
ScheduledThreadPoolExecutor |
继承ThreadPoolExecutor的ScheduledExecutorService接口实现,周期性任务调度的类实现。 |
- 线程池执行流程:

- 线程池使用场景&推荐:
java.util.concurrent.Executors#newCachedThreadPool()
优点:如果当第二个任务开始,第一个任务已经执行结束,那么第二个任务会复用第一个任务创建的线程,并不会重新创建新的线程,提高了线程的复用率;
不足:这种方式虽然可以根据业务场景自动的扩展线程数来处理我们的业务,但是最多需要多少个线程同时处理缺是我们无法控制的;
备注:还是建议使用java.util.concurrent.ThreadPoolExecutor自己创建管理线程池,避免OOM风险
java.util.concurrent.Executors#newSingleThreadExecutor()
java.util.concurrent.Executors#newFixedThreadPool(int)
优点:newFixedThreadPool的线程数是可以进行控制的
不足:默认队列大小为Intageer.Max_Value,大量请求堆积,会产生OOM
java.util.concurrent.Executors#newScheduledThreadPool(int)
scheduleWithFixedDelay:上一个任务结束后的固定时间间隔后执行下一个任务
scheduleAtFixedRate:两个任务开始时间的间隔为initialDelay+period -> initialDelay + 2 * period(如果间隔时间大于任务执行时间,任务不受影响。如果间隔时间小于任务执行时间,那么执行结束后会立即执行,至此就会打乱间隔时间。)
备注:最大线程池大小Integer.MAX_VALUE。大量请求堆积,会产生OOM风险。定时任务也可以达到类似的效果。
- 常见线程池问题定位&排查建议:
- 创建线程是定义有意义的线程名称
- 打印日志时同时打印出对应线程名称
- 除非确定的短平快的线程任务才破例可以使用JDK默认的线程池,否则一律禁止使用JDK默认线程池
- 项目中建议写定时任务去监控线程池运行情况,以便实时监控和问题定位
privatevoid printStats(ThreadPoolExecutor threadPool) {
Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(() -> {
log.info("=========================");
log.info("Pool Size: {}", threadPool.getPoolSize());
log.info("Active Threads: {}", threadPool.getActiveCount());
log.info("Number of Tasks Completed: {}", threadPool.getCompletedTaskCount());
log.info("Number of Tasks in Queue: {}", threadPool.getQueue().size());
log.info("=========================");
}, 0, 1, TimeUnit.SECONDS);
}
- 每一个线程池的使用请确认是否可以复用
- 建议编码过程参考一些大公司的开发手册
- 参考资料:线程的优先级与控制:https://www.cnblogs.com/HelloBigTable/p/10827269.html
sleep(),wait(),yield()和join()方法的区别: https://blog.csdn.net/xiangwanpeng/article/details/54972952
JVM线程模型详解:https://blog.csdn.net/u014730165/article/details/81981154
volatile底层原理详解:https://zhuanlan.zhihu.com/p/133851347
Java内存模型:https://www.cnblogs.com/nexiyi/p/java_memory_model_and_thread.html
volatile底层原理详解:https://zhuanlan.zhihu.com/p/133851347
java笔记--关于线程同步(7种同步方式):https://www.cnblogs.com/XHJT/p/3897440.html
synchronized(this/.class/Object),synchronize方法区别:https://www.jianshu.com/p/4c1ed2048985
java中多线程产生死锁的原因以及解决意见:https://www.cnblogs.com/handsome1013/p/11232713.html
java并发---并发的基本概念:https://blog.csdn.net/weianluo/article/details/80081399
Java常见编程错误:线程池:https://www.cnblogs.com/liekkas01/p/12791700.html
AI大火腿 :痛苦预示着超脱
本文来自博客园,作者:AI大火腿,转载请注明原文链接:https://www.cnblogs.com/supperlhg/articles/15981919.html

浙公网安备 33010602011771号