深入JVM 线程安全的实现

——互斥同步

互斥同步(Mutual Synchronization)是常见的一种并发正确性保障手段,同步是指多线程并发访问共享数据时,保证共享数据同一时刻只能被一个(或一些 信号量)线程使用。而互斥是同步的一种手段,临界区、互斥量、信号量都是主要的互斥实现手段,互斥是手段,同步是结果,互斥是方法,同步是目的

在Java语言层面,最基本的互斥同步手段就是synchronized关键字,synchronized关键字经过编译后,会在同步块前后分别形成MontiorEnter和MontiorExit字节码指令,这2个字节码都需要一个reference类型参数来指明需要锁定和释放的对象。如果程序中synchronized指明了对象,那就是该对象的reference 否则synchronized修饰的方法 根据是实例方法或类方法去获取对象实例或者Class对象来作为锁对象

根据虚拟机规范要求,在执行MonitorEnter指令时,首先尝试获取对象的锁,如果对象没有锁定,或者当前线程已经拥有了对象的锁,把锁的计数器+1 相应的MonitorExit指令执行时,计数器-1 当计数器为0时,锁被释放,如果对象获取锁失败,那么线程需要阻塞等待,直到对象锁被另一个线程释放

在虚拟机规范中MonitorEnter和MonitorExit的行为描述中,需要注意几点

Synchornized同步块对于同一线程是可重入的,不会出现自己将自己锁死的情况,其次,同步块在已进入的线程执行完之前,会阻塞后面线程的进入。 Java线程是映射到操作系统原生线程之上的,如果需要阻塞或唤醒一个线程,需要操作系统来协助,这就需要从用户态切换内核态,因此切换需要耗费处理器时间。所有synchronized是一个重量级的操作,所以应在不必须的情况下减少使用

除synchronized之外,还可以使用java.util.concurrent中的重入锁 Reentrantlock来实现同步,同样具备线程重入特性。同时引入高级功能

  • 等待可中断是指当持有锁的线程长时间不释放锁的时候,正在等待的线程可以放弃等待,改为处理其它事情
  • 公平锁是指多个线程在等待同一个锁时,必须按照申请锁的顺序来获得锁:而非公平锁不保证这一点,在锁释放时任何一个等待的线程都有机会获得锁。synchornized中的锁是不公平的
  • 锁绑定多个条件是指一个Reentrantlock对象可以同时绑定多个Condition对象,而Synchronized中,对象的wait和notify方法可以实现一个隐含的条件,如果要和多于一个条件关联时,不得不添加新的锁

——非互斥同步

互斥同步最主要的问题是进行线程阻塞和唤醒所带来的性能问题,因此这种同步也称为阻塞同步。从处理策略上讲,互斥同步是一种悲观并发策略,即认为如果不进行同步,必定会出现不一致错误,无论实际是否真的会出现竞争它都进行加锁,用户态内核态切换,维护锁计数器,和检查被阻塞线程是否需要唤醒。

随着硬件指令集的发展,一种新的思想出现:基于冲突检测的乐观并发策略,即先进行操作,如果没有其它线程争用共享数据,那操作成功,如果发生了争用冲突,那就采取补偿措施,乐观的并发策略不需要线程挂起,所以这类操作被称为非互斥同步

比较和交换 Compare and Swap CAS

CAS指令需要3个操作数,分别是内存位置(Java中可以理解为变量的内存地址) 旧的预期值(用A表示) 新值(用B表示) CAS指令执行时,当且仅当V符合预期值A时,处理器用新的值B更新V的值,否则不执行更新,但无论是否更新,都会返回V的旧值,并且该操作是一个原子操作

JDK1.5之后,sun.misc.unsafe类compareAndSwapInt()和compareAndSwapLong()等方法提供支持,由于unsafe类不是提供给用户调用的类,因此如果不使用反射手段,只能通过其它API间接使用它。如java.util.concrruent中的compareAndSet和getAndIncrement使用了CAS操作

如采用AtomicInteger代替Integer在并发环境中使用

尽管CAS看似很强,但无法涵盖互斥同步的所有使用场景,并且存在一定缺陷:如果一个变量V初次读取时是A值,并且准备赋值时检查到其仍然是A值,那我们就能说它的值没有被其它线程该变了吗? 显然不能,所有JDK中由通过版本标识等确定变量变化,因此CAS 并不见得更为高效

——无同步方案

要保证线程安全,并不一定需要同步,同步只是确保共享数据争用时的正确性手段,如果一个方法不涉及共享数据,则无需同步,因此一些代码天生就是线程安全的

可重入代码Reentrant Code 也被称为纯代码,可在代码执行任何时刻中断,转而执行另一段代码(递归调用),而在控制权

返回后,原本程序不会出错。相对于线程安全而言,可重入性是更基本特性,它保证线程安全,即所有可重入的代码是线程安全的

不依赖存储于堆上的数据和公用的系统资源,用到的状态量由参数传入,不调用非可重入方法

判断是否可重入依据:如果一个方法,它的返回结果在给定相同输入结果可预测,那它满足可重入特性,也即线程安全

猜你喜欢

转载自blog.csdn.net/qq_33369979/article/details/88618025
今日推荐