Java面试题整理2

Synchronized 和 Lock的区别

Synchronized和Lock都是为了解决并发编程线程安全问题

区别:
Synchronized是一个关键字,依靠Java的内置锁实现;使用方便,但锁颗粒较大性能相对较低;发生异常时,会自动释放锁

Lock是个接口,提供了多种锁控制功能和锁类型,像是公平锁,可重入锁;发生异常不会自动释放锁,需要手动释放锁

AQS是什么

AQS:AbstractQueuedSynchronizer 抽象队列式同步器,是Java的一种锁机制

AQS维护了两个重要的数据结构:volatile修饰的整型变量state,代表同步状态(例如:锁状态);FIFO线程等待队列

多个线程获取同步状态时,该状态被占用,线程放入FIFO等待队列,等待获取同步状态的线程释放状态

悲观锁和乐观锁

悲观锁和乐观锁是不同的并发控制策略

悲观锁认为在同步数据操作的时候,会被其他线程修改,所以在操作数据时,会加锁,但是频繁地加深和释放锁影响性能

乐观锁则认为在对数据操作的时候,大部分情况下不会出现并发修改的问题,所以在操作数据前不加锁,只是在提交数据的时候检查是否有其他线程同时修改了这个数据,如果没有就提交

所以悲观锁用于并发量较低,对数据安全性要求较高的场景,乐观锁用于高并发,少读写的场景,通常使用版本号或者时间戳实现

CAS

CAS:

CAS (Compare-And-Swap比较并交换) 操作包含三个操作数:内存位置 V、预期原值 A 和新值 B。主要流程如下:

从内存中取出当前内存位置 V 的值;
比较当前内存位置 V 中的值和预期原值 A 是否相等;
如果相等,则将内存位置 V 中的值修改为新值 B;
如果不相等,则不做任何操作。

Synchronized加静态方法和非静态方法上的区别

对象锁:Synchronized用在实例方法上,锁住的是这个对象实例,当线程访问被Synchronized修饰的实例方法时,会获取该对象实例的锁,其他线程不能同时访问该对象实例的所有同步方法

类锁:Synchronized用在静态方法上,锁住的是Class对象,当线程访问被Synchronized修饰的静态方法时,会获取Class对象的锁,其他线程不能访问该类的所有同步静态方法

Synchronized(this) 和 Synchronized (User.class)的区别

Synchronized(this) 中,this代表的是该对象实例,不会被所有实例共享
Synchronized (User.class),代表的是对类加锁,会被所有实例共享

Synchronized和volatile的区别

Synchronized和volatile都是用来解决并发编程中线程安全问题

区别:
volatile实现原理:每次使用volatile变量的时候,都会从内存中重新获取最新的值,修改变量后立刻同步到内存;Synchronized是锁定当前变量,让其他线程处于阻塞状态

volatile只能修饰变量,Synchronized可用在方法,同步代码块中

volatile修饰的变量在读写的时候有一个内存屏障,可以保证操作顺序性,也就是说对volatile写入操作要在其它线程读取该字段之前完成

volatile不会造成线程阻塞,高并发时性能更高;Synchronized会造成线程阻塞,适用于并发量低,但对数据安全性要求高的场景

volatile不能保证操作的原子性,因此线程不安全

Synchronized锁原理

Synchronized基于JVM内置锁(Monitor监视器锁)实现。

每个Java对象都与一个监视器锁关联,每个监视器锁又依赖于操作系统提供的互斥锁(Mutex Lock)。

当一个线程进入由一个Synchronized保护的代码块,该线程会尝试获取代码块的监视器锁,如果该监视器锁没被任何线程持有,该线程将获得该锁,或者当前线程阻塞

监视器锁依赖操作系统提供的互斥锁,多个线程尝试获取相同的锁,会产生锁竞争,需要互斥锁提供互斥性,但是互斥锁的获取释放涉及到CPU状态切换,涉及到一定开销还需要需要保存上下文信息,所以是重量级锁

