Administrator
Published on 2021-12-15 / 110 Visits
0
0

JUC之说一说Java锁事

说一说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

读写锁

自旋锁


Comment