Java的锁事

文章介绍了Java中的悲观锁和乐观锁的概念,以及synchronized关键字和Lock接口的实现。悲观锁在数据读取时加锁,适合写操作频繁的场景,而乐观锁在更新时检查数据是否被修改,适用于读多写少的情况。文中通过8个锁的案例分析了不同锁在多线程环境下的行为,包括线程同步、资源竞争等场景。
摘要由CSDN通过智能技术生成

乐观锁和悲观锁

悲观锁

认为自己在使用数据的使用一定有别的线程来修改数据,因此在获取数据的时候会先加锁,确保数据不会被别的线程修改

synchronized关键字和Lock的实现都是悲观锁

适合写操作多的场景,先加锁可以保证写操作时数据正确

乐观锁

认为自己在使用数据时不会有别的线程修改数据或资源,所以不会加锁

在java中通过使用无锁编程来实现,知识在更新数据的时候去判断,之前没有别的线程更新了这个数据

如果这个数据没有被更新,当前线程将自己修改的数据成功写入

如果这个数据已经被其他线程更新,则根据不同的实现方式执行不同的操作,比如放弃修改、重试抢锁等等

判断规则

  1. 版本号机制version
  2. 最常采用CAS算法,java原子类中的递增操作就是通过CAS自炫实现的

适合读多的场景,不加锁的特点能够使其读操作的性能大幅度提升

8锁案例

请添加图片描述

  • class Phone{// 资源类
        public synchronized void sendEmail()
        {
            System.out.println("------sendEmail");
        }
        public synchronized void sendSMS()
        {
            System.out.println("------sendSMS");
        }
    }
    
    /**
     * 题目:谈谈您对多线程锁的理解,8锁案例说明
     * 口诀: 线程 操作 资源类
     * 8锁案例说明:
     * 1. 标准访问有ab两个线程,请问先打印邮件还是短信
     * 2. sendEmail方法中加入暂停3秒钟,请问先打印邮件还是短信
     * 3. 添加一个普通的hello方法,请问先打印邮件还是短信
     * 4. 有两部手机,请问先打印邮件还是短信
     * 5. 有2个静态同步方法,有1部手机,请问先打印邮件还是短信
     * 6. 有2个静态同步方法,有2部手机,请问先打印邮件还是短信
     * 7. 有1个静态同步方法,有1个普通方法,有1部手机,请问先打印邮件还是短信
     * 8. 有1个静态同步方法,有1个普通方法,有2部手机,请问先打印邮件还是短信
     */
    public class Lock8Demo {
        public static void main(String[] args) throws InterruptedException {
            Phone phone = new Phone();
            new Thread(() -> {
                phone.sendEmail();},"a").start();
    
            TimeUnit.MILLISECONDS.sleep(200);
            new Thread(() -> {
                phone.sendSMS();},"b").start();
    
    
        }
    }
    
  • class Phone{// 资源类
        public synchronized void sendEmail()
        {
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("------sendEmail");
        }
        public synchronized void sendSMS()
        {
            System.out.println("------sendSMS");
        }
    }
    
    /**
     * 题目:谈谈您对多线程锁的理解,8锁案例说明
     * 口诀: 线程 操作 资源类
     * 8锁案例说明:
     * 1. 标准访问有ab两个线程,请问先打印邮件还是短信
     * 2. sendEmail方法中加入暂停3秒钟,请问先打印邮件还是短信
     * 3. 添加一个普通的hello方法,请问先打印邮件还是短信
     * 4. 有两部手机,请问先打印邮件还是短信
     * 5. 有2个静态同步方法,有1部手机,请问先打印邮件还是短信
     * 6. 有2个静态同步方法,有2部手机,请问先打印邮件还是短信
     * 7. 有1个静态同步方法,有1个普通方法,有1部手机,请问先打印邮件还是短信
     * 8. 有1个静态同步方法,有1个普通方法,有2部手机,请问先打印邮件还是短信
     */
    public class Lock8Demo {
        public static void main(String[] args) throws InterruptedException {
            Phone phone = new Phone();
            new Thread(() -> {
                phone.sendEmail();},"a").start();
    
            TimeUnit.MILLISECONDS.sleep(200);
            new Thread(() -> {
                phone.sendSMS();},"b").start();
    
    
        }
    }
    

    一个对象里面如果有多个synchronized方法,某一个时刻,只要有一个线程去调用 其中一个synchronized方法 了,其他的线程只能等待,换句话说,某一个时刻,只能有唯一的一个线程去访问这些synchronized方法,锁的是当前对象的this,被锁定后,其他的线程都不能进入到当前对象的其他的synchronized方法

  • class Phone{// 资源类
        public synchronized void sendEmail()
        {
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("------sendEmail");
        }
        public synchronized void sendSMS()
        {
            System.out.println("------sendSMS");
        }
        public void hello()
        {
            System.out.println("hello world");
        }
    }
    
    /**
     * 题目:谈谈您对多线程锁的理解,8锁案例说明
     * 口诀: 线程 操作 资源类
     * 8锁案例说明:
     * 1. 标准访问有ab两个线程,请问先打印邮件还是短信
     * 2. sendEmail方法中加入暂停3秒钟,请问先打印邮件还是短信
     * 3. 添加一个普通的hello方法,请问先打印邮件还是短信
     * 4. 有两部手机,请问先打印邮件还是短信
     * 5. 有2个静态同步方法,有1部手机,请问先打印邮件还是短信
     * 6. 有2个静态同步方法,有2部手机,请问先打印邮件还是短信
     * 7. 有1个静态同步方法,有1个普通方法,有1部手机,请问先打印邮件还是短信
     * 8. 有1个静态同步方法,有1个普通方法,有2部手机,请问先打印邮件还是短信
     */
    public class Lock8Demo {
        public static void main(String[] args) throws InterruptedException {
            Phone phone = new Phone();
            new Thread(() -> {
                phone.sendEmail();},"a").start();
    
            TimeUnit.MILLISECONDS.sleep(200);
            new Thread(() -> {
                phone.hello();
                },"b").start();
    
    
        }
    }
    

应该是如您所愿哈哈,资源类 这个概念很好

  • class Phone{// 资源类
        public synchronized void sendEmail()
        {
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("------sendEmail");
        }
        public synchronized void sendSMS()
        {
            System.out.println("------sendSMS");
        }
        public void hello()
        {
            System.out.println("hello world");
        }
    }
    
    /**
     * 题目:谈谈您对多线程锁的理解,8锁案例说明
     * 口诀: 线程 操作 资源类
     * 8锁案例说明:
     * 1. 标准访问有ab两个线程,请问先打印邮件还是短信
     * 2. sendEmail方法中加入暂停3秒钟,请问先打印邮件还是短信
     * 3. 添加一个普通的hello方法,请问先打印邮件还是短信
     * 4. 有两部手机,请问先打印邮件还是短信
     * 5. 有2个静态同步方法,有1部手机,请问先打印邮件还是短信
     * 6. 有2个静态同步方法,有2部手机,请问先打印邮件还是短信
     * 7. 有1个静态同步方法,有1个普通方法,有1部手机,请问先打印邮件还是短信
     * 8. 有1个静态同步方法,有1个普通方法,有2部手机,请问先打印邮件还是短信
     */
    public class Lock8Demo {
        public static void main(String[] args) throws InterruptedException {
            Phone phone = new Phone();
            Phone phone2 = new Phone();
            new Thread(() -> {
                phone.sendEmail();},"a").start();
    
            TimeUnit.MILLISECONDS.sleep(200);
            new Thread(() -> {
                phone2.sendSMS();
                },"b").start();
        }
    }
    

    两部手机当然喽

  • class Phone{// 资源类
        public static synchronized void sendEmail()
        {
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("------sendEmail");
        }
        public static synchronized void sendSMS()
        {
            System.out.println("------sendSMS");
        }
        public void hello()
        {
            System.out.println("hello world");
        }
    }
    
    /**
     * 题目:谈谈您对多线程锁的理解,8锁案例说明
     * 口诀: 线程 操作 资源类
     * 8锁案例说明:
     * 1. 标准访问有ab两个线程,请问先打印邮件还是短信
     * 2. sendEmail方法中加入暂停3秒钟,请问先打印邮件还是短信
     * 3. 添加一个普通的hello方法,请问先打印邮件还是短信
     * 4. 有两部手机,请问先打印邮件还是短信
     * 5. 有2个静态同步方法,有1部手机,请问先打印邮件还是短信
     * 6. 有2个静态同步方法,有2部手机,请问先打印邮件还是短信
     * 7. 有1个静态同步方法,有1个普通方法,有1部手机,请问先打印邮件还是短信
     * 8. 有1个静态同步方法,有1个普通方法,有2部手机,请问先打印邮件还是短信
     */
    public class Lock8Demo {
        public static void main(String[] args) throws InterruptedException {
            Phone phone = new Phone();
            Phone phone2 = new Phone();
            new Thread(() -> {
                phone.sendEmail();},"a").start();
    
            TimeUnit.MILLISECONDS.sleep(200);
            new Thread(() -> {
                phone.sendSMS();
    //            phone2.sendSMS();
                },"b").start();
        }
    }
    

    5-6 都换成静态同步方法后,情况又有变化

    三种synchronized锁的内容有一些差别:

    对于普通同步方法,锁的是当前实例对象,通常是指this,具体的一部手机,所有的普通同步方法用的都是同一把锁–>实例对象本身

    对于静态同步方法,锁的是当前类的Class对象,如Phone.class唯一的一个模版

    对于同步方法块,锁的是synchronized括号内的对象

  • class Phone{// 资源类
        public static synchronized void sendEmail()
        {
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("------sendEmail");
        }
        public   synchronized void sendSMS()
        {
            System.out.println("------sendSMS");
        }
        public void hello()
        {
            System.out.println("hello world");
        }
    }
    
    /**
     * 题目:谈谈您对多线程锁的理解,8锁案例说明
     * 口诀: 线程 操作 资源类
     * 8锁案例说明:
     * 1. 标准访问有ab两个线程,请问先打印邮件还是短信
     * 2. sendEmail方法中加入暂停3秒钟,请问先打印邮件还是短信
     * 3. 添加一个普通的hello方法,请问先打印邮件还是短信
     * 4. 有两部手机,请问先打印邮件还是短信
     * 5. 有2个静态同步方法,有1部手机,请问先打印邮件还是短信
     * 6. 有2个静态同步方法,有2部手机,请问先打印邮件还是短信
     * 7. 有1个静态同步方法,有1个普通方法,有1部手机,请问先打印邮件还是短信
     * 8. 有1个静态同步方法,有1个普通方法,有2部手机,请问先打印邮件还是短信
     */
    public class Lock8Demo {
        public static void main(String[] args) throws InterruptedException {
            Phone phone = new Phone();
            Phone phone2 = new Phone();
            new Thread(() -> {
                phone.sendEmail();},"a").start();
    
            TimeUnit.MILLISECONDS.sleep(200);
            new Thread(() -> {
                phone.sendSMS();
    //            phone2.sendSMS();
                },"b").start();
        }
    }
    

    7-8结果

    一个是类锁,一个是对象锁,你说呢

Synchronized体现

作用与实例方法,当前实例加锁,进入 同步代码前要获得当前实例的锁;

作用于代码块,对括号里配置的对象加锁;

作用于静态方法,当前类加锁,进去同步代码前要获得当前类对象的锁

Synchronized字节码实现

java -c ***.classs 文件反编译

synchronzied 同步代码块

请添加图片描述

保证异常情况下,程序退出

一般情况下,一个enter两个exit

极端:在程序中手动抛出异常

synchronized 普通同步方法

请添加图片描述

调用指令将会检查方法的ACC—SYNNCHRONIZED访问标志是否设置。

如果设置了,执行线程会将现持有monitor锁,然后再执行方法,最后在方法完成(无论是正常完成还是非正常完成)时释放monitor

synchronized 静态同步方法

请添加图片描述

ACC_STATIC,ACC_SYNCHRONIZED访问标志区分该方法是否时静态同步方法

反编译synchronized锁的是什么

面试题:为什么任何一个对象都可以成为一个锁

public class ThreadTest {
    Object o = new Object();
    public void m1()
    {
        synchronized (o){
            System.out.println("hello world");
        }
    }
    public static void main(String[] args) {
        
    }
}

在HotSpot虚拟机中,monitor采用ObjectMonitor实现

ObjectMonitor.java->ObjectMonitor.cpp->objectMonitor.hpp

每个对象天生都带着一个对象监视器

每个被锁的对象都会和Monitor关联起来

公平锁与非公平锁

公平锁:多个线程按照申请锁的顺序来获取锁,这里类似排队买票,先来的人先买后来的人在队尾排着,这是公平的Lock lock = new ReentrantLock(true);// true 表示公平锁,先来先得

非公平锁:是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁,在高并发环境下,有可能造成优先级翻转或者饥饿状态(某个线程一致得不到锁)

Lock lock = new ReentrantLock(false);// false 表示非公平锁,后来的也可能先获得锁,默认非公平锁

为什么会有公平锁/非公平锁的设计?为什么默认非公平锁?

  1. 恢复挂起的线程到真正锁的获取还是有时间差的,从开放人员来看这个时间微乎其微,但是从cpu的角度来看,这个时间存在的还是很明显的。所以非公平锁能更充分的利用cpu的时间片,尽量减少cpu空闲状态时间。
  2. 使用多线程很重要的考量点事线程切换的开销,当采用非公平锁,当1个线程请求锁获取锁的同步状态,然后释放同步状态,所以刚释放锁的线程在此刻在此获取锁同步状态的概率就变得非常大,所以就减少了了线程开销

什么时候用公平?什么时候非公平?

如果为了吞吐量,使用非公平,否则,公平锁

可重入锁(又名递归锁)

是指同一个线程在外层方法获取锁的时候,再进入该线程内层方法会自动获取到锁(前提,锁对象得是同一个对象),不会因为之前已经获取过还没释放而阻塞

如果是1个有synchronized修饰得递归调用方法,程序第2次进入被自己阻塞岂不是天大的笑话,出现了作茧自缚。

所以,java中的ReentrantLock和Synchronized都是可重入锁,可重入锁的一个优点是可以一定程度避免死锁

隐式重入锁(synchronized)

原理

每隔锁对象拥有一个锁计数器和一个指向持有该锁的线程的指针。

当执行monitorenter时,如果目标锁对象的计数器为零,那么说明他没有被其他线程所持有,java虚拟机会将该锁对象持有线程设置为当前线程,并且将其计数器+1

在目标锁对象的计数器不为零的情况下,如果锁对象的持有线程是当前线程,那么java虚拟集可以将其计数器+1,否则则需要等待,直至持有线程释放该锁

当执行monitorexit时,java虚拟机则需要将锁对象的计数器-1。计数器为零代表所以被释放

显式重入锁(Lock)

死锁

请添加图片描述

死锁是指两个或两个以上的线程再执行过程中,因为争夺资源而造成的一种互相等待的现象,若无外力干涉他们将无法推荐下去,如果胸资源充足,进程的资源请求都能够得到满足,死锁出现的肯能行就很低,否则就会因为争夺有限的资源而陷入死锁

模拟死锁

public static void main(String[] args) {
    final Object a = new Object();
    final Object b = new Object();

    new Thread(() -> {
        synchronized (a) {
            System.out.println("A锁");
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            synchronized (b){
                System.out.println("A获取到B锁");
            }
        }
    }).start();
    new Thread(() -> {
        synchronized (b) {
            System.out.println("B锁");
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            synchronized (a){
                System.out.println("B获取到A锁");
            }
        }
    }).start();
}

检测死锁

命令

​ jps

​ jstack

图形化

​ jconsole

主要原因

系统资源不足;

进程运行推进的顺序不合适

资源分配不当

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值