SYNCHRONIZED和LOCK比较浅析

synchronized是基于jvm底层实现的数据同步,lock是基于Java编写,主要通过硬件依赖CPU指令实现数据同步。下面一一介绍

一、synchronized的实现方案

  1.synchronized能够把任何一个非null对象当成锁,实现由两种方式:

  a.当synchronized作用于非静态方法时,锁住的是当前对象的事例,当synchronized作用于静态方法时,锁住的是class实例,又因为Class的相关数据存储在永久带,因此静态方法锁相当于类的一个全局锁。

  b.当synchronized作用于一个对象实例时,锁住的是对应的代码块。

  2.synchronized锁又称为对象监视器(object)。

3.当多个线程一起访问某个对象监视器的时候,对象监视器会将这些请求存储在不同的容器中。

  >Contention List:竞争队列,所有请求锁的线程首先被放在这个竞争队列中

  >Entry List:Contention List中那些有资格成为候选资源的线程被移动到Entry List中

  >Wait Set:哪些调用wait方法被阻塞的线程被放置在这里

  >OnDeck:任意时刻,最多只有一个线程正在竞争锁资源,该线程被成为OnDeck

  >Owner:当前已经获取到所资源的线程被称为Owner

  > !Owner:当前释放锁的线程

  下图展示了他们之前的关系

  4.synchronized在jdk1.6之后提供了多种优化方案:

  >自旋锁

    jdk1.6之后默认开启,可以使用参数-XX:+UseSpinning控制,自旋等待不能代替阻塞,且先不说对处理器数量的要求,自旋等待本身虽然避免了线程切换的开销,但它是要占用处理器时间的,因此,如果锁被占用的时间很短,自旋等待的效果就会非常好,反之,如果锁被占用的时候很长,那么自旋的线程只会白白消耗处理器资源,而不会做任何有用的工作,反而会带来性能上的浪费。自旋次数的默认值是 10 次,用户可以使用参数 -XX:PreBlockSpin 来更改。

    自旋锁的本质:执行几个空方法,稍微等一等,也许是一段时间的循环,也许是几行空的汇编指令。

  >锁消除

    即时编译器在运行时,对一些代码上要求同步,但是被检测到不可能存在共享数据竞争的锁进行消除,依据来源于逃逸分析的数据支持,那么是什么是逃逸分析?对于虚拟机来说需要使用数据流分析来确定是否消除变量底层框架的同步代码,因为有许多同步的代码不是自己写的。

例1.1

public static String concatString(String s1, String s2, String s3) {
    return s1 + s2 + s3;
}  

  由于 String 是一个不可变的类,对字符串的连接操作总是通过生成新的 String 对象来进行的,因此 Javac 编译器会对 String 连接做自动优化。在 JDK 1.5 之前,会转化为 StringBuffer 对象的连续 append() 操作,在 JDK 1.5 及以后的版本中,会转化为 StringBuilder 对象的连续 append() 操作,这里的stringBuilder.append是线程不同步的(假设是同步)。

  Javac 转化后的字符串连接代码为:

public static String concatString(String s1, String s2, String s3) {
    StringBuffer sb = new StringBuffer();
    sb.append(s1);
    sb.append(s2);
    sb.append(s3);
    return sb.toString();
}  

  此时的锁对象就是sb,虚拟机观察变量 sb,很快就会发现它的动态作用域被限制在 concatString() 方法内部。也就是说,sb 的所有引用永远不会 “逃逸” 到concatString() 方法之外,其他线程无法访问到它,虽然这里有锁,但是可以被安全地消除掉,在即时编译之后,这段代码就会忽略掉所有的同步而直接执行了。

  >锁粗化

  将同步块的作用范围限制得尽量小——只在共享数据的实际作用域中才进行同步,这样是为了使得需要同步的操作数量尽可能变小,如果存在锁竞争,那等待锁的线程也能尽快拿到锁。

  >轻量级锁

  加锁过程:在代码进入同步块的时候,如果此同步对象没有被锁定(锁标志位为 “01” 状态)虚拟机首先将在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用于存储锁对象目前的 Mark Word 的拷贝,这时候线程堆栈与对象头的状态如图 13-3 所示

   

  然后,虚拟机将使用 CAS 操作尝试将对象的 Mark Word 更新为指向 Lock Record 的指针。如果这个更新动作成功了,那么这个线程就拥有了该对象的锁,并且对象 Mark Word 的锁标志位 (Mark Word 的最后 2bit)将转变为 “00”,即表示此对象处于轻量级锁定状态,这时线程堆栈与对象头的状态如图13-4                

  如果上述更新操作失败,则说明这个锁对象被其他锁占用,此时轻量级变为重量级锁,标志位为“10”,后面等待的线程进入阻塞状态。

  解锁过程:也是由CAS进行操作的,如果对象的 Mark Word 仍然指向着线程的锁记录,那就用 CAS 操作把对象当前的 Mark Word 和线程中复制的 Displaced Mark Word 替换回来,如果替换成功,整个同步过程就完成了。如果替换失败,说明有其他线程尝试过获取该锁,那就要释放锁的同时,唤醒被挂起的线程。

  轻量级锁能提升程序同步性能的依据是 “对于绝大部分的锁,在整个同步周期内都是不存在竞争的”,这是一个经验数据。如果没有竞争,轻量级锁使用 CAS 操作避免了使用互斥量的开销,但如果存在锁竞争,除了互斥量的开销外,还额外发生了 CAS 操作,因此在有竞争的情况下,轻量级锁会比传统的重量级锁更慢。

  >偏向锁

  偏向锁也是 JDK 1.6 中引入的一项锁优化,它的目的是消除数据在无竞争情况下的同步原语,进一步提高程序的运行性能。如果说轻量级锁是在无竞争的情况下使用 CAS 操作去消除同步使用的互斥量,那偏向锁就是在无竞争的情况下把整个同步都消除掉,连 CAS 操作都不做了。

  实质就是设置一个变量,判断这个变量是否是当前线程,是就避免再次加锁解锁操作,从而避免了多次的CAS操作。坏处是如果一个线程持有偏向锁,另外一个线程想争用偏向对象,拥有者想释放这个偏向锁,释放会带来额外的性能开销,但是总体来说偏向锁带来的好处还是大于CAS的代价的。在具体问题具体分析的前提下,有时候使用参数 -XX:-UseBiasedLocking 来禁止偏向锁优化反而可以提升性能。

