Skip to content

Commit b7c8647

Browse files
committed
feat: lock
1 parent 1048ae1 commit b7c8647

File tree

1 file changed

+96
-3
lines changed

1 file changed

+96
-3
lines changed
Lines changed: 96 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,102 @@
1-
https://blog.csdn.net/javaloveiphone/article/details/59483188
1+
一、锁
2+
java提供的锁机制有两种,关键字和concurrent包下的lock锁
3+
1.关键字volatile:synchronized是Java中的关键字,用于实现内置锁(Intrinsic Lock)机制。它可以用于方法或代码块上,用来标识需要同步访问的代码区域。当线程进入synchronized保护的代码区域时,会尝试获取对象的内置锁,如果锁没有被其他线程占用,则获取成功,执行代码区域,执行完毕后释放锁。如果锁已被其他线程占用,则线程进入阻塞状态,等待锁的释放。
4+
synchronized关键字最主要有以下3种应用方式,下面分别介绍:
5+
- 修饰实例方法,作用于当前实例加锁,进入同步代码前要获得当前实例的锁
6+
- 修饰静态方法,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁
7+
- 修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。
28

3-
线程是方法吗?是一个方法一个线程
9+
synchronized底层语义原理:
10+
对象在内存中的布局分为三块区域:对象头、实例数据和对齐填充
411

5-
百千万数据导出一个Excel,一分钟解决,你会怎么做
612

