- 多线程锁的八种情况
- 公平锁和非公平锁
- 可重入锁
- 死锁
先举个常见的栗子:
一个手机类, 有发送短信(sendSMS)和发送邮件(sendEmail)的同步方法, 以及发送hello的异步方法(sendHello)
-
标准访问,先打印短信还是邮件
------sendSMS
------sendEmail谁在前面谁就先访问
-
在短信方法内停(sleep)4秒,先打印短信还是邮件
------sendSMS
------sendEmail虽然短信sleep了4s, 但是由于是在方法内的,sleep是别的类Thread的类方法, 和对象没有关系, 所以不会把对象锁解开, 等待4s之后还是先发短信
-
新增非同步的hello方法,是先发短信(sleep了4s)还是hello
------getHello
------sendSMShello方法没有同步, 所以短信是否sleep都和hello没有关系
-
现在有两部手机,先打印短信还是邮件
------sendEmail
------sendSMS两部手机是两个对象, 两个线程获取的是不是两个不同的对象锁, 他们之间没有同步关系
-
两个静态同步方法,1部手机,先打印短信还是邮件
------sendSMS
------sendEmail静态方法访问不是对象锁, 而是类锁, 和具体的对象没有关系, 因此两个线程都是访问同一个类锁, 先获取锁的先执行(sleep不会释放锁)
-
两个静态同步方法,2部手机,先打印短信还是邮件
------sendSMS
------sendEmail不管几部手机, 都是同一个类, 访问的是同一个类锁
-
1个静态同步方法,1个普通同步方法,1部手机,先打印短信还是邮件
------sendEmail
------sendSMS一个访问的类锁, 一个访问的对象锁, 没有直接的关系
-
1个静态同步方法,1个普通同步方法,2部手机,先打印短信还是邮件
------sendEmail
------sendSMS一个访问的类锁, 一个访问的对象锁,和上面同理
所以说:
-
异步方法不上锁, 和其他方法的各自执行, 互不关联
-
不同的对象调用的是各自的对象锁
-
普通同步方法调用的是对象锁, 静态同步方法调用的是静态锁, 两者不是一把锁
先看看ReentrantLock的构造器源码:
- ReentrantLock还有一个有参构造器, 参数fair表示是否公平锁
- ReentrantLock默认都是非公平锁
- synchronize关键字也是非公平锁
区别:
- 在公平锁中, 不同的线程能较为公平地平均分享到资源线程, 阳光普照, 效率较低
- 在非公平锁中, 某一个线程(不一定是开始的线程)会很大程度上优先得到到资源线程, 效率较高, 但容易饿死某些线程(总是抢不到资源)
还是考虑原来的卖票例子
如果是公平锁:
如果是非公平锁:
可重入锁可重入锁,又称递归锁, 指的是同一个线程外层函数获得锁之后,内层递归函数仍然能获取该锁的代码,在同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁。
也就是说,线程可以进入任何一个他已经拥有锁的所有同步代码块
synchronize和Lock都是可重入锁, 前者是隐式的, 后者是显式的
用synchronize写一段获取某一个特定对象锁的同步代码:
Object obj = new Object();
synchronize(obj){代码...}
用synchronize演示可重用锁:
public class ReentrantDemo{
public static final Object o = new Object();
public static void main(String[] args){
new Thread(() -> {
synchronized(o){
System.out.println(Thread.currentThread().getName() + ": 外层");
synchronized(o){
System.out.println(Thread.currentThread().getName() + ": 中层");
synchronized(o){
System.out.println(Thread.currentThread().getName() + ": 内层");
}
}
}
}, "AAA").start();
}
// 该递归方法能够调用,说明外层进程能进入内层进程中, 也说明是可重用锁
public synchronized void add(){
add();
}
}
用Lock演示可重用锁:
一个栗子: 把房子当成外层线程, 里面有两个内层线程: 厨房和卧室
public class House{
private final ReentrantLock lock = new ReentrantLock();
// 演示的时候调用house函数
public void house(){
lock.lock();
try{
System.out.println("进入房子");
bedroom();
kitchen();
System.out.println("4s后从房子出来");
}finally{
lock.unlock();
}
}
private void bedroom(){
lock.lock();
try{
System.out.println("进入卧室");
Thread.sleep(2000);
System.out.println("2s秒后从卧室出来");
}catch(InterruptedException e){
e.printStackTrace();
}finally{
lock.unlock();
}
}
private void kitchen(){
lock.lock();
try{
System.out.println("进入厨房");
Thread.sleep(2000);
System.out.println("2s秒后从厨房出来");
}catch(InterruptedException e){
e.printStackTrace();
}finally{
lock.unlock();
}
}
}
死锁
什么是死锁:
两个或者两个以上线程在执行过程中,因为争夺资源而造成一种互相等待的现象,如果没有外力干涉,他们无法再执行下去
产生死锁原因:
- 系统资源不足
- 进程运行推进顺序不合适
- 资源分配不当
一个死锁的栗子:
public class DeadLockDemo{
public static final Object a = new Object();
public static final Object b = new Object();
public static void main(String[] args){
new Thread(() -> {
synchronized(a){
System.out.println("A线程拥有a锁, 正在试图获取b锁");
try{
Thread.sleep(1000);
// 睡一秒钟, 否则可能B线程还没拿到b锁,就被A抢到了两个锁
} catch(InterruptedException e){
e.printStackTrace();
}
synchronized(b){
System.out.println("A线程获取b锁");
}
}
}, "AAA").start();
new Thread(() -> {
synchronized(b){
System.out.println("B线程拥有b锁, 正在试图获取a锁");
synchronized(a){
System.out.println("B线程获取a锁");
}
}
}, "BBB").start();
}
}
在正常写业务中如何判断死锁:
在终端打开当前目录(使用idea的终端十分方便)
使用命令:
jps -l
找到当前程序的端口号, 使用命令:
jstack [端口号]
如果最后显示Found 1 deadlock, 说明这是死锁
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)