cas+synchronized

4.CAS与原子类
4.1CAS
CAS即Compare and Swap,配合volatile使用的一种技术,它体现的是一种乐观锁的思想,也被称为无锁并发,比如多个线程要对一个共享的整型变量执行+1操作:

//需要不断尝试
while(true){
	int 旧值 = 共享变量;//比如拿到了当前值0
	int 结果 = 旧值 + 1;//在旧值0的基础上增加1,正确结果是1
	/*
	这时候如果别的线程把共享变量改成了5,本线程的正确结果1就作废了,这时候compareAndSwap返回false,重新尝试直到:compareAndSwap返回true,表示我本线程做修改的同时,别的线程没有干扰
	*/
	if (compareAndSwap(旧值, 结果)){
	//成功,退出
	}
}

获取共享变量时,为了保证该变量的可见性,需要使用volitile修饰。结合cas和volitile可以试下无锁并发,适用于竞争不激烈(否则效率受影响)、多核cpu(重试操作使用cpu时间,synchronized在等待过程中线程阻塞,必须其他线程释放锁才能恢复运行,只有一个cpu,重试就无从谈起了,别的线程在修改共享变量占用cpu,没有可用的cpu)的场景下。
1)因为没有使用synchronized,所以线程不会陷入阻塞(涉及到线程上下文的切换,即把当前的线程状态保存下来,在一边阻塞休眠,等待其他线程唤醒),这是效率提升的因素之一。
2)但如果竞争激烈,可以想到重试必然频繁发生,反而效率受影响。

cas底层依赖于一个Unsafe类来直接调用操作系统底层的cas指令,下面是直接使用Unsafe对象进行线程安全保护的例子。

4.2乐观锁与悲观锁
CAS是基于乐观锁的思想:最乐观的估计,不怕别的线程来修改共享变量,就算修改了也没有关系,我吃亏点再重试呗。
synchronized是基于悲观锁的思想:最悲观的估计,得防着其他线程来修改共享变量,我上了锁你们都别想改,我改完了解开锁,你们才有机会。
4.3原子操作类
juc(java.util.concurrent)中提供了原子操作类,可以提供线程安全的操作,例如:AtomicInteger、AtomicBoolean等,它们底层采用CAS技术+volatile来实现的。

  • synchronized优化
    Java HotSpot虚拟机中,每个对象都有对象头(包括class指针和Mark Word)。Mark Word平时存储这个对象的哈希码、分代年龄,当加锁时,这些信息就根据情况被替换为标记位、线程锁记录指针、重量级锁指针、线程ID等。
    5.1 轻量级锁
    如果一个对象虽然有多线程访问,但多线程访问的时间是错开的(也就是没有竞争),那么可以使用轻量级锁来优化。
    每个线程的栈帧记录都会包含一个锁记录的结构,内部可以存储锁定对象的Mark Word.
    相当于线程1和对象之间做了名片交换,对象将Mark word给了线程,线程将锁记录地址给了Mark word。将来解锁再将其换回来。
    在这里插入图片描述在这里插入图片描述在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    5.2 锁膨胀
    如果在尝试加轻量级锁的过程中,cas操作无法成功,这时有一种情况就是有其他线程为此对象加上了轻量级锁(有竞争),这时需要进行锁膨胀,将轻量级锁变为重量级锁。
    5.3重量锁-自旋
    重量级锁竞争的时候,还可以使用自旋来进行优化,如果当前线程自旋成功(即这时候持锁线程已经退出了同步块,释放了锁),这时当前线程就可以避免阻塞。
    在java6之后,自旋锁是自适应的,比如对象刚刚的一次自旋操作成功过,那么认为这次自旋成功的可能性会高,就多自旋几次;反之,就少自旋甚至不自旋,总之,比较智能。
    自旋会占用cpu时间,单核cpu自旋就是浪费,多核cpu自旋才能发挥优势。
    java7之后不能控制是否开启自旋功能。
    5.4偏向锁
    轻量级锁在没有竞争时(就自己这个线程),每次重入仍然需要执行CAS操作。Java6中引入了偏向锁来做进一步优化:只有第一次使用CAS将线程ID设置到对象的Mark Wor头,之后发现这个线程ID是自己的就表示没有竞争,不用重新cas。
  • 撤销偏向需要将持锁线程升级为轻量级锁,这个过程所有线程需要暂停(STW)
  • 访问对象的hashcode也会撤销偏向锁
  • 如果对象虽然被多个线程访问,但没有竞争,这时偏向了线程T1的对象仍有机会重新偏向T2,重新偏向会重置对象的Thread ID
  • 撤销偏向和重偏向都是批量进行的,以类为单位
  • 如果撤销偏向达到某个阈值,整个类的所有对象都会变为不可偏向的
  • 可以使用-xx:-UseBiasedLocking禁用偏向锁
    5.5 其他优化
    1.减少上锁时间
    同步代码块中尽量短,竞争机会减小,降低轻量级锁升级为重量级锁
    2.减少锁的粒度
    将一个锁拆分为多个锁提高并发度,例如:
    ConcurrentHashMap
    在这里插入图片描述
    LinkedBlockingQueue入队和出队使用不同的锁,相对于LinkedBlockingArray只有一个锁效率更高
    3.锁粗化
    多次循环进入同步块不如同步块内多次循环
    另外jvm可能会做如下优化,把多次append的加锁操作粗化为一次(因为都是对同一个对象加锁,没有必要重入多次)
    new StringBuffer().append(“a”).append(“b”).append(“c”);
    4.锁消除
    jvm会进行代码的逃逸分析,例如某个加锁对象是方法内局部变量,不会被其他线程所访问到,这时候就会被即时编译器忽略掉所有同步操作。
    5.读写分离
    CopyOnWriteArrayList
    CopyOnWriteSet
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值