synchronized锁优化

因为重量级锁总是在用户态和内核态之间转换,大大消耗性能。因此,jdk1.6之后对synchronized进行优化。

一、锁优化手段

1.自旋锁与自适应自旋

共享数据的锁定状态只会持续很短的一段时间, 为了这段时间去挂起和恢复线程并不值得。 现在绝大多数的个人电脑和服务器都是多路(核) 处理器系统, 如果物理机器有一个以上的处理器或者处理器核心, 能让两个或以上的线程同时并行执行, 我们就可以让后面请求锁的那个线程“稍等一会”, 但不放弃处理器的执行时间, 看看持有锁的线程是否很快就会释放锁。 为了让线程等待, 我们只须让线程执行一个忙循环(自旋) , 这项技就是所谓的自旋锁。

a线程占有锁,如果b线程来获取锁发现a线程正在用,之前的逻辑是此时b线程应该挂起了(用户态转换到内核态,然后才能操作系统资源挂起线程),但是a线程占用的时间很短,不至于再让b线程挂起。因此,现在的策略是让b线程自旋等待,就是b线程依然占用cpu资源通过自旋的方式等待a线程释放锁。如果a线程还没有释放锁,那就让b线程现在的状态一直升级到重量级锁,这时候才可以进入阻塞状态。

JDK 6中对自旋锁的优化, 引入了自适应的自旋。 自适应意味着自旋的时间不再是固定的了, 而是由
前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定的。

道格李把【自旋】用到极致。
在看了AQS+ReentrantLock+ConcurrentHashMap+Semaphore的源码的时候,也就是juc下包类的源码的时候,你始终离不开两个核心:1.自旋(for或者while) 2.CAS。

2.锁粗化

stringbuffer的append方法是被synchronized修饰的,调用三次append方法,如果每次都syn太消耗性能,因此锁粗化-相当于把三个append使用一个syn同步。

public void test(){
   buffer.append("zhangsan");
   buffer.append("lisi");
   buffer.append("wangwu");
}

锁粗化底层使用的是“逃逸分析”中的第一条。

2.1逃逸分析

使用逃逸分析,编译器可以对代码做如下优化:
一、同步省略。如果一个对象被发现只能从一个线程被访问到,那么对于这个对象的操作可以不考虑同步。
二、将堆分配转化为栈分配。如果一个对象在子程序中被分配,要使指向该对象的指针永远不会逃逸,对象可能是栈分配的候选,而不是堆分配。

User:id、name、age、address。

public String void test(){
  User user = new User();
  user.setId = 1;
  user.setName = "zhangsan";
  return user.getName();
}

如上案例:
user这个对象不会在堆上分配,因为最终返回的结果只是一个字符串,user对象随着方法执行的结束而被销毁,因此jvm不会把user放到堆中,而是栈上。

三、分离对象或标量替换。有的对象可能不需要作为一个连续的内存结构存在也可以被访问到,那么对象的部分(或全部)可以不存储在内存,而是存储在CPU寄存器中。

标量替换:
还是上面的代码案例,即使在栈上,也不是存储完整的user对象中的内容的,栈上只存储user.name的值和user.id的值,不会分配age、address的空间。

是不是所有的对象和数组都会在堆内存分配空间?
不一定

3.锁消除

锁消除是指虚拟机即时编译器在运行时, 对一些代码要求同步, 但是对被检测到不可能存在共享
数据竞争的锁进行消除。
在这里插入图片描述

4.锁膨胀升级

4.1偏向锁
4.2轻量级锁

二、结语

经过Orcale公司的诸多努力,现在的synchronzied性能与Lock的性能不看上下了。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

@来杯咖啡

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值