乐观锁和悲观锁描述的是两种不同的加锁态度。
乐观锁:预测锁冲突的概率不高,因此做的工作就可以简单一点。
悲观锁:预测锁冲突的概率较高,因此做的工作就要复杂一点。
比如:你明天就要去面试:
乐观的做法:早睡早起,按时正常去参加面试流程。
悲观的做法:晚上熬夜复习、订好回家的机票、第二天早上早早起来准备、提前到公司一段时间再参加面试。
普通互斥锁:就像synchronized,当两个线程竞争同一把锁,就会有人得阻塞等待。
读写锁:分为加读锁和加写锁
读锁和读锁之间,不会产生竞争。 多个线程同时读一个变量没事儿。
写锁和写锁之间,会产生竞争。 就像普通互斥锁,有“锁竞争”,发生阻塞等待。
读锁和写锁之间,也会产生竞争。 就像普通互斥锁,有“锁竞争”,发生阻塞等待。
注:在实际环境中,读的频率要远远大于写的频率。加读写锁就会少很多的“锁竞争”,优化了执行效率
重量级锁:加锁和解锁开销比较大。 是典型的从用户态进入内核态的逻辑,开销较大。
轻量级锁:加锁和解锁开销比较小。 是典型的纯用户态的逻辑,开销较小。
注:
自旋锁:如果获取锁失败,立即再次尝试重新获取锁,无限循环,直到获取到了锁。
优点:第一:不释放CPU资源;第二:如果其他线程释放了锁,这个线程就能马上获取到锁。
缺点:如果其他线程持有锁的时间较长时,会造成CPU资源的浪费。
挂起等待锁:如果获取锁失败,则进入阻塞等待状态,一段时间后再次尝试重新获取锁。
优点:在阻塞等待阶段会释放CPU资源。
缺点:不能及时获取到锁。
前提:三个线程请求获取锁的先后顺序:t1、t2、t3
公平锁:遵守先来后到。t1先获取到锁、然后t2获取到锁、最后t3获取到锁
非公平锁:随机调度。t1、t2、t3谁能先获取到锁是随机的
可重入锁:同一个线程针对同一把锁,连续加锁两次,不会死锁。
不可重入锁:同一个线程针对同一把锁,连续加锁两次,会死锁。
总结:
对于synchronized
注:
synchronized是自适应的。初始使用的时候是乐观锁、轻量级锁、自旋锁;如果当前“锁竞争”不激烈,就保持最开始的状态不变。如果“锁竞争”激烈,就会自动升级为悲观锁、重量级锁、挂起等待锁。
CAS(Compare And Swap):比较和交换。
即:把内存中的某个值和CPU寄存器A中的值进行比较。如果两个值相同,就把寄存器B中的值和内存中的值进行交换。如果不同,就不做操作。
优势:这个操作是通过一条指令来完成的。所以是线程安全的,也是高效率的。
//原子类 多用于计数
//count.getAndIncrement = count++
//count.incrementAndGer = ++count
//count.getAndDecrement = count--
//count.decrementAndGer = --count
public static void main(String[] args) {
AtomicInteger count = new AtomicInteger();
Thread thread = new Thread(() -> {
for (int i = 0; i < 50000; i++){
//相当与count++;
count.getAndIncrement();
}
});
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 50000; i++){
//相当于count++;
count.getAndIncrement();
}
});
thread.start();
thread1.start();
try {
thread.join();
thread1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(count.get());
}

在CAS中进行比较的时候,当主内存和寄存器A中的值相同时,我们无法判断主内存中的值是一直没变,还是已经变了又变回来了。
前提:用户的存款有1000元

解决办法:
另外搞一个内存(寄存器C),记录内存中数据的变化。比如:
在每次比较的时候,同时比较寄存器A和寄存器C中读到的数据和主内存中的数据是否一致。
死锁:一个线程在加上锁之后,就无法释放锁了。
场景一:一个线程,一把锁,该线程连续加锁两次。
解决:使用可重入锁,比如:synchronized
场景二:两个线程,两把锁。
解决:设计时考虑周到
//死锁的案例
public static void main(String[] args) {
Object locker1 = new Object();
Object locker2 = new Object();
Thread thread1 = new Thread(() -> {
synchronized (locker1){
System.out.println("在locker1里,获取对locker1的锁");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (locker2){
System.out.println("在locker1里,获取对locker2的锁");
}
}
});
Thread thread2 = new Thread(() -> {
synchronized (locker2){
System.out.println("在locker2里,获取对locker2的锁");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (locker1){
System.out.println("在locker2里,获取对locker1的锁");
}
}
});
thread1.start();
thread2.start();
}
}
场景三:多个线程,多把锁。“哲学家就餐问题”
解决:1. 约定必须先拿哪一把锁再拿哪一把锁 2. “银行家算法”
总结:
死锁的必要条件:
对应的解决: