关于线程安全的思考
线程安全是什么?
维基百科:线程安全是程序设计中的术语,指某个函数、函数库在多线程环境中被调用时,能够正确地处理多个线程之间的公用变量,使程序功能正确完成。
《Java并发编程实战(Java Concurrency In Practice)》的作者Brian Goetz:当多个线程同时访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那就称这个对象是线程安全的。”
实现线程安全的几种方式
同步的定义:同步是指在多线程环境中,控制多个线程
阻塞同步(悲观)
互斥是方法是手段,同步是目的。
使用
synchronized和ReentrantLock对比:
-
Java语法层面的同步,足够清晰,也足够简单;
-
对开发者要求高,必须要在finally块中释放锁;
-
JVM可以在线程和对象的元数据中记录synchronized中锁的相关信息来进行优化,而Lock不行。
如果synchronized能满足使用需求那就使用synchronized。
ReentrantLock的高级功能:
-
线程阻塞等待时可以中断;
-
可以实现公平锁(在锁被释放时,等待锁的所有线程按照时间顺序来依次获得锁,性能差,吞吐量下降);
-
锁可以绑定多个条件。
这是一种悲观的策略。不管共享数据有没有出现竞争,都会默认加上锁,主要问题是进行
(不过JDK6下的synchronized除外,JVM会通过线程和对象的元数据信息优化掉很大一部分不必要的加锁。
synchronized的升级过程:偏向锁 --自旋+CAS的方式两个线程来抢锁--> 轻量级锁 --自旋等待的过程中,锁仍未释放,就会升级为重量级锁--> 重量级锁:OS级别的上下文用户态到内核态的转换)。 -
非阻塞同步(乐观无锁)
硬件层面提供的基于冲突检测的乐观并发策略,通俗地说就是不管共享数据有没竞争,直接操作;如果共享的数据的确出现竞争了,产生了冲突,那再进行其他的补偿措施,最常用的补偿措施是
JDK下的Unsafe类中的native方法compareAndSwapInt,其中JUC下的AutomicInteger的incrementAndGet方法就使用到了。
无需同步
可重入代码
定义:一个方法的返回结果对于相同的输入始终相同。
可重入代码的确具备上述特征,例如不依赖全局变量、存储在堆上的数据和公用的系统资源,用到的状态量都由参数中传入,不调用非可重入的方法等。因此,如果一个方法的返回结果是可以预测的,只要输入了相同的数据就都能返回相同的结果,那么它就满足可重入性的要求,即使这个方法被多个线程同时调用也不会出现线程安全问题。
可重入代码的另一个重要特征是,当一个线程在执行可重入代码时,它可以被另一个线程中断,并在中断后恢复执行,而不会影响程序的正确性。这是因为可重入代码只依赖于传入的参数和局部变量,它不会修改全局变量和其他线程共享的资源,因此不会对其他线程的执行产生影响。
总之,可重入代码具有预测性和可中断性两个特征,这些特征保证了可重入代码的线程安全性,使得它可以被多个线程同时调用而不会出现数据竞争、死锁等线程安全问题。
线程本地存储
把共享数据的代码保证在同一个线程中执行。
Java中的实现就是ThreadLocal。一般使用在存储用户数据上下文。
参考:《深入理解Java虚拟机》
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