13+
2.关键字volatile
14+
15+
3.锁的分类
16+
乐观锁和悲观锁
17+
独占锁和共享锁
18+
互斥锁和读写锁
19+
公平锁和非公平锁
20+
可重入锁 自旋锁 分段锁
21+
- 锁升级
22+
JDK1.6 为了提升性能减少获得锁和释放锁所带来的消耗,引入了4种锁的状态:无锁、偏向锁、轻量级锁和重量级锁,它会随着多线程的竞争情况逐渐升级,但不能降级。
23+
- 无锁
24+
无锁状态其实就是上面讲的乐观锁,这里不再赘述。
25+
- 偏向锁
26+
Java偏向锁(Biased Locking)是指它会偏向于第一个访问锁的线程,如果在运行过程中,只有一个线程访问加锁的资源,不存在多线程竞争的情况,那么线程是不需要重复获取锁的,这种情况下,就会给线程加一个偏向锁。
27+
偏向锁的实现是通过控制对象Mark Word的标志位来实现的,如果当前是可偏向状态,需要进一步判断对象头存储的线程 ID 是否与当前线程 ID 一致,如果一致直接进入。
28+
注:如果不加偏向锁,那么一个线程进入同步块时需要重复获取锁,有很大获取锁的开销;如果对象的锁状态是偏向锁,并且当前线程是持有偏向锁的线程,那么JVM会直接让线程进入同步块,无需额外的同步操作
29+
- 轻量级锁
30+
当线程竞争变得比较激烈时,偏向锁就会升级为轻量级锁,轻量级锁认为虽然竞争是存在的,但是理想情况下竞争的程度很低,通过自旋方式等待上一个线程释放锁。
31+
注:如果在撤销偏向锁的过程中,JVM发现除了持有偏向锁的线程外,没有其他线程竞争该锁,那么JVM会将偏向锁状态转换为轻量级锁状态(偏向锁转轻量级锁)
32+
- 重量级锁
33+
如果线程并发进一步加剧,线程的自旋超过了一定次数,或者一个线程持有锁,一个线程在自旋,又来了第三个线程访问时(反正就是竞争继续加大了),轻量级锁就会膨胀为重量级锁,重量级锁会使除了此时拥有锁的线程以外的线程都阻塞。
34+
升级到重量级锁其实就是互斥锁了,一个线程拿到锁,其余线程都会处于阻塞等待状态。
35+
注:如果在撤销偏向锁的过程中,JVM发现除了持有偏向锁的线程外,还有其他线程竞争该锁,那么JVM会将偏向锁状态转换为重量级锁状态。此时,JVM会先暂停持有偏向锁的线程,并尝试使用CAS操作将对象的Mark Word替换为指向重量级锁的数据结构。(偏向锁转重量级锁)
36+
在 Java 中,synchronized 关键字内部实现原理就是锁升级的过程:无锁 --> 偏向锁 --> 轻量级锁 --> 重量级锁。这一过程在后续讲解 synchronized 关键字的原理时会详细介绍
37+
4.锁优化技术(锁粗化、锁消除)
38+
39+
锁粗化就是将多个同步块的数量减少,并将单个同步块的作用范围扩大,本质上就是将多次上锁、解锁的请求合并为一次同步请求。
40+
举个例子,一个循环体中有一个代码同步块,每次循环都会执行加锁解锁操作
41+
```
42+
private static final Object LOCK = new Object();
43+
for (int i=0;i<100;i++) {
44+
synchronized (LOCK) {
45+
//do something
46+
}
47+
}
48+
```
49+
```
50+
private static final Object LOCK = new Object();
51+
synchronized (LOCK) {
52+
for (int i=0;i<100;i++) {
53+
54+
}
55+
}
56+
```
57+
锁消除
58+
锁消除是指虚拟机编译器在运行时检测到了共享数据没有竞争的锁,从而将这些锁进行消除
59+
上面代码中有一个 test 方法,主要作用是将字符串 s1 和字符串 s2 串联起来。
60+
test 方法中三个变量s1, s2, stringBuffer, 它们都是局部变量,局部变量是在栈上的,栈是线程私有的,所以就算有多个线程访问 test 方法也是线程安全的。
61+
我们都知道 StringBuffer 是线程安全的类,append 方法是同步方法,但是 test 方法本来就是线程安全的,为了提升效率,虚拟机帮我们消除了这些同步锁,这个过程就被称为锁消除。
62+
```
63+
public String test(String s1,String s2) {
64+
StringBuffer stringBuilder = new StringBuffer();
65+
stringBuilder.append(s1);
66+
stringBuilder.append(s2);
67+
return stringBuilder.toString();
68+
}
69+
```
70+
71+
二、CAS
72+
73+
CAS存在三大问题:ABA问题,循环时间长且开销大,以及只能保证一个共享变量的原子操作;
74+
ABA问题的场景如下:
75+
76+
1.初始状态,共享变量的值为A。
77+
2.线程1读取共享变量的值A。
78+
3.在线程1读取值A的同时,线程2将共享变量的值从A修改为B,然后又修改回A。
79+
4.线程1执行CAS操作,比较共享变量的值为A,发现与预期值相等,于是进行更新操作。
80+
在这种情况下,CAS操作成功,因为线程1读取共享变量时,发现其值与预期值相等。然而,实际上共享变量的值已经发生了变化,虽然在值的变化过程中经历了A->B->A的变化,但是线程1并不知道这个过程,它仅仅是关注共享变量的当前值与预期值是否相等。
81+
82+
这就是ABA问题,CAS操作成功,但忽略了共享变量的值在过程中发生了其他变化。ABA问题可能导致一些潜在的风险和错误,特别是在并发数据结构和多线程算法中使用CAS时。
83+
84+
为了解决ABA问题,可以使用版本号或标记来跟踪共享变量的变化。每次共享变量发生变化时,版本号或标记也会相应地增加。这样,在CAS操作时,不仅要比较值是否相等,还要比较版本号或标记是否匹配。如果版本号或标记不匹配,说明共享变量在过程中发生了变化,CAS操作将失败。
85+
86+
**如果修改失败如何解决**
87+
1.自旋重试:在获取失败后,可以使用自旋重试的方式,即反复尝试执行CAS操作,直到获取成功或达到一定的尝试次数或时间限制。自旋重试避免了线程的上下文切换,适用于短时间内锁竞争不激烈的情况。
88+
2.适当延迟:为了减少不必要的CPU自旋消耗,在获取失败后可以适当地引入延迟。可以使用Thread.sleep()等方法在重试之前让线程进行短暂的休眠,以降低CPU占用。
89+
3.加锁:如果CAS操作失败的次数较多或存在激烈的竞争情况,可以考虑使用传统的加锁机制,如使用synchronized关键字或ReentrantLock,来保证线程的互斥访问。加锁可以避免无限自旋的情况,但也会引入线程的上下文切换和额外的开销。
90+
4.采用其他策略:根据具体的业务场景和需求,还可以采用其他策略来处理CAS获取失败的情况。例如,可以选择放弃当前操作、回退到其他实现方式,或者根据具体情况进行异常处理等。
91+
92+
二、为什么频繁创建线程会需要时间,具体那部分的时间
93+
94+
线程分为守护线程和非守护线程
95+
当Java程序的所有非守护线程都执行完毕时,程序会正常退出,无论是否还存在守护线程。非守护线程是程序的主要执行线程,它们执行程序的核心逻辑。一旦所有非守护线程执行完毕,JVM会自动退出,无需显式地停止守护线程。
96+
97+
然而,如果还存在活跃的非守护线程,程序仍然可以继续执行。在这种情况下,守护线程的行为会有所影响。
98+
99+
守护线程是一类特殊的线程,它的生命周期与非守护线程不同。当程序中只剩下守护线程时,JVM会判断程序已经不再需要继续执行,因此会强制终止所有守护线程,然后退出程序。
7100

8101

9102
[面试官:Java线程与底层操作系统线程是一 一对应的吗? 中篇[]_mob60475706bec5的技术博客_51CTO博客](https://blog.51cto.com/u_15127698/2842977)

0 commit comments

Comments
 (0)