Synchronized锁升级原理

每个对象都拥有对象头,对象头由Mark Word(标志词),指向类的指针,数组长度三部分组成,锁升级依赖Mark Word中的锁标志位实现

锁升级过程分为偏向锁,轻量级锁,重量级锁三个阶段

偏向锁:
当一个线程访问同步代码块并获取锁时,JVM在对象头中Mark Word标记线程的ID,表示该对象偏向该线程,下次该线程访问同步代码块的时候无需再次获取锁(消除了锁重入的开销【CAS的开销】),直接进入同步状态;同时,其他线程也访问该代码块时,撤销该对象的偏向状态,转换为轻量级锁

轻量级锁:
当一个线程尝试获取偏向锁失败,JVM将对象状态改为轻量级锁状态;JVM会在当前线程的栈帧中创建Lock Record(锁定记录)对象,并将对象头中Mark Word指向该记录,同时自旋等待锁的释放,长时间自旋可能出现忙等现象

重量级锁:锁竞争情况严重,出现达到最大自旋次数(默认10次)的线程,轻量级锁升级为重量级锁,此时,持有锁的线程会将自身挂起,释放CPU资源,等待其他线程通过操作系统提供的Mutex Lock机制将它唤醒;重量级锁情况下,线程阻塞,等待锁的释放

乐观锁的使用场景

乐观锁主要用在并发控制,通过比较版本号或者预期值实现数据同步更新

常见场景:
原子类的CAS操作:比较期望值和实际值是否相等,如果相等则更新为新值,否则不做任何操作

数据库:并发操作时,比较数据记录的版本号实现对记录更新和修改,在数据表增加version字段,每次更新记录,version值加1,更新时检查version是否仍然等于提交前的版本号,如果是说明没有其他线程修改过记录,安全更新数据,或则进行回滚重试

Ealsticsearch:用文档的version控制并发修改

AtomicInterger怎么保证并发安全性的

AtomicInteger的并发安全性主要基于三个方面:CAS操作、volatile关键字和C++的cmpxchg指令

C++的cmpxchg指令,保证了对同一内存区域的访问排队执行,实现了原子性

什么是重入锁,什么是自旋锁,什么是阻塞

重入锁是指允许同一个线程多次获取同一把锁

自旋锁不是锁,而是一种状态,当一个线程尝试获取一把锁的时候,如果这个锁已经被占用了,该线程就处于等待状态,并间隔一段时间后再次尝试获取的状态,就叫自旋

阻塞,指的是当一个线程尝试获取锁失败了,线程就就进行阻塞,这是需要操作系统切换CPU状态的

你用过JUC中的类吗,说几个

JUC是Java Concurrency Utilities的缩写,即Java并发工具类。它位于java.util.concurrent包中,提供了强大的多线程编程支持,为开发者提供了更高效、更方便、更安全的并发编程实现。

Lock锁体系:ConcurrentHashMap 

Atomic原子类,如:AtomicInteger ;

ThreadLoal ; 

ExecutorService

ThreadLocal的作用和原理

ThreadLocal线程局部变量,为每个线程提供了自己的数据副本,避免了多线程情况下的数据竞争问题

ThreadLocal原理:
1.每个线程维护了一个ThreadLocalMap,是个哈希表,key为ThreaLocal对象,value为对应线程的变量副本

2.ThreadLocal本身是个序号,作为key访问ThreadLocalMap的value,相当于访问当前线程的变量副本

3.调用ThreadLocal的set方法时,会在ThreadLocalMap中存储一个键值对,其中key为ThreadLocal对象,value是要设置的值

4.调用ThreadLocal的get方法时,会在ThreadLocalMap中查找对应的value,并返回结果

注意:当ThreadLocal对象被GC回收,ThreadLocalMap中对应的entery也会被删除,但entry占用的内存需要我们调用remove()清理,避免出现内存泄漏问题

内存泄漏:程序中一部分不再需要使用的内存仍然得不到释放,导致系统内存占用率持续增加
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值