Java多线程面试高配问题---线程并发安全(2)🧵
Java多线程
线程安全知识
1. synchronized关键字的底层原理
底层是一个monitor,在class文件中体现,加锁,解锁,为了防止代码出现bug,最后又解锁了一次,相当于一个try catch finally代码块。
Monitor实现
lock对象关联一个monitor
初级总结
2. synchronized关键字的底层原理-进阶
没有竞争的情况下可以用偏向锁与轻量级锁,一旦发生竞争,锁需要升级为重量级锁
monitor与lock对象是怎么关联上的?
要明白这些,首先要知道对象的内存结构。
对象头,实例数据,对齐填充(无意义的数据),为了保证字节是8的整数倍。
MarkWord-32bit
通过markword中的指针地址相关联
轻量级锁
偏向锁
偏向锁只有重入的第一次才做CAS操作,后面只是记录一下,判断上面的线程ID是不是自己
三种锁的对比
3. JMM(Java Memory Model)Java内存模型
通过主内存同步数据
4. CAS(Compare And Swap)
CAS流程
CAS底层实现
调用了操作系统的CAS指令,java 在 unsafe 类中,native 修饰的是操作系统底层的函数,由C或者C++实现的。
乐观锁与悲观锁
volatile关键字
代码运行之后,发现线程 3 还是一直在执行, 因为 jit 对我们的代码做了优化
volatile保证线程间的可见性
避免添加了volatile的变量被优化掉
volatile禁止指令重排序
示例:未加volatile
在y上添加volatile
在x上添加volatile,无效,因为写操作加的屏障只能阻止上面的指令不能往下走,不能阻止下边的指令往上走。
读操作的屏障,只能阻止下面的指令越过它排在它(添加了volatile的变量)之上。
volatile总结
5. 什么是AQS?(AbstractQueuedSynchronizer)
AQS本质也是一种锁 乐观锁
AQS基本工作机制
内部有一个state属性,有线程就来持有了锁,state变为1,其他线程再进来改state就失败了,加入到队列中,先进先出的双向链表,有头部和尾部。
假如同时来了两个线程0和线程4 同时抢这个锁
被0抢到了,线程4就加入队列,在队尾排队。
AQS是公平锁还是非公平锁?都可以实现****
此时线程0释放了锁,状态变为0
同时来了一个线程5,线程5和1同时抢,就是非公平锁
同时来了一个线程5,加入队尾,唤醒线程1持有锁,此时就是公平锁
公平锁与非公平锁
AQS总结
6. ReentrantLock的实现原理(可重入锁)
ReentrantLock基本介绍
可重入
可重入是指一个线程如果获取了锁,那么它就是锁的主人,那么它可以再次获取这把锁,这种就是理解为重入,简而言之,可以重复获取同一把锁,不会造成阻塞
实现原理
ReentrantLock总结
7. synchronized和Lock的区别?
三个层面:
1. 语法层面
2. 功能层面
可打断:在等待获取锁的过程中,可以被其他线程打断,可以通过调用线程的interrupt()提前终止线程。举个例子:
public void testInterrupt() throws InterruptedException {
ReentrantLock lock = new ReentrantLock();
// 主线程普通加锁
System.out.println("主线程优先获取锁");
lock.lock();
try {
// 创建子线程
Thread t1 = new Thread(() -> {
try {
System.out.println("t1尝试获取打断锁");
lock.lockInterruptibly();
} catch (InterruptedException e) {
System.out.println("t1没有获取到锁,被打断,直接返回");
return;
}
try {
System.out.println("t1成功获取锁");
} finally {
System.out.println("t1释放锁");
lock.unlock();
}
}, "t1");
t1.start();
Thread.sleep(2000);
System.out.println("主线程进行打断锁");
t1.interrupt();
} finally {
// 主线程解锁
System.out.println("主线程优先释放锁");
lock.unlock();
}
}
可超时:调用tryLock(long timeout, TimeUnit unit)方法,在给定时间内获取锁,获取不到就退出,这也是synchronized没有的功能。
public void testLockTimeout() throws InterruptedException {
ReentrantLock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {
try {
// 调用tryLock获取锁
if (!lock.tryLock(2, TimeUnit.SECONDS)) {
System.out.println("t1获取不到锁");
return;
}
} catch (InterruptedException e) {
System.out.println("t1被打断,获取不到锁");
return;
}
try {
System.out.println("t1获取到锁");
} finally {
lock.unlock();
}
}, "t1");
// 主线程加锁
lock.lock();
System.out.println("主线程获取到锁");
t1.start();
Thread.sleep(3000);
try {
System.out.println("主线程释放了锁");
} finally {
lock.unlock();
}
}
多条件变量:可以对锁设置条件,达到了条件要求后才能获取锁.
在线程1中调用c1.await();,线程2中调用c2.await(),线程3中按顺序唤醒c1,c2,调用c1.signal(),c2.signal()
如下示例
3. 性能层面
8. 死锁产生的条件是什么?
四个必要条件
(1) 互斥条件:一个资源每次只能被一个进程使用。
(2) 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
(3) 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
(4) 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁。
什么是死锁
当两个线程为了保护两个不同的共享资源而使用了两个互斥锁,那么这两个互斥锁应用不当的时候,可能会造成两个线程都在等待对方释放锁,
在没有外力的作用下,这些线程会一直相互等待,就没办法继续运行,这种情况就是发生了死锁。
9. ConcurrentHasMap
1.7
segment数组不能扩容,且每次只能有一个线程拿到锁,性能比较低
1.8
10. java程序中怎么保证多线程的执行安全(导致并发程序出现问题的根本原因)?
Java 并发编程的三大特性
- 原子性
- 可见性
- 有序性
synchronized保证原子性,valitale保证可见性和有序性
本文来自博客园,作者:xiaolifc,转载请注明原文链接:https://www.cnblogs.com/xiaolibiji/p/18083096
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 因为Apifox不支持离线,我果断选择了Apipost!
· 通过 API 将Deepseek响应流式内容输出到前端