您的位置:首页 > 其它

深入理解JVM虚拟机 线程安全与锁优化

2018-04-03 09:47 537 查看

java操作共享数据分类

不可变

不可变的对象一定是线程安全的。java中基本数据类型,只要被定义为final关键字则保证了不可变的。如果是一个对象,那就需要保证对象的行为不会对其状态产生影响—-把对象中带有状态的变量都声明为final。

绝对线程安全

相当严格,“不管运行时环境如何,调用者额都不需要任何额外的同步措施”。大多数都不是绝对的线程安全的。

相对线程安全

我们通常意义上说的线程安全,保证对这个对象单独的操作时线程安全的,调用不需要额外的措施。

线程兼容

对象本身并不是线程安全的,但是可以通过调用端正确的使用同步手段保证对象在并发环境中可以安全的使用。

线程对立

无论调用端是否采取同步措施,都无法在多线程环境中并发使用的代码,

线程安全的实现方法

互斥同步

同步:多个线程并发访问共享数据时,保证共享数据在同一个时刻只被一个线程使用。互斥是实现同步的一个手段。

java中同步手段为synchronized关键字,具体的可以查看本人的关于线程的博客。

互斥同步主要的问题是进行线程阻塞的唤醒所带来的性能问题,因此这种同步也称为阻塞同步

非阻塞同步

基于冲突检测的乐观并发策略,先进行操作,如果没有其他线程争用共享数据,那操作就成功,如果共享数据有争用,产生冲突,再采取其他的补偿措施(重试),不需要把线程挂起,因此是非阻塞的同步。

无同步方案

如果一个方法本来就不涉及共享数据,无须同步措施去保证正确性。

锁优化

自旋锁与自适应自旋

互斥同步对性能最大的影响是阻塞的实现,挂起线程和恢复线程的操作都需要转入内核态完成,会给系统带来很大的压力,但是共享数据的锁定状态只会至粗很短的时间,为了这段时间去挂起恢复线程不值得。

如果物理机器有一个以上的处理器,一个处理器正在处理已经获得锁的线程,那么可以让后面请求锁的线程在处理器中“等一下”,不放弃处理器的执行时间,看看持有锁的线程是否很快释放。实现它可以让线程执行忙循环(自旋)。JDK1.6后默认开启。-XX:+UserSpinning。自旋虽然避免了线程切换的开销,但是他占用了处理器的时间,如果锁占用的时间短,那效果就好,反之则会消耗处理器资源。因此自旋默认10次,可以通过参数与-XX:PreBlockSpin来更改。

JDK1.6引入自适应自旋锁,自旋时间不固定。

锁消除

虚拟机在编译器的运行时,对一些代码上要求同步,但是被检测到不可能存在共享数据竞争的锁进行消除。判断依据是逃逸分析的数据支持。如果一段代码,堆上的所有数据都不会逃逸出去从而被其他线程访问到,那么就可以把它当作栈上的数据对待,认为他们是线程私有的,同步加锁自然就无须进行。

举例

public String concatString(String s1,String s2,String s2){
return s1+s2+s3;
}


Javac编译器做自动优化

public String concatString(String s1,String s2,String s2){
StringBuffer sb=new StringBuffer();
sb.append(s1);
sb.append(s2);
sb.append(s3);
return sb.toString();
}


StringBuffer.append()方法都有一个同步块,锁为sb对象,但是虚拟机发现sb作用域在方法内部,永远不会逃逸,因此虽然有锁,他可会被安全的消除掉。

锁粗化

一般情况下推荐同步块的作用范围限制尽量小,这样如果存在锁竞争,等待锁的线程可能尽快拿到锁。

但是如果一系列的连续操作会对同一个对象反复加锁解锁,比如出现在循环体中,会导致不必要的性能损耗,以上的append()方法也属于这类情况。

如果虚拟机探测到这样的情况,(零碎加锁),则会把枷锁的同步范围粗化,只需要加锁一次

轻量级锁

并不是用来替代重量级锁,本意是在没有多线程竞争的前提下,减少传统的重量级锁使用操作系统互斥量产生的性能消耗。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: