多线程并发-synchronized 和CAS锁的性能差别

一、认识锁:

       在多线程并发情况下,当写操作比较频繁时,有可能导致数据脏读的情况,为了解决数据脏读的问题,在jdk1.6之前,已经给我们提供synchronized 关键字,不管线程访问的目的是还是写,统统先加一把锁。

 

二、锁的分类:

       1)synchronized(同步锁,也叫独占锁),它是一种悲观锁,可以加在方法上,表示线程进入该方法,需要竞争锁;当然也可以修饰代码块,针对类的对象设置锁。

       如果synchronied修饰静态方法,表示在类上增加了锁(类.class);这个范围就比对象锁大。这里就算是不同对象,但是只要是该类的对象,就使用的是同一把锁。多个线程调用该类的同步的静态方法时,都会阻塞。

       如果修饰非静态方法,表示增加了对象锁(this),在多线程运行情况下,如果传入同一个对象,当访问对象方法时,是同步锁。如果是不同对象,访问方法时,不会阻塞。

       2)乐观锁:

乐观锁:乐观锁是所有线程访问共享资源都不会发生冲突,既然不会发生冲突就不会阻塞其他线程,那么线程不会出现阻塞等待的状态。
每次操作都认为不会发生冲突,尝试执行,并检测结果是否正确。如果正确则执行成功,否则发生了冲突,回退再重新尝试直到没有冲突。其实重试时间很快,差不多几毫秒,这个线程在尝试的过程一直在cpu上跑,只是跑的是无用指令,那么就没有从用户态切换到内核态,开销因此变小。

在Java多线程中,乐观锁一个常见实现是CAS操作。

CAS操作过程:
CAS(比较交换)。
其具有三个操作数:

    V:当前内存地址实际存放的值;
    O:内存存放预期值(旧值);
    N :更新的新值。

当V=0,预期值和内存实际值相等,内存存放的实际值没有被任何其他线程修改,即O就是目前最新的值,可以将新值N赋给V;

当V!=O,表明内存存放的实际值已经被其他线程修改,因此O值不是当前最新值,返回V,无法修改。(将V返回是因为再次重试时期待值就变为V)

当多个线程使用CAS操作时一个变量时,只有一个线程会成功,并成功更新,其余线程均会失败。失败的线程会重新尝试(自旋),当然也可以选择重新挂起(阻塞)。自旋,当系统资源竞争激烈的情况下,会导致频繁的上下线。

当多个线程使用CAS操作时一个变量时,只有一个线程会成功,并成功更新,其余线程均会失败。失败的线程会重新尝试(自旋),当然也可以选择重新挂起(阻塞)。

三、汇总一下:

  在jdk1.6中,有四种锁:

  1)无锁:

  2)轻量级锁:

        轻量级锁:多个线程在不同时段获取同一把锁,即不存在锁竞争的情况,也就没有线程阻塞。针对这种情况,JVM采用轻量级锁来避免线程的阻塞与唤醒。

  3)偏向锁:

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

       HotSpot的作者经过研究发现,大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让线程获得锁的代价更低而引入了偏向锁。
偏向锁是四种状态中最乐观的一种锁:从始至终只有一个线程请求某一把锁。

  4)重量级锁:

        当其他线程试图获取锁时,都会被阻塞,只有持有锁的线程释放锁后才会唤醒这些线程来进行竞争。

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

    重量级锁会阻塞、唤醒请求加锁的线程。它针对的是多个线程同时竞争同一把锁的情况。JVM采用了自适应自旋(如果获取到锁就返回,失败就阻塞),来避免线程在面对非常小的synchronized代码块时,仍会被阻塞、唤醒的情况。
    轻量级锁采用CAS操作,将锁对象的标记字段替换为一个指针,指向当前线程栈上的一块空间,存储着锁对象原本的标记字段。它针对的是多个线程在不同时间段申请同一把锁的情况。
    偏向锁只会在第一次请求时采用CAS操作,在锁对象的标记字段中记录下当前线程的地址。在之后的运行过程中,持有该偏向锁的线程的不用加锁操作将直接返回。它针对的是锁仅会被同一线程持有的情况。

 

4、当并发量比较大是,sychronized的性能会比CAS块,当然当查询量很少是,可以直接执行CAS对应的指令,减少了上下文的缺陷,大大提升了工作效率。
 

你可能感兴趣的:(多线程并发-synchronized 和CAS锁的性能差别)