多线程学习(6)几种锁机制

原创
04/10 16:42
阅读数 142

CAS 机制
适用场景:乐观认为并发不高,不需要阻塞,可以不上锁。
特点:不断比较更新,直到成功。
缺点:高并发cpu压力大;ABA问题。
ABA问题: 
CAS机制生效的前提是,取出内存中某时刻的数据,而在下时刻比较并替换。 
如果在比较之前,数据发生了变化,例如:A->B->A,即A变为B然后又变化A,那么这个数据还是发生了变化,但是CAS还是会成功。

Java中CAS机制使用版本号进行对比,避免ABA问题。

   //该方法实现了i++的非阻塞的原子操作   
   public final int getAndIncrement() {   
         for (;;) { //循环,使用CAS的经典方式,这是实现non-blocking方式的代价   
            int current = get();//得到现在的值     111  
            int next = current + 1;//通过计算得到要赋予的新值   
            if (compareAndSet(current, next)) //关键点,调用CAS原子更新,  222  
                 return current;   
         }   
     }   

synchronized

  1. 适用场景:悲观认为并发很高,需要阻塞,需要上锁。
  2. 特点:语言层面的优化,锁消除、锁粗化、偏向锁、轻量锁等等;可读性高。

ReentrantLock 和 Atomic类

以上两种并发工具都使用了CAS机制。 

在并发不高竞争不激烈时候,性能略低于synchronized;相反,并发高竞争激烈时候,性能高于synchronized。

ReentrantLock相对于synchronized:

ReentrantLock等待可中断,synchronized不可以。
ReentrantLock需要手动释放锁,synchronized不需要。
ReentrantLock可支持公平非公平锁,synchronized只支持非公平锁。
ReentrantLock没有语言层面的优化,底层实现机制AQS和CAS,synchronized有优化。
ReentrantLock可重入锁,synchronized不可重入,可能导致死锁。
ReentrantLock支持读写锁,可以提高高并发读操作。
synchronized由操作系统支持,涉及内核态和用户态的上下文切换,并发高时切换开销非常大。
ReentrantLock(AQS)依赖volatile int变量标示锁状态,结构为双向链表的等待队列,通过(cas+死循环)更改锁状态,一旦更新成功,标示竞争到锁。
 

什么是公平锁和非公平锁

  • 公平锁(Fair):加锁前检查是否有排队等待的线程,优先排队等待的线程,先来先得
  • 非公平锁(Nonfair):加锁时不考虑排队等待问题,直接尝试获取锁,获取不到自动到队尾等待

吞吐量非公平锁比公平锁大,但是非公平锁容易阻塞早已等待的线程,造成线程饥饿现象。

乐观锁/悲观锁

  • 悲观锁 

悲观锁,正如其名,它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度(悲观),因此,在整个数据处理过程中,将数据处于锁定状态。 悲观锁的实现,往往依靠数据库提供的锁机制 (也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)

  • 乐观锁 

乐观锁( Optimistic Locking ) 相对悲观锁而言,乐观锁假设认为数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则让返回用户错误的信息,让用户决定如何去做。 

相对于悲观锁,在对数据库进行处理的时候,乐观锁并不会使用数据库提供的锁机制。一般的实现乐观锁的方式就是记录数据版本。

锁优化

   JDK1.6中高校并发是一个重要改进。里面的给出的各种锁都是为了线程间更高效的共享数据。优化的方法有下面几种。这里的锁优化主要是针对synchronized关键字来说,它产生的是一种重量级的锁定(重量级的锁定不是用CAS),会有互斥,效率较低。而在JDK1.6后,引入的自旋锁,轻量级锁,偏向锁对互斥同步进行了优化,它们三种锁默认都是开启的。

1.锁消除,锁粗化。

锁消除:是判断当堆上的数据不会被其他线程访问到时,该线程上的同步加锁就无需进行。

锁粗化:由于加锁和解锁的开销很大,如果不断的加锁和解锁操作都是对于同一个对象,虚拟机会把整个加锁同步的范围扩张到操作序列的外部,就是只加一次锁。

2.自旋锁:互斥同步对性能最大的影响是阻塞的实现,挂起线程和恢复线程都需要转入内核中完成。现将本该要阻塞的线程不去挂起,不放弃处理器的执行时间,而是在那做一个忙循环(自旋),看看持有锁的线程是否很快释放锁。自旋的次数(循环的次数)是有限度的,默认是10次,如果没有获得锁就采用传统的方式去挂起线程。

3.轻量级锁:在线程没有竞争的时候,采用CAS操作,避免使用互斥量的开销。这里涉及到对象头的概念。

4.偏向锁:它相对于轻量级锁,减少了锁重入的开销,对于第一个获得锁的线程,后面的执行如果该锁没有被其他线程获取,则该线程将不再进行同步(CAS操作)。

轻量级锁和偏向锁都是在没有竞争的情况下出现,一旦出现竞争就会升级为重量级锁。

对于synchronized,锁的升级情况可能是 偏向锁—>轻量锁—>自适应自旋锁—>重量锁

 

展开阅读全文
打赏
0
0 收藏
分享
加载中
更多评论
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部