目录
🐳3. 说一说自己对于 synchronized 关键字的了解?
🐳4. 谈谈 synchronized 和 ReenTrantLock 的区别?
🐳5. synchronized 和 volatile 的区别是什么?
🐳6. ReentrantReadWriteLock 的特点
🌹🌹🌹哈喽,大家好丫,你们的小郭子又来啦 ~
🌞今天我们继续聊一聊java面试中的【并发】,
话不多说,直接上干货,嘻嘻嘻 ~
1. Java 线程同步的几种方法?
-
使用 Synchronized 关键字;
-
wait 和 notify;
-
使用特殊域变量 volatile 实现线程同步;
-
使用可重入锁实现线程同步;
-
使用阻塞队列实现线程同步;
-
使用信号量 Semaphore。
2. 谈谈对 ThreadLocal 的理解?
Java 的 Web 项目大部分都是基于 Tomcat。每次访问都是一个新的线程,每一个线程都独享一个 ThreadLocal,我们可以在接收请求的时候 set 特定内容,在需要的时候 get 这个值。
ThreadLocal 提供 get 和 set 方法,为每一个使用这个变量的线程都保存有一份独立的副本。
1、get() 方法是用来获取 ThreadLocal 在当前线程中保存的变量副本;
2、set() 用来设置当前线程中变量的副本;
3、 remove() 用来移除当前线程中变量的副本;
4、 initialValue() 是一个 protected 方法,一般是用来在使用时进行重写的,如果在没有 set 的时候就调用 get,会调用 initialValue 方法初始化内容。
3. 说一说自己对于 synchronized 关键字的了解?
synchronized关键字解决的是多个线程之间访问资源的同步性,synchronized 关键字可以保证被它修饰的方法或者代码块在任意时刻只能有一个线程执行。
另外,在 Java 早期版本中,synchronized 属于重量级锁,效率低下,因为监视器锁(monitor)是依赖于底层的操作系统的 Mutex Lock 来实现的,Java 的线程是映射到操作系统的原生线程之上的。如果要挂起或者唤醒一个线程,都需要操作系统帮忙完成,而操作系统实现线程之间的切换时需要从用户态转换到内核态,这个状态之间的转换需要相对比较长的时间,时间成本相对较高,这也是为什么早期的 synchronized 效率低的原因。
庆幸的是在 JDK6 之后 Java 官方对从 JVM 层面对synchronized 较大优化,所以现在的 synchronized 锁效率也优化得很不错了。J
DK6 对锁的实现引入了大量的优化,如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销。
synchronized 关键字底层原理属于 JVM 层面。
4. 谈谈 synchronized 和 ReenTrantLock 的区别?
1、synchronized 是和 for、while 一样的关键字,ReentrantLock 是类,这是二者的本质区别。既然 ReentrantLock 是类,那么它就提供了比 synchronized 更多更灵活的特性:等待可中断、可实现公平锁、可实现选择性通知(锁可以绑定多个条件)、性能已不是选择标准。
2、 synchronized 依赖于 JVM 而 ReenTrantLock 依赖于 API。synchronized 是依赖于 JVM 实现的,JDK1.6 为 synchronized 关键字进行了很多优化,但是这些优化都是在虚拟机层面实现的,并没有直接暴露给我们。ReenTrantLock 是 JDK 层面实现的(也就是 API 层面,需要 lock() 和 unlock 方法配合 try/finally 语句块来完成),所以我们可以通过查看它的源代码,来看它是如何实现的。
5. synchronized 和 volatile 的区别是什么?
-
volatile 本质是在告诉 JVM当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取;synchronized 则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。
-
volatile 仅能使用在变量级别;synchronized 则可以使用在变量、方法、和类级别的。
-
volatile 仅能实现变量的修改可见性,不能保证原子性;而 synchronized 则可以保证变量的修改可见性和原子性。
-
volatile 不会造成线程的阻塞;synchronized 可能会造成线程的阻塞。
-
volatile 标记的变量不会被编译器优化;synchronized 标记的变量可以被编译器优化。
6. ReentrantReadWriteLock 的特点
1、写锁可以降级为读锁,但是读锁不能升级为写锁;
2、 不管是 ReadLock 还是 WriteLock 都支持 Interrupt,语义与 ReentrantLock 一致;
3、WriteLock 支持 Condition 并且与 ReentrantLock 语义一致,而 ReadLock 则不能使用 Condition,否则抛出 UnsupportedOperationException 异常;
4、 默认构造方法为非公平模式 ,开发者也可以通过指定 fair 为 true 设置为公平模式 。
7. 说下对悲观锁和乐观锁的理解?
- 观锁悲
总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。传统的关系型数据库里边就用到了很多这种锁机制,比如:行锁、表锁、读锁、写锁等,都是在做操作之前先上锁。Java 中 synchronized 和 ReentrantLock 等独占锁就是悲观锁思想的实现。
- 乐观锁
总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和 CAS 算法实现。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于 write_condition 机制,其实都是提供的乐观锁。在 Java 中 java.util.concurrent.atomic 包下面的原子变量类就是使用了乐观锁的一种实现方式 CAS 实现的
8. CAS 和 synchronized 的使用场景?
简单的来说 CAS 适用于写比较少的情况下(多读场景,冲突一般较少),synchronized 适用于写比较多的情况下(多写场景,冲突一般较多)。
1、对于资源竞争较少(线程冲突较轻)的情况,使用 synchronized 同步锁进行线程阻塞和唤醒切换以及用户态内核态间的切换操作额外浪费消耗 cpu 资源;而 CAS 基于硬件实现,不需要进入内核,不需要切换线程,操作自旋几率较少,因此可以获得更高的性能。
2、对于资源竞争严重(线程冲突严重)的情况,CAS 自旋的概率会比较大,从而浪费更多的 CPU 资源,效率低于 synchronized。
🐳🌞好啦,今天的分享到这里就结束啦 ~
🐳觉得我分享的文章不错的话,可以关注一下哦,嘻嘻嘻