JavaEE之多线程进阶-面试问题

一.常见的锁策略

锁策略不是指某一个具体的锁,所有的锁都可以往这些锁策略中套

1.悲观锁与乐观锁

预测所冲突的概率是否高,悲观锁为预测锁冲突的概率较高,乐观锁为预测锁冲突的概率更低。

2.重量级锁和轻量级锁

从加锁的开销角度判断,重量级锁与悲观锁相对应,轻量级锁与乐观锁相对应。(并不是一定的,大多数情况是这样的)

3.挂起等待锁和自旋锁

挂起等待锁,就是悲观锁/重量级锁的一种典型实现。

自旋锁,则是乐观锁/轻量级锁的一种典型实现。

自旋锁,会在不释放cpu资源的情况下一直检测目标锁是否被释放,一旦锁被释放,就立即有机会获得锁(需要消耗更多cpu资源,且要在所冲突不高的时候)

挂起等待锁就是让出cpu资源,让cpu去做别的事情,等待通知后才会获得锁资源。(时效性低,需消耗更多时间)

4.公平锁和非公平锁

公平锁:先到先得,先阻塞等待的先得到锁,后来后得。

非公平锁:不按照先来后到的顺序,谁争抢到锁归谁(大部分情况下都是使用非公平锁,效率高)

5.可重入锁和不可重入锁

如果对一个线程,针对一把锁重复加锁两次,就有可能出现死锁。

如果把锁设定成**"可重入"**就可以避免死锁了

可重入锁:对同一个锁资源可以加多次锁

不可重入锁:不可以对同一个锁资源加多次锁

6.读写锁

注:synchronized并非是读写锁

所谓读写锁,就是把"加锁操作"分成两种情况:加读锁,加写锁

读锁:共享锁,读与读操作都能同时拿到锁资源

写锁:排它锁,读写,写读,写写不能同时拿到锁资源

二.synchronized原理

1.特征

1)既是乐观锁,又是悲观锁(自适应)

2)是轻量级锁,也是重量级锁(自适应)

3)不是读写锁

4)挂起等待锁和自旋锁(自适应)

5)是可重入锁

6)非公平锁(锁竞争)

2.锁升级

1)刚开始使用synchronized加锁,首先锁会处于"偏向锁"状态。

偏向锁:相当于一种标记,不是真正加锁(更为轻量高效)

2)遇到线程之间的锁竞争时,会升级到"轻量级锁"。

3)进一步统计锁竞争出现的频次,达到一定程度后,升级到"重量级锁"

无锁=》偏向锁=》轻量级锁=》重量级锁

上述锁升级的过程,主要是为了能够让synchronized这个锁更好的适应不同的场景,降低程序员负担。

注:上诉锁升级的过程是不可逆的

3.锁消除与锁粗化

1)锁消除

编译器会对所写的synchronized代码做出判定,判断这个地方是否确实需要加锁。

如果这个加锁是没有必要的,能够自动把synchronized给干掉。

2)锁粗化

也是一种编译器的优化策略

锁的粗细,是根据锁的粒度来判断的。

代码越多,则粒度越粗,

代码越少,则粒度越细。

锁粗化,就是把多个"细粒度"的锁合并为"粗粒度"的锁

三.CAS

1.概念

CAS全称"compare and swap",用于比较内存与cpu寄存器种的内容。如果发现相同,就进行交换(交换的是内存和另一个寄存器的值)

如有一个内存中的数据和寄存器1,寄存器2.

比较内存中的数据与寄存器1中的数据是否相等,如果相等,将内存和寄存器2的数据进行交换。

CAS的关键是通过一个cpu指令(原子的)完成了一系列操作,可为编写多线程代码带来新的思路

称为"无锁化编程"

2.使用场景

基于CAS实现"原子类"。

java 复制代码
public class Dem1 {
    private static AtomicInteger count = new AtomicInteger();

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i <50000; i++) {
                count.getAndIncrement();
            }

        });
        Thread t2=new Thread(()->{
            for (int i = 0; i < 50000; i++) {
                count.getAndIncrement();
            }

        });

        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("count="+count.get());

    }
}

3.CAS工作原理

假如有两个线程t1,t2进行自增操作

线程t1先读取到数据,t2后读取到,但t2优先进行cas操作,value+1.

此时调度回t1进行cas操作,能够发现数据发生了变化(value!=oldvalue)。

因此,就能判断出其它线程趁着t1load时修改了value的值,此时便不会进行cas操作,而是再来一次load,确保寄存器中的值是正确的值,然后进行cas。

3.ABA问题

CAS之所以能保证线程安全,重要的一点在于通过CAS比较的过程中,来确认是否有其它线程插入进来执行(通过比较value和oldvalue)

此处是通过判定值是否相同,来判断是否有其它线程修改过。

但是值相同!=没有修改可能。

有可能存在另一个线程修改了值,又修改回去了。这就是ABA问题。

ABA问题产生的原因是因为value可增可减

引入"版本号",设置版本号只能增加,可以解决ABA问题

相关推荐
落落落sss32 分钟前
MQ集群
java·服务器·开发语言·后端·elasticsearch·adb·ruby
我救我自己32 分钟前
UE5运行时创建slate窗口
java·服务器·ue5
2401_853275731 小时前
ArrayList 源码分析
java·开发语言
爪哇学长1 小时前
SQL 注入详解:原理、危害与防范措施
xml·java·数据库·sql·oracle
MoFe11 小时前
【.net core】【sqlsugar】字符串拼接+内容去重
java·开发语言·.netcore
_江南一点雨1 小时前
SpringBoot 3.3.5 试用CRaC,启动速度提升3到10倍
java·spring boot·后端
转转技术团队1 小时前
空间换时间-将查询数据性能提升100倍的计数系统实践
java·后端·架构
深情废杨杨2 小时前
后端-实现excel的导出功能(超详细讲解)
java·spring boot·excel
智汇探长2 小时前
EasyExcel自定义设置Excel表格宽高
java·excel·easyexcel
酸奶代码2 小时前
Spring AOP技术
java·后端·spring