6 多线程锁 (公平锁和非公平锁,死锁,可重锁)
某一个时刻内,只能有唯一一个线程去访问这些synchronized 方法
所有的 静态同步方法用的也是同一把锁 ——类对象本身,这两把锁是两个不同的对象,所以静态同步方法与非静态同步方法之间是不会有竞态条件的,但是一旦一个静态同步方法获取锁后,其他的静态同步方法都必须等待该方法释放锁后才能获取锁,而不管是同一个实例对象的静态同步方法之间,还是不同的实例对象的静态同步方法之间,只要它们同一个类的实例对象
synchronized 锁的是方法,则是对象锁,同个对象锁的机制要等待,不同对象锁的机制调用同一个不用等待
加了static则为class锁而不是对象锁
对于同步方法块,锁是 synchronized 括号里配置对象
6.1 synchronized 锁的八种情况
通过具体例子进行分析
class Phone {
public synchronized void sendSMS() throws Exception {
//停留4秒
TimeUnit.SECONDS.sleep(4);
System.out.println("------sendSMS");
}
public synchronized void sendEmail() throws Exception {
System.out.println("------sendEmail");
}
public void getHello() {
System.out.println("------getHello");
}
}
public class SynchronizedLockTest {
public static void main(String[] args) throws Exception {
Phone phone = new Phone();
Phone phone2 = new Phone();
new Thread(() -> {
try {
phone.sendSMS();
} catch (Exception e) {
e.printStackTrace();
}
}, "A").start();
Thread.sleep(100);
new Thread(() -> {
try {
// phone.sendEmail();
// phone.getHello();
phone2.sendEmail();
} catch (Exception e) {
e.printStackTrace();
}
}, "B").start();
}
}
不同案例输出的不同结果
1 标准访问,先打印短信还是邮件
------sendSMS
------sendEmail
2 停4秒在短信方法内,先打印短信还是邮件
------sendSMS
------sendEmail
3 新增普通的hello方法,是先打短信还是hello
------getHello
------sendSMS
4 现在有两部手机,先打印短信还是邮件
------sendEmail
------sendSMS
5 两个静态同步方法,1部手机,先打印短信还是邮件
------sendSMS
------sendEmail
6 两个静态同步方法,2部手机,先打印短信还是邮件
------sendSMS
------sendEmail
7 1个静态同步方法,1个普通同步方法,1部手机,先打印短信还是邮件
------sendEmail
------sendSMS
8 1个静态同步方法,1个普通同步方法,2部手机,先打印短信还是邮件
------sendEmail
------sendSMS
6.2 对上述例子的总结
同样的对象访问不同的同步锁,是按照顺序执行
同样的对象访问同步锁与不同步锁,是先不同步锁执行
不同对象访问不同同步锁,按照顺序执行
同一对象访问不同静态同步锁,按照顺序执行
不同对象访问不同静态同步锁,按照顺序执行
同一对象访问一个静态同步锁,一个同步锁,先执行同步锁
不同对象访问一个静态同步锁,一个同步锁,先执行同步锁,即先出同步锁在出静态同步锁
概括来说,锁之间的执行顺序是:不同步锁 > 同步锁 > 静态同步锁
6.3 公平锁和非公平锁
- 公平锁:效率相对低 ,但是cpu 的利用高了
- 非公平锁:效率高,但是线程容易饿死(所有的工作,有一个线程完成)
用法: 在创建可重入锁时,想构造器中传入true
private final ReentrantLock lock = new ReentrantLock(true);
因为 ReentrantLock 的构造器源码如下:
// 在没有传入参数时,默认创建一个非公平锁
public ReentrantLock() {
sync = new NonfairSync();
}
// 当传入一个true值时,为公平锁
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
6.4 可重入锁
synchronized和lock都是可重入锁
- sychronized是隐式锁,不用手工上锁与解锁,而lock为显示锁,需要手工上锁与解锁
- 可重入锁也叫递归锁
而且有了可重入锁之后,破解第一把之后就可以一直进入到内层结构
嵌套实现代码 他能进入下一个锁内而不会出现死锁
synchronized的示例代码
/**
* 演示可重入锁是什么意思,可重入,就是可以重复获取相同的锁而不会出现死锁
* synchronized和ReentrantLock都是可重入的
* */
public class WhatReentrantSynchronized {
// 创建一个锁对象
static Object mylock = new Object();
public static void main(String[] args) {
new Thread(()->{
// 创建第一个锁
synchronized (mylock){
System.out.println("这是第一层锁");
synchronized (mylock){
System.out.println("这是第二层锁");
}
}
}).start();
}
}
ReentrantLock的示例代码
/**
* lock和unlock的数量必须一致,否则会出现死锁
* */
public class WhatReentrantLock {
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
new Thread(()->{
// 上锁
lock.lock();
try {
System.out.println("这是第一层锁");
// 再次上锁
lock.lock();
try{
System.out.println("这是第二层锁");
}finally {
lock.unlock();
}
}finally {
lock.unlock();
}
}).start();
}
}
6.5 死锁
两个或以上的进程因为争夺资源而造成互相等待资源的现象称为死锁
产生死锁的原因:
- 系统资源不足
- 系统资源分配不当
- 进程运行顺序不当
我们有时候不知道是否是死锁 。那么怎么来验证呢? (电脑配置的有环境变量,在命令窗口)
- jps 类似于linux中的
ps -ef
查看进程号 - jstack 自带的堆栈跟踪工具
具体死锁的操作代码实列
可理解背下来,大厂面试可考,死锁的简单案例
public class DeadLock {
//创建两个对象
static Object a = new Object();
static Object b = new Object();
public static void main(String[] args) {
new Thread(()->{
// 获取a这把锁
synchronized (a) {
System.out.println(Thread.currentThread().getName()+" 持有锁a,试图获取锁b");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (b) {
System.out.println(Thread.currentThread().getName()+" 获取锁b");
}
}
},"A").start();
new Thread(()->{
// 获取b这把锁
synchronized (b) {
System.out.println(Thread.currentThread().getName()+" 持有锁b,试图获取锁a");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (a) {
System.out.println(Thread.currentThread().getName()+" 获取锁a");
}
}
},"B").start();
}
}