您的位置:首页 > 其它

JVM学习篇(4)之线程安全与锁优化

2016-01-21 16:50 183 查看

线程安全与锁优化

Java中线程安全

对共享数据的操作

1.        不可变:

不可变的对象一定是线程安全的。如String类。

2.        绝对线程安全

3.        相对线程安全

4.        线程兼容

5.        线程对立

线程安全的实现方法

1.        互斥同步(阻塞式同步)

1)        同步指的是:多个线程并发访问共享数据时,保证共享数据在同一时刻只能被一个线程使用。

2)        互斥指的是:同步的手段。如:临界区、互斥量和信号量。

3)        最基本的同步互斥手段:synchronized关键字。

a)        原理:synchronized关键字经过编译后,会在同步代码块前后分别形成monitorenter和monitorexit这两个字节码指令。

b)        在执行monitorenter指令时,首先要尝试获取对象锁。若这个对象没被设定锁,或已经拥有了这个对象锁,把锁的计数器+1。

c)        相应的执行monitorexit指令时,锁计数器-1,当为0时释放锁。

d)        若获得对象失败则阻塞当前线程。

4)        另一种手段:ReentrantLock来实现

reentrantLock增加的新功能:

a)        等待中断:当持有锁的线程长期不释放锁的时候,正在等待的线程可以选择放弃等待。

b)        公平锁:可以讲多个线程在等待同一个锁时,必须按照申请锁的时间顺序依次获得锁。

c)        锁绑定多个条件:一个ReentranLock对象可以同时绑定多个Condition对象。

5)        两种方式的比较:

a)        在JDK1.6之前在多线程环境中synchronized的吞吐量下降的严重,但ReentranLock性能几乎不变。

b)        在JDK1.6之后二者性能基本上持平。

c)        应该优先考虑使用synchronized来同步。

2.        非阻塞同步

1)        互斥同步属于一种悲观的并发策略。即总认为不去做同步措施就一定会出现问题,故无论共享数据是否真的会出现竞争,都要加锁。

2)        非线程阻塞:是基于冲突检测的乐观并发控制策略。即先操作,如果没有其它线程征用共享数据,则成功。如果共享数据有征用,采取补偿措施。(不断的重试,直到成功)

3.        无同步方案

如果方法中不涉及共享数据,那它自然无须任何同步措施去保证正确性。

1)        可重入代码

可重入的代码都是线程安全的,但并非所有的线程安全的代码都是可重入的。

2)        线程本地保存

锁优化

锁优化技术:适应性自旋、锁消除、锁粗化、轻量级锁和偏向锁。

适应性自旋

1.        自旋锁:当两个或两个以上的线程同时并行执行,让后面请求锁的那个线程“稍等一下”,但不放弃处理器的执行时间,看看持有所的线程是否能很快释放锁。

2.        缺点:自旋的线程只会白白消耗处理器资源,带来性能上的浪费。故需要使等待时间有一定的限度

3.        改进:等待时间要自适应。等待时间随着程序运行和性能监控信息的不断完善。

锁消除

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

2.        判断依据:源于逃逸分析的数据支持。若堆上的所有数据都不会逃逸出去从而被其他线程访问到,那就可以吧它们当作栈上的数据看待。

粗粒化

1.        上面出现的问题:如果一系列的连续操作都对同一个对象反复加锁和解锁,甚至加锁操作是出现在循环体中,那即使没有线程竞争,频繁地进行互斥同步操作也会导致不必要的性能损耗。

2.        措施:如果虚拟机探测到有这样一串零碎的操作都对同一个对象加锁,【将会把加锁同步的范围扩展到整个操作序列外部】。

轻量级锁

1.        加锁过程:

1)        代码进入到同步代码块的时候,如果此同步对象没有被锁定,将会在当前线程的栈帧中建立一个名为锁记录(Lock Record)空间,用来存储对象目前的头数据。

2)        将对象的头记录更新为指向Lock Record的指针。

a)        更新成功:那么这个线程就拥有了该对象的锁

b)        失败:检查对象的对象头是否指向当前线程的栈帧。

a)        是:说明当前线程已经拥有这个对象的锁,直接进入同步块中执行。

b)        否:说明这个锁对象已经被其他线程抢占。

3)        如果两条以上线程征用同一个锁,那轻量级锁失效。

2.        解锁过程:

1)        若对象的对象头仍然指向现成的锁记录,则把对象当前的Mark Word和线程中副本替换回来。

a)        成功:整个同步过程完成

b)        失败:说明其他线程尝试获取该锁,那就要释放锁的同时唤醒被挂起的线程。

偏向锁

1.        与轻量级锁的区别:

轻量级锁:是在无竞争情况下,消除同步使用的互斥量。

偏向锁:在无竞争情况下把整个同步都消除掉。

2.        加锁过程:

1)        当锁对象第一次被线程获取的时候,虚拟机包对象头中的标记为设为“01”。

2)        把获取到这个锁的线程的ID记录在对象的Mark Word之中。

a)        成功:持有偏向锁的线程以后每次进入这个锁相关的同步块时,可不再进行任何同步操作。

3.        解锁过程:

1)        当有另外一个线程去尝试获取这个锁时,偏向模式宣告结束。

2)        根据锁对象目前是否处于被锁定的状态,撤销偏向后恢复到“01”或“00”。

4.        优点:

提高带有同步但竞争的程序性能。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  JVM