深入理解jvm--线程安全与锁优化

提纲

线程安全

面向过程的编程思想:把数据和过程分别作为独立的部分来考虑,数据代表问题空间中的客体,程序代码则用于处理数据。

面向对象的编程思想:站在现实世界的角度去抽象和解决问题,把数据和行为都看做是对象的一部分。

当多个线程访问一个对象时,如果不用考虑这些线程在运行环境下的调度和交替运行,也不需要进行额外的同步,或者在调用方进行任何其它的协调操作,调用这个对象的行为都可以获得正确的结果,那这个结果是线程安全的。

Java语言中的线程安全

java语言中各种操作共享的数据分为5类:不可变、绝对线程安全、相对线程安全、线程兼容和线程对立。

1. 不可变:不可变的对象一定是线程安全的。

   基本数据类型使用final修饰可以保证它不可变。

   把对象中带有状态的变量都声明为final,这样在构造函数之后,它就是不可变的。

2. 绝对线程安全:不管运行时环境如何,调用者都不需要任何额外的同步措施。

3. 相对线程安全:保证这个对象单独的操作是线程安全的。大部分的线程安全类都属于这种类型,例如Vector、hashTable、Collections的synchronizedCollection()方法包装的集合等

4. 线程兼容:对象本身并不是安全的,但是可以通过在调用端正确地使用同步手段来保证对象在并发环境中可以安全地使用。大部分的类是线程兼容的,如ArrayList和HashMap等。

5. 线程对立:无论调用端是否采取了同步措施,都无法在多线程环境中并发使用的代码。会造成死锁,如Thread类的suspend()和resume()方法

线程安全的实现方法

1. 互斥同步:同步是指在多个线程并发访问共享数据时,保证共享数据在同一个时刻只被一个线程使用,而互斥则是同步的一种手段,临界区、互斥量、和信号量都是主要的互斥实现方式。

   java中最基本的互斥同步手段就是synchronized关键字,synchronized经过编译之后,分别形成monitorenter和monitorexit这两个字节码指令。

   首先,synchronized是对同一条线程来说是可重入的。其次,同步块在已进入的线程执行完之前,会阻塞后面的线程进入。而阻塞和唤醒线程都需要进行用户态到核心态的转换,所以synchronized是一个重量级锁。

   还可以使用concurrent中的重入锁(ReentrantLock)实现同步。ReentrantLock的高级功能:

   (1). 等待可中断:当持有锁的线程长期不释放锁的时候,正在等待的线程可已选择放弃等待,改为处理其他事情。

   (2). 公平锁:指多个线程在等待同一个锁时,必须按照申请锁的时间顺序来依次获得锁。可以通过带boolean值的构造函数实现公平锁。

   (3). 锁绑定多个条件:指一个ReentrantLock对象可以同时绑定多个Condition对象。而synchronized中,锁对象的wait()和notify()或notifyAll()可以实现一个隐含条件。

2. 非阻塞同步:基于冲突检测的乐观并发策略,就是先进行操作,如果没有其它线程征用共享数据,那操作成功;吐过共享数据争用,产生了冲突,那就再采取其他的补偿措施。这种乐观的并发策略很多实现都不需要把线程挂起,称为非阻塞同步。

  通过硬件保证操作和冲突检测这两个步骤具备原子性,硬件保证一个从语义上看起来需要多次操作的行为只通过一条处理器指令就能完成,这类指令常用的有:

   (1)测试并设置

   (2)获取并增加

   (3)交换

   (4)比较并交换(CAS)

   (5)加载链接/条件存储(LL/SC)

CAS操作可通过unsafe类里面的compareAndSwapInt()和compareAndSwapLong()等几个方法包装提供。由于unsafe类限制了只有启动类加载器加载,所以可通过JUC包里的整数原子类,通过compareAndSet()方法和getAndIncrement()等方法使用CAS操作。

CAS操作的缺点:只能检查值是否相等,不能检测出是否被改变过(先+1再-1)。

3.无同步方案:有些代码天生就是安全的,包括:

  (1)可重入代码:不依赖存储在堆上数据和公用的系统资源、用到的状态量都由参数传入、不调用非可重入的方法。如果一个方法,它的返回结果是可预测的,只要输入了相同的数据,就能返回相同的结果,那他就满足可重入性的要求。

  (2)线程本地存储 :保证共享数据在同一线程中运行。可以通过java.lang.ThreadLocal类实现线程本地存储的功能。

锁优化

自旋锁与自适应锁

自旋锁:为了避免线程挂起和恢复带来的时间消耗,可以让线程执行一个忙循环(自旋),这项技术就是所谓的自旋锁。

自适应锁:意味着自旋的时间不在固定,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。

参数:使用自旋锁-XX:+UseSpinning

      设定自旋次数-XX:PreBlockSpin

锁消除

虚拟机即时编译器在运行时,对一些代码上要求同步,但实际被检测到不可能存在共享数据竞争的锁进行消除。

如字符串相加,可以将第一次锁与最后一次保留,其它锁进行消除

锁粗化

如果虚拟机检测到有这样一串零碎的操作都对同一个对象加锁(锁粗化),将会吧加锁的同步范围扩展到整个操作序列的外部。

轻量级锁(对象头)

在无竞争的情况下,使用CAS操作完成加锁和解锁,消除同步使用的互斥量。先复制当前对象头到锁记录中,当栈帧到达该对象时,使该对象指向所记录中的对象头并修改所标志位完成加锁


偏向锁(对象头)

在无竞争的情况下把整个同步都消除掉。

这个锁会偏向于第一个获得它的线程,如果接下来该所没有被其它线程获取,则持有偏向锁的线程将永远不需要进行同步。即持有偏向锁的线程以后每次进入这个锁相关的同步块时,虚拟机都可以不再进行同步操作。当有另外一个线程获取这个锁时,偏向模式结束。


猜你喜欢

转载自blog.csdn.net/yinweicheng/article/details/80918901