目录
🍇死锁问题
🍎 N个线程M把锁
🍇总结
乐观锁&悲观锁
锁的实现者预测当前加锁代码的锁冲突(就是锁竞争,多个线程针对同一个对象加锁)的概率大不大,从而选择不同的加锁力度。
不同的锁会导致最终做的事不一样。
一般来说乐观锁比悲观锁做的事情少一些,效率相对也就会高一些(不绝对)。
轻量级锁&重量级锁
跟乐观锁和悲观锁的概念有一定的重合。
同样的一个乐观锁大概率也是一个轻量级锁,一个悲观锁大概率也是一个重量级锁。
自旋锁&挂起等待锁
自旋锁是轻量级锁的一种典型实现。
挂起等待锁是重量级锁的一种典型实现。
自旋锁通常是纯用户态,不需要通过内核(时间相对短),但是由于在不停的尝试获取锁,也就会忙等,就会消耗cpu资源。
挂起等待锁则是通过内核机制来实现(时间相对长),但是不会忙等,消耗cpu资源也就相对少一点。
互斥锁&读写锁
互斥锁:一个被加锁的对象,多个线程来了,有一个线程拿到锁对象后其他线程就不能再加锁,只有等当前锁被释放后才能进行加锁。
读写锁:我们知道线程安全问题的一个原因就是多个线程针对同一个变量进行修改操作。如果多个线程针对同一变量进行读操作,并不会有线程安全问题。那么理论上是不需要加锁,或者说这个锁再针对多个线程读的时候不会将其他线程阻塞。同理,读写锁再多个线程写的时候会有阻塞。
可重入锁&不可重入锁
一个线程针对一把锁连续加锁两次,出现死锁问题的就是比可重入锁,反之就是可重入锁。
死锁问题
一个线程一把锁
public class Demo {
synchronized public void getSay(){
say();
}
synchronized public void say(){
System.out.println("hello !!");
}
public static void main(String[] args) {
Demo demo = new Demo();
demo.getSay();
}
}
例如这段代码,再执行getSay方法时候会执行到say方法,但是由于两个方法都被加上同一把锁,要执行say方法必须要将getSay方法执行结束后解锁了才能执行,但是要解锁getSay方法就要将方法执行完,所以就陷入一个死锁的情况。
但是synchronized是可重入锁,所以这段代码其实是可以执行打印结果的。
两个线程两把锁
public class Demo {
public static void main(String[] args) {
Object clock1 = new Object();
Object clock2 = new Object();
Thread t1 = new Thread(()->{
synchronized (clock1){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (clock2){
System.out.println("hello t1");
}
}
});
Thread t2 = new Thread(()->{
synchronized (clock2){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (clock1){
System.out.println("hello t2");
}
}
});
t1.start();
t2.start();
}
}
当t1线程拿到clock1,t2线程拿到clock2时候,此时就陷入死锁,t1要往下执行需要拿到clock2,而t2要往下执行要拿到clock1。
N个线程M把锁
更容易造成死锁,例如:哲学家就餐问题
总结
一般破除死锁问题从循环等待方向入手,我们可以规定一个加锁的顺序(例如哲学家就餐问题,规定每个人都先拿左边的筷子,这样死锁就破除了)。
公平锁&非公平锁
synchronized
synchronized属于既是乐观锁也是悲观锁,既是轻量级锁,也是重量级锁, 自旋锁部分根据轻量级锁实现,挂起等待锁基于重量级锁实现。是可重入锁,是互斥锁,是非公平锁。
synchronized会根据当前代码中的锁竞争激烈程度自适应。
注:synchronized目前只能升级(从无锁到重量级锁),还不能降级(从重量级锁到无锁)。