二、lock的实现方案

  与synchronized不同的是lock是纯java手写的,与底层的JVM无关。在java.util.concurrent.locks包中有很多Lock的实现类,常用的有ReenTrantLock、ReadWriteLock(实现类有ReenTrantReadWriteLock)

,其实现都依赖java.util.concurrent.AbstractQueuedSynchronizer类(简称AQS),实现思路都大同小异,因此我们以ReentrantLock作为讲解切入点。

分析之前我们先来花点时间看下AQS。AQS是我们后面将要提到的CountDownLatch/FutureTask/ReentrantLock/RenntrantReadWriteLock/Semaphore的基础,因此AQS也是Lock和Excutor实现的基础。它的基本思想就是一个同步器,支持获取锁和释放锁两个操作。

  

  要支持上面锁获取、释放锁就必须满足下面的条件:

  1、  状态位必须是原子操作的

  2、  阻塞和唤醒线程

  3、  一个有序的队列,用于支持锁的公平性

  场景:可定时的、可轮询的与可中断的锁获取操作,公平队列,或者非块结构的锁。

  主要从以下几个特点介绍:

  1.可重入锁

    如果锁具备可重入性,则称作为可重入锁。像synchronized和ReentrantLock都是可重入锁,可重入性在我看来实际上表明了锁的分配机制:基于线程的分配,而不是基于方法调用的分配。

  2.可中断锁

    可中断锁:顾名思义,就是可以相应中断的锁。

    在Java中,synchronized就不是可中断锁,而Lock是可中断锁。

    如果某一线程A正在执行锁中的代码,另一线程B正在等待获取该锁,可能由于等待时间过长,线程B不想等待了,想先处理其他事情,我们可以让它中断自己或者在别的线程中中断它,这种就是可中断锁。

  3.公平锁和非公平锁

     公平锁以请求锁的顺序来获取锁,非公平锁则是无法保证按照请求的顺序执行。synchronized就是非公平锁,它无法保证等待的线程获取锁的顺序。而对于ReentrantLock和ReentrantReadWriteLock,它默认情况下是非公平锁,但是可以设置为公平锁。

    参数为true时表示公平锁,不传或者false都是为非公平锁。

