关于线程安全的思考

road2master / 2023-05-03 / 原文

线程安全是什么?

维基百科:线程安全程序设计中的术语,指某个函数函数库多线程环境中被调用时,能够正确地处理多个线程之间的公用变量,使程序功能正确完成。

《Java并发编程实战(Java Concurrency In Practice)》的作者Brian Goetz:当多个线程同时访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那就称这个对象是线程安全的。”

实现线程安全的几种方式

同步的定义:同步是指在多线程环境中,控制多个线程

阻塞同步(悲观)

互斥是方法是手段,同步是目的。

使用

 

synchronized和ReentrantLock对比:

  • Java语法层面的同步,足够清晰,也足够简单;

  • 对开发者要求高,必须要在finally块中释放锁;

  • JVM可以在线程和对象的元数据中记录synchronized中锁的相关信息来进行优化,而Lock不行。

    如果synchronized能满足使用需求那就使用synchronized。

     

    ReentrantLock的高级功能:

    • 线程阻塞等待时可以中断;

    • 可以实现公平锁(在锁被释放时,等待锁的所有线程按照时间顺序来依次获得锁,性能差,吞吐量下降);

    • 锁可以绑定多个条件。

    •  

这是一种悲观的策略。不管共享数据有没有出现竞争,都会默认加上锁(不过JDK6下下的synchronized除外,JVM会通过线程和对象的元数据信息优化掉很大一部分不必要的加锁)。主要问题是进行

非阻塞同步(乐观无锁)

硬件层面提供的基于冲突检测的乐观并发策略,通俗地说就是不管共享数据有没竞争,直接操作;如果共享的数据的确出现竞争了,产生了冲突,那再进行其他的补偿措施,最常用的补偿措施是不断地重试,直到出现没有竞争的共享数据为止。

JDK下的Unsafe类中的native方法compareAndSwapInt,其中JUC下的AutomicInteger的incrementAndGet方法就使用到了。

无需同步

 

可重入代码的确具备上述特征,例如不依赖全局变量、存储在堆上的数据和公用的系统资源,用到的状态量都由参数中传入,不调用非可重入的方法等。因此,如果一个方法的返回结果是可以预测的,只要输入了相同的数据就都能返回相同的结果,那么它就满足可重入性的要求,即使这个方法被多个线程同时调用也不会出现线程安全问题。

可重入代码的另一个重要特征是,当一个线程在执行可重入代码时,它可以被另一个线程中断,并在中断后恢复执行,而不会影响程序的正确性。这是因为可重入代码只依赖于传入的参数和局部变量,它不会修改全局变量和其他线程共享的资源,因此不会对其他线程的执行产生影响。

总之,可重入代码具有预测性和可中断性两个特征,这些特征保证了可重入代码的线程安全性,使得它可以被多个线程同时调用而不会出现数据竞争、死锁等线程安全问题

 

把共享数据的代码保证在同一个线程中执行。

Java中的实现就是ThreadLocal。一般使用在存储用户数据上下文。

 

参考:《深入理解Java虚拟机》