锁是用来在多线程并发阶段保障数据同步的重要手段,防止出现脏数据,加锁代码在某个时间点只能由一个线程运行,其他线程等待。
PS:有数据表明,除去大型互联网公司,80%的系统不存在多线程的竞争的情况,一定要熟悉这几种锁,对以后面试镀金(面试)真的很有用。
面试官: 派大星,我们今天来讨论一下Java中的锁机制,特别是synchronized和ReentrantLock这两个锁。首先,我想问一下,在Java 1.5后期(1.6之前)的时候,synchronized是重量级锁,后来引入了锁升级的概念。你能给我解释一下这个锁升级的过程吗?
那sychronized是如何实现可见性的呢,其实就是利用了内存屏障。 如下: sychronized(this){ // monitorenter // Load内存屏障 //... } //monitorexit //Store内存屏障 sychronized 那sychronized是如何实现有序性的呢,其实就是利用了这两个内存屏障。 sychronized的锁优化 jdk1.6后jvm对sychronized进行了锁优化,这部分我们做个概念了解就可以了。 例如 sychronized(this){} sychronized(this){} sychronized(this){} 连着三个加锁操作,编译后会变成一个。
Sychronized的偏向锁、轻量级锁、重量级锁 偏向锁:在锁对象的对象头中记录⼀下当前获取到该锁的线程ID,该线程下次如果⼜来获取该锁就可以直接获取到了 轻量级锁:由偏向锁升级⽽来,当⼀个线程获取到锁后 Sychronized和ReentrantLock的区别 sychronized是⼀个关键字,ReentrantLock是⼀个类 sychronized会⾃动的加锁与释放锁,ReentrantLock需要程序员 ⼿动加锁与释放锁 sychronized的底层是JVM层⾯的锁,ReentrantLock是API层⾯的锁 sychronized是⾮公平锁,ReentrantLock可以选择公平锁或⾮公平锁 sychronized 锁的是对象,锁信息保存在对象头中,ReentrantLock通过代码中int类型的state标识来标识锁的状态 sychronized底层有⼀个锁升级的过程
sychronized 用法 sychronized 是Java语法层面的同步策略,可以用来修饰instance变量、object reference(对象引用)、static函数和class literals 1、当非static 元素被sychronized修饰时,当前线程都会取得该对象锁,该对象的其他线程均无法访问任何被sychronized修饰的变量或方法。 sychronized涉及对象锁如果在两个以上很容易造成死锁,谨慎使用同步策略,避免无谓的取锁。 很显然sychronized是一种独占锁,也就是悲观锁,默认一定会发生资源争用,所以每次都默认取锁。 自旋锁、自适应性自旋锁 说到这里,sychronized主流程已经清楚了,但还有两个概念,自旋锁、自适应性自旋锁。
在争用频次非常高的情况下性能会急剧下降,这种观点是存在时效性的,就当前1.8版本使用体验而言,sychronized在大量争用的情况性能其实还好并不会出现所谓的急剧下降,倒是在激烈争用时sychronized 的性能要好一些,这个问题去官网确认了下,就现状而言官方是建议使用sychronized的,这次的体验也是sychronized更好。 因为当前JVM是对于sychronized做出了优化了,借鉴ReentrantLock的CAS加锁方式,并且引入了偏向锁、轻量级锁等特性后,常规情况下两者比较相似,实践中得到的体验是sychronized 性能更好一点,可能是JVM层面加锁之后,并且编译器同时做优化Sychronized 更适合在用户态把加锁问题解决避免陷入内核态的线程阻塞更有用吧。 这里重点要说的是使用锁的一些方式: 1、锁选择 鉴于上面性能比较的结果,推荐使用sychronized 2、锁粒度 粒度要尽可能的控制到小,避免不必要的加锁。
最近的面试,我也经常会问到volatile相关的问题,比如volatile和sychronized的区别;volatile的使用场景;volatile的实现原理等等。 而sychronized也是解决了可见性问题,它不允许同一时间有两个线程操作同一共享资源,因为其无法保证可见性。 通过如下的sychronized的修饰就符合原子性了 sychronized(this) { num++; } 其道理还是因为sychronized的独占性。 道理和上面没有加sychronized的描述是一样的。 线程A还有执行完num++后,线程B也来访问num值,得到的是0,然后执行num++,最终还是两个线程得到的值都是1。 volatile可以禁止重排序(sychronized也是可以的)。 其实与之相关概念还有重排序、happens-before,as-if-serial等等,限于篇幅,不再详述。
我们在日常开发中熟知的锁有两个 JUC 包中的sychronized,可重入锁,支持公平锁和公平锁。其内部实现是通过JUC包中的AQS,但是AQS中也使用到了JVM 的锁Sychronized。 sychronized关键字是JVM底层实现的锁,在每一个对象都会有一个对象头,其中对象头中有几个字节专门表示对象的锁状态,在JDK1.6之前sychronized还是一个比较重量极的锁,但是后续进行了优化 使其效率提高了起来,且sychronized是非公平锁。 总结 主要还是介绍了并发和Java中的线程,并发的概念,线程通过获取CPU的时间便进行交替执行的过程成为并发。 明天更新锁两把锁“sychronized”,“sychronized”的详细情况 思考 通过反射机制或字节码操纵技术会将不变的对象变化掉吗?
当其他线程调用Condition对象的signal(),才会唤醒Condition对象的等待队列中的WATING线程 Condition newCondition(); } 1.2 Lock与sychronized 的异同 共同点 Lock的子类中有可重入锁 区别 Lock显式的获取锁与释放锁 sychronized隐式的获取锁与释放锁 Lock只能给代码块上锁 sychronized可以给代码块,方法上锁 Lock 依赖JDK实现 sychronized依赖JVM实现 Lock有独占模式与共享模式,每个模式还分公平锁与非公平锁 sychronized是独占模式的非公平锁 Lock是可中断的,线程在获得锁的过程中是可以影响中断 sychronized不可中断,线程在阻塞等待锁的释放的时候,是不会响应中断的 Lock可以设定超时时间,超时会返回 sychronized不行 2.
monitor被某个线程持有,它将处于锁定状态(获取锁) ObjectMonitor() { _header = NULL; _count = 0; //锁计数器,sychronized Word 对象由三个部分组成——对象头,实例数据,对齐填充 对象头结构如下(非数组2个字,数组3个字) 第一个字——Mark Word 第二个字——指向对象Class对象的指针 第三个字——数组长度 sychronized 重量级锁 竞争锁的线程阻塞,不消耗CPU 线程阻塞,响应时间慢 追求吞吐量,同步代码快执行时间较长 3. sychronized 只允许一个线程获得锁后执行同步代码块 保证有序性 JMM不允许同步语句与非同步语句重排序,但是允许同步代码块内在不改变结果的前提下进行重排序(JMM虽然允许同步代码块内重排序,但是在程序员看来仍是有序的) 4. volatile与sychronized 的区别 volatile更轻量,不需要加锁,不会阻塞线程 sychronized可以保证复合语句的原子性,volatile不行
蚂蚁一面面试真题解析|配套笔记: 6、sychronized和ReentrantLock的区别 sychronized是⼀个关键字,ReentrantLock是⼀个类 sychronized会⾃动的加锁与释放锁 ,ReentrantLock需要程序员⼿动加锁与释放锁 sychronized的底层是JVM层⾯的锁,ReentrantLock是API层⾯的锁 sychronized是⾮公平锁,ReentrantLock 可以选择公平锁或⾮公平锁 sychronized锁的是对象,锁信息保存在对象头中,ReentrantLock通过代码中int类型的state标识来标识锁的状态 sychronized底层有⼀个锁升级的过程 7、sychronized的⾃旋锁、偏向锁、轻量级锁、重量级锁,分别介绍和联系 偏向锁:在锁对象的对象头中记录⼀下当前获取到该锁的线程ID,该线程下次如果⼜来获取该锁就可以直接获取到了 轻量级锁:
java.util.concurrent.locks包下的接口,Lock 实现提供了比使用synchronized 方法和语句可获得的更广泛的锁定操作,它能以更优雅的方式处理线程同步问题,我们拿Java线程(二)中的一个例子简单的实现一下和sychronized System.out.print(name.charAt(i)); } } finally { lock.unlock();// 释放锁 } } } 这样就实现了和sychronized 一样的同步效果,需要注意的是,用sychronized修饰的方法或者语句块在代码执行完之后锁自动释放,而用Lock需要我们手动释放锁,所以为了保证锁最终被释放(发生异常情况),要把互斥区放在try内,释放锁放在 Thread-2写入12 Thread-4读取12 Thread-5读取5 Thread-1写入12 我们要实现写入和写入互斥,读取和写入互斥,读取和读取互斥,在set和get方法加入sychronized
HashTable hashTbles的实现基本可以以HashMap结构为基础,要说差异的话就是每个方法都变成了sychronized,这种直接在每个方法上直接sychronized,怕不是当时当时临时上的策略了 并且,这次可不是挤牙膏式更新,舍弃了之前Segment分段锁式设计,底层采用数组+链表+红黑树结构实现,采用CAS+sychronized实现并发安全。 下面是putVal中的CAS+Sychronized的使用,putval也是整个ConcurrentHashMap中比较核心的,推荐详细去看一下,因为篇幅,只说里面的一两点。 ? ?
---- 【Sychronized执行流程图(截取自网络技术文章中的图)】
download_jmeter.cgi 为了避免有些小伙伴访问不到官网,我上传到了百度云:链接:https://pan.baidu.com/s/1c9l3Ri0KzkdIkef8qtKZeA 提取码:kjh6 初次实战(sychronized ) 第一次进行并发实战,我是首先想到sychronized关键字的。 isConflict(Integer roomId, Timestamp startTime, Timestamp endTime) { // 判断房间排期是否有冲突... } } sychronized 事务必须在同步代码块sychronized中提交,这是必须的。否则当线程A使用房间1开房,同步代码块执行完,事务还未提交,线程B发现房间1的房间排期没有冲突,那么此时是有问题的。
存在的问题: 1.超卖问题: 解决方案: (先考虑单机情况)我们将使用sychronized锁去 锁数据库的操作。 1.2秒杀版本: 业务流程: 1.用户点击下单2. (此时已经是sychronized锁内)查询用户有没有下过订单,库存是否充足3.若没有查询到信息,则扣减库存,增加订单记录。 若查询到了 则返回信息。 但由于一直是单机版本,所以接下来将考虑分布式的问题 解决方案:1.分布式情况,sychronized将无效 我们将采用redis分布式锁来锁操作 1.4秒杀版本: 在1.3的版本上加分布式锁 业务流程:
然后可以改造一下,分别使用sychronized ,ReetrantLock改造,保证线程安全。 小结 本篇对比了sychronized和ReetrantLock的区别; 然后说了线程安全的概念和保证线程手段。
加锁有两种方式,一种是sychronized的重量级锁,一种是volatile,相比更为轻量级 先来看看具体的代码编写: public class Singleton { private volatile new Singleton (); } } } return single; } } volatile和sychronized
Syschronized实现同步有两种方式 通过将代码块放入Sychronized关键字中,底层实现方式是:在JVM中使用monitorenter和monitorexit指令实现a 通过将某个方法对象放入 线程执行到 monitorenter 指令时,将会尝试获取对象所对应的 monitor 的所有权,即尝试获得对象的锁 sychronized加锁过程: ?