Java---------synchronized锁的优化

JDK1.6之后对于synchronized锁的优化

如果想了解synchronized的底层实现,以及用法请参考我的上一篇及上上(或者上上上一篇???)一篇博客。

在JDK1.5中,synchronized是性能低效的。因为这是一个重量级操作,它对性能最大的影响是阻塞的是实现,挂起 线程和恢复线程的操作都需要转入内核态中完成,这些操作给系统的并发性带来了很大的压力。相比之下使用Java 提供的Lock对象,性能更高一些。

到了JDK1.6,发生了变化,对synchronize加入了很多优化措施,有自适应自旋,锁消除,锁粗化,轻量级锁,偏 向锁等等。导致在JDK1.6上synchronize的性能并不比Lock差。官方也表示,他们也更支持synchronized,在未来 的版本中还有优化余地,所以还是提倡在synchronized能实现需求的情况下,优先考虑使用synchronized来进行 同步。

 synchronized优化 :

了解了Synchronized的底层实现就知道,它最大的特征就是在同一时刻只有一个线程能够获得对象的监视器 (monitor),从而进入到同步代码块或者同步方法之中,即表现为互斥性(排它性)。这种方式肯定效率低下, 每次只能通过一个线程,既然每次只能通过一个,这种形式(互斥:保证安全)不能改变的话,那么我们能不能让每次通过的速度变快 一点呢。

1、CAS操作(无锁实现的同步—乐观锁)-自旋

之前synchronized获取锁失败是将线程挂起-悲观锁策略(假设只要当前线程进入同步方法或者同步代码块,一定有人竞争,所以挂起。直到有人通知我)

public class Test implements Runnable{

private int i=0;

public synchronized void setI(){

System.out.println(Thread.currentThread().getName()+"----"+i);

this.i=10;

System.out.println(Thread.currentThread().getName()+"----"+i);

}

public static void main(String[]args){

Runnable runnable=new Test();

Thread thread1=new Thread(runnable);                       

Thread thread2=new Thread(runnable);

Thread thread3=new Thread(runnable);

thread1.start();

thread2.start();

thread3.start();

}

public void run(){

    System.out.println(Thread.currentThread().getName()+"----"+i);

setI();

}

}

 

Compare And Swap(O,V,N)

O 预期的值 (旧值);

V 内存地址存放的实际值;

N 更新的新值

当O==V时,此时表示没有线程修改共享变量的值,此时可以成功的将内存中的值修改为N

当O!=V时,此时表示内存中共享变量的值已被其他线程修改,此时返回将内存中的最新值V,再次尝试修改变量

For/while 循环,减少了挂起阻塞操作

自旋带来的问题

1、ABA问题:

解决方法:添加版本号,比较的时候不仅比较值,还要比较版本号

i = 0(初始化)

 

线程1

线程2

线程3

 

 

 

i= 1

I = 0

i= 0

 

 

 

2、自旋在CPU上跑无用指令,会浪费CPU资源

解决方法:JVM尝试自旋一段时间,若在此时间段内,线程成功获取到锁,再下次获取锁时,适当延长自旋时间

若在此时间段内,线程没有获取到锁,再下次获取锁时,适当减少自旋时间

3、公平性问题:处于阻塞态线程比处于自旋状态的锁更慢获取到锁

处于阻塞状态的锁可能一直无法获取到锁

解决方案:只有LOCK锁可以实现公平性,synchronized无法实现公平锁

1、偏向锁:最乐观的一种锁(进入同步方法或同步块的始终是一个线程)

JDK1.6之后默认synchronized。

当出现另一个线程也尝试获取锁(在不同的时刻)时,偏向锁会升级为轻量级锁

2、轻量级锁

不同时刻有不同的线程尝试获取锁,“由于深夜十字路口的车辆来往可能比较少,如果还设置红 绿灯交替,那么很有可能出现四个方向仅有一辆车在等红灯的情况。

因此,红绿灯可能被设置为闪黄灯的情况,代表车辆可以自由通过,但是司机需要注意观察。

同一时刻有不同的线程尝试获取锁,会将偏向锁升级为重量锁

3、重量级锁

JDK1.6之前的synchronized都是重量级锁,就是将线程阻塞挂起

锁只有升级,没有降级

 

Java虚拟机中synchronized关键字的实现,按照代价由高到低可以分为重量级锁、轻量锁和偏向锁三种。

1. 重量级锁会阻塞、唤醒请求加锁的线程。它针对的是多个线程同时竞争同一把锁的情况。JVM采用了自适应自旋,来避免线程在面对非常小的synchronized代码块时,仍会被阻塞、唤醒的情况。

2. 轻量级锁采用CAS操作,将锁对象的标记字段替换为一个指针,指向当前线程栈上的一块空间,存储着锁对象原本的标记字段。它针对的是多个线程在不同时间段申请同一把锁的情况。

3. 偏向锁只会在第一次请求时采用CAS操作,在锁对象的标记字段中记录下当前线程的地址。在之后的运行过程中,持有该偏向锁的线程的加锁操作将直接返回。它针对的是锁仅会被同一线程持有的情况。

 

2、锁粗化:当多次连续的加锁与解锁过程,会将多次加减锁过程粗化为一次加锁与解锁的过程

锁粗化就是将多次连接在一起的加锁、解锁操作合并为一次,将多个连续的锁扩展成为一个范围更大的锁。举例如 下:

public class Test{

private static StringBuffer sb = new StringBuffer();

public static void main(String[] args) {

sb.append("a");

sb.append("b");

sb.append("c");

}

}

这里每次调用stringBuffffer.append方法都需要加锁和解锁,如果虚拟机检测到有一系列连串的对同一个对象加锁

和解锁操作,就会将其合并成一次范围更大的加锁和解锁操作,即在第一次append方法时进行加锁,最后一次

append方法结束后进行解锁。

 

3、锁消除:当对象不属于共享资源时,对象内部的同步方法或同步代码块的锁会自动解除

锁消除即删除不必要的加锁操作。根据代码逃逸技术,如果判断到一段代码中,堆上的数据不会逃逸出当前线程,

那么可以认为这段代码是线程安全的,不必要加锁。看下面这段程序:

public class Test{

public static void main(String[] args) {

StringBuffer sb = new StringBuffer();

sb.append("a").append("b").append("c");

}

}

虽然StringBuffffer的append是一个同步方法,但是这段程序中的StringBuffffer属于一个局部变量,并且不会从该方

法中逃逸出去,所以其实这过程是线程安全的,可以将锁消除。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值