说一说Java锁事
乐观锁和悲观锁
- 悲观锁
- 认为自己在使用数据的时候一定有别的线程来修改数据,因此在获取数据的时候会先加锁,确保数据不会被别的线程修改
- synchronized 关键字和 Lock 的实现类都是悲观锁
- 适合写操作多的场景,先加锁可以保证写操作时数据正确
- 悲观锁
- 乐观锁认为自己在使用数据时不会有别的线程修改数据,所以不会添加锁,只是在更新数据的时候去判断之前有没有别的线程更新了这个数据
- 如果这个数据没有被更新,当前线程将自己修改的数据成功写入,如果数据已经被其他线程更新,则根据不同的实现方式执行不同的操作
- 乐观锁在Java中是通过使用无锁编程来实现,最常采用的是CAS算法,Java原子类中的递增操作就通过CAS自旋实现的
Synchronized
- 修饰普通函数。监视器锁(
monitor
)便是对象实例(this
) - 修饰静态函数。视器锁(
monitor
)便是对象的Class
实例(每个对象只有一个Class
实例) - 修饰代码块。监视器锁(
monitor
)是指定对象实例
一定是一个monitorenter两个monitorexit吗?
不一定,如果直接抛异常,就是一对一
公平锁与非公平锁
为什么会有公平锁/非公平锁的设计?
- 恢复挂起的线程到真正锁的获取还是有时间差的,从开发人员来看这个时间微乎其微,但是从CPU的角度来看,这个时间差存在的还是很明显的,所以非公平锁能更充分的利用CPU 的时间片,尽量减少 CPU 空闲状态时间
- 使用多线程很重要的考量点是线程切换的开销,当采用非公平锁时,当1个线程请求锁获取同步状态,然后释放同步状态,因为不需要考虑是否还有前驱节点,所以刚释放锁的线程在此刻再次获取同步状态的概率就变得非常大,所以就减少了线程的开销
使用公平锁会有什么问题
- 公平锁保证了排队的公平性,非公平锁霸气的忽视这个规则,所以就有可能导致排队的长时间在排队,也没有机会获取到锁,这就是传说中的锁饥饿
可重入锁(又名递归锁)
一个线程中的多个流程可以获取同一把锁,持有这把同步锁可以再次进入
隐式锁(Synchronized)
每个锁对象拥有一个锁计数器和一个指向持有该锁的线程的指针
当执行 monitorenter 时,如果目标锁对象的计数器为零,那么说明它没有被其他线程所持有,Java虚拟机会将该锁对象的持有线程设置为当前线程,并且将其计数器加 1
在目标锁对象的计数器不为零的情况下,如果锁对象的持有线程是当前线程,那么 Java 虚拟机可以将其计数器加1,否则需要等待,直至持有线程释放该锁
当执行 monitorexit 时,Java虚拟机则需将锁对象的计数器减 1,计数器为零代表锁已被释放
显式锁
ReentrantLock
死锁
死锁是指两个或两个以上的线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力干涉那它们都将无法推进下去,如果系统资源充足,进程的资源请求都能够得到满足,死锁出现的可能性就很低,否则就会因争夺有限的资源而陷入死锁
死锁代码case
package com.atguigu.juc.senior.prepare;
import java.util.concurrent.TimeUnit;
/**
* @auther zzyy
* @create 2020-05-14 10:56
*/
public class DeadLockDemo
{
public static void main(String[] args)
{
final Object objectLockA = new Object();
final Object objectLockB = new Object();
new Thread(() -> {
synchronized (objectLockA)
{
System.out.println(Thread.currentThread().getName()+"\t"+"自己持有A,希望获得B");
//暂停几秒钟线程
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
synchronized (objectLockB)
{
System.out.println(Thread.currentThread().getName()+"\t"+"A-------已经获得B");
}
}
},"A").start();
new Thread(() -> {
synchronized (objectLockB)
{
System.out.println(Thread.currentThread().getName()+"\t"+"自己持有B,希望获得A");
//暂停几秒钟线程
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
synchronized (objectLockA)
{
System.out.println(Thread.currentThread().getName()+"\t"+"B-------已经获得A");
}
}
},"B").start();
}
}
死锁原因
- 系统资源不足
- 进程运行推进的顺序不合适
- 资源分配不当
如何排查死锁
- 纯命令
- jps -l
- jstack 进程编号
- 图形化
- jconsole