ReentrantLock lock = new ReentrantLock(truehttp://www.qwangxiao.com/k/gaozhong/);

  4.读写锁

  读写锁将对一个资源(比如文件)的访问分成了2个锁,一个读锁和一个写锁。

  正因为有了读写锁,才使得多个线程之间的读操作不会发生冲突。

  ReadWriteLock就是读写锁,它是一个接口,ReentrantReadWriteLock实现了这个接口。

  可以通过readLock()获取读锁,通过writeLock()获取写锁。

三、总结

  1.synchronized

  优点:实现简单,语义清晰,便于JVM堆栈跟踪,加锁解锁过程由JVM自动控制,提供了多种优化方案,使用更广泛

  缺点:悲观的排他锁,不能进行高级功能

  2.lock

  优点:可定时的、可轮询的与可中断的锁获取操作,提供了读写锁、公平锁和非公平锁  

  缺点:需手动释放锁unlock,不适合JVM进行堆栈跟踪

  3.相同点 

  都是可重入锁

时间: 2024-09-29 10:51:04

SYNCHRONIZED和LOCK比较浅析的相关文章

synchronized 与 Lock 的那点事

最近在做一个监控系统,该系统主要包括对数据实时分析和存储两个部分,由于并发量比较高,所以不可避免的使用到了一些并发的知识.为了实现这些要求,后台使用一个队列作为缓存,对于请求只管往缓存里写数据.同时启动一个线程监听该队列,检测到数据,立即请求调度线程,对数据进行处理. 具体的使用方案就是使用同步保证数据的正常,使用线程池提高效率. 同步的实现当然是采用锁了,java中使用锁的两个基本工具是 synchronized 和 Lock. 一直很喜欢synchronized,因为使用它很方便.比如,需要

Java中synchronized和Lock的区别

synchronized和Lock的区别synchronize锁对象可以是任意对象,由于监视器方法必须要拥有锁对象那么任意对象都可以调用的方法所以将其抽取到Object类中去定义监视器方法这样锁对象和监视器对象是同一个,只要创建了锁对象它既是锁对象同时也是监视器对象这样不能实现在一个锁对象上绑定多个监视器对象jdk1.5中Lock对象仅仅是一个锁对象监视器方法被封装到了Condition对象中这样实现了锁对象和监视器对象进行了分离更加的面向对象这样可以实现在一个锁对象上绑定多个监视器对象 在一个

线程安全、数据同步之synchronized与Lock

线程安全.数据同步之synchronized与Lock 技术交流1群:46523908 技术交流2群:46505645 NoHttp 源码及Demo托管在Github欢迎大家Star:https://github.com/Y0LANDA/NoHttp NoHttp是专门做Android网络请求与下载的框架. 本文Demo下载传送门 写在前面 本篇文章讲的东西都是Android开源网络框架NoHttp的核心点,当然线程.多线程.数据安全这是Java中就有的,为了运行快我们用一个Java项目来讲解.

java多线程并发(一)——Semaphore,volatile,synchronized ,Lock

在并发编程中,我们通常会遇到以下三个问题:原子性问题,可见性问题,有序性问题.我们先看具体看一下这三个概念: 1.原子性 原子性:即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行. 一个很经典的例子就是银行账户转账问题 2.可见性 可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值. 3.有序性 有序性:即程序执行的顺序按照代码的先后顺序执行. Semaphore 简介 信号量(Semaphore),有时被称为信号

线程安全、数据同步之 synchronized 与 Lock

本文Demo下载传送门 写在前面 本篇文章讲的东西都是Android开源网络框架NoHttp的核心点,当然线程.多线程.数据安全这是Java中就有的,为了运行快我们用一个Java项目来讲解. 为什么要保证线程安全/数据同步 当多个子线程访问同一块数据的时候,由于非同步访问,所以数据可能被同时修改,所以这时候数据不准确不安全. 现实生活中的案例 假如一个银行帐号可以存在多张银行卡,三个人去不同营业点同时往帐号存钱,假设帐号原来有100块钱,现在三个人每人存钱100块,我们最后的结果应该是100 +

synchronized与lock 对象锁、互斥锁、共享锁以及公平锁和非公平锁

synchronized与lock  都是用来实现线程同步的锁,synchronized对象锁,lock是一个接口,她的实现有reentrantlock互斥锁以及ReentrantReadWriteLock共享锁. 这里说明一下ReentrantReadWriteLock共享锁,所谓共享就是该锁提供读读锁共享,即可以多个线程共享一个读取锁,但是读写锁以及读读锁是互斥的. 看到网上有好多介绍介绍锁的种类的,有对象锁.互斥锁.共享锁以及公平锁和非公平锁,但是说明都不够详细了然,在这里用直白的说法记录

Synchronized和lock的区别和用法

一.synchronized和lock的用法区别 (1)synchronized(隐式锁):在需要同步的对象中加入此控制,synchronized可以加在方法上,也可以加在特定代码块中,括号中表示需要锁的对象. (2)lock(显示锁):需要显示指定起始位置和终止位置.一般使用ReentrantLock类做为锁,多个线程中必须要使用一个ReentrantLock类做为对 象才能保证锁的生效.且在加锁和解锁处需要通过lock()和unlock()显示指出.所以一般会在finally块中写unloc

【java多线程】(3)---synchronized、Lock

synchronized.Lock 一.概述 1.出现线程不安全的原因是什么? 如果我们创建的多个线程,存在着共享数据,那么就有可能出现线程的安全问题:当其中一个线程操作共享数据时,还未操作完成,另外的线程就参与进来,导致对共享数据的操作出现问题. 2.线程不安全解决办法 要求一个线程操作共享数据时,只有当其完成操作完成共享数据,其它线程才有机会执行共享数据.java提供了两种方式来实现同步互斥访问:synchronized和Lock. 二.synchronized synchronized可以

java中synchronized与lock的理解与应用

总结来说,Lock和synchronized有以下几点不同: 1)Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现: 2)synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生:而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁: 3)Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchr