【多线程初阶】synchronized锁

🌅synchronized关键字

monitor lock 是JVM中采用的一个术语,使用锁的过程中抛出一些异常,可能会看到,监视器锁这样的报错信息

🌊 synchronized 的互斥

synchronized 会起到互斥的效果,某个线程执行到某个对象的 synchronized 中时,其他线程如果也执行到同一个对象 synchronized就会阻塞等待

  • 进入 synchronized 修饰的代码块,相当于 加锁
  • 退出 synchronized 修饰的代码块,相当于 解锁
synchronized {	//进入代码块,加锁
	//执行一些要保护的逻辑
}				//退出代码块,解锁

synchronized 用的锁是存在Java对象里的
可以粗略理解成,每个对象在内存在存储的时候,都存有一块内存表示当前的"锁定状态(类似于卫生间的"有人/无人")
如果当前是"无人"状态,那么就可以使用,使用时需要设为"有人"状态
如果当前是"有人"状态,呢么其他人无法使用,只能排队

理解阻塞等待:
针对每一把锁,操作系统内部都维护了一个等待队列,当这个锁被某个线程占有的时候,其他线程尝试进行加锁,就加不上了,就会阻塞等待,一直等到之间的线程解锁之后,由操作系统唤醒一个新的线程,再来获取到这个锁
注意:

  • 上一个线程解锁之后,下一个线程并不是立即就能获取到锁,而是要靠操作系统来"唤醒",这也是操作系统线程调度的一部分工作
  • 假设由 A B C 三个线程,线程 A 先获取到锁,然后 B 尝试获取锁,然后 C再尝试获取锁,此时 B 和 C都在阻塞队列中排队等待,但是当 A 释放锁之后,虽然 B 比 C先来,但是B 不一定就能获取锁,而是和 C 重新竞争,并不遵守先来后到的规则

在这里插入图片描述
两个线程,针对同一个对象加锁,才会产生互斥效果(一个线程加上锁了,另一个线程就得阻塞等待,等到第一个线程释放锁,才有机会)

如果是不同的锁对象,此时不会有互斥效果,线程安全没问题,没有得到改变

在这里插入图片描述

public class Demo17 {
    private static int count = 0;

    public static void main(String[] args) {
        Object locker = new Object();
        Thread t1 = new Thread(() ->{
            for (int i = 0; i < 500000; i++) {
                synchronized (locker){
                    count++;
                }
            }
            System.out.println(" t1 结束");
        });
        Thread t2 = new Thread(() ->{
            for (int i = 0; i < 500000; i++) {
                synchronized (locker){
                    count++;
                }
            }
            System.out.println(" t2 结束");
        });
    }
}

分析一下上述代码的具体执行
在这里插入图片描述
Java中为啥使用 synchronized + 代码块的做法?而不是采用lock + unlock函数的方式来搭配呢?(其实在Java中也有 lock/unlock风格的锁,只不过一般很少使用)

Java采取 synchronized,就能确保,只要出了 } 一定能释放锁,无论因为 return 还是异常,无论里面调用了哪些其它代码,都可以确保unlock 操作执行到的

此时,其他语言也参考了Java这里的设定,C++提供了lock_guard机制,Python提供了with语句(上下文管理器),Go提供了defer关键字

本质上都是和synchronized一样的,都是出了代码块就能自动释放锁

🌊 synchronized 的变种写法

🏄‍♂️synchronized 修饰代码块 :明确指定锁哪个对象

  • 1.锁任意对象
public static void main(String[] args) {
        Object locker = new Object();
        Thread t1 = new Thread(() ->{
                synchronized (locker){
                    count++;
            }
        });
  • 2.锁当前对象
public static void main(String[] args) {
        Thread t1 = new Thread(() ->{
                synchronized (this){
            }
        });

🏄‍♂️synchronized 修饰方法

  • 1.直接修饰普通方法 : 锁的SynchronziedDemo对象
  • 2.修饰静态方法 : 锁的SynchronziedDemo 类的对象
class Counter{
    private int count = 0;
    synchronized public void add(){
        count++;
    }
    public int get(){
        return count;
    }
    public synchronized  static void func(){
        synchronized (Counter.class){

        }
    }

}
public class Demo18 {
    public static void main(String[] args) throws InterruptedException {
        Object locker = new Object();
        Counter counter = new Counter();
        Thread t1 = new Thread(() ->{
            for (int i = 0; i < 50000; i++) {
                counter.add();

            }
        });
        Thread t2 = new Thread(() ->{
            for (int i = 0; i < 50000; i++) {
                counter.add();
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("count="+counter.get());
    }
}

针对上述代码,其中:
在这里插入图片描述

🌊 synchronized 的可重入性

synchronized 同步对同一条线程来说是可重入的,不会出现自己把自己锁死的问题

理解"把自己锁死"
一个线程没有释放锁,然后又尝试再次加锁
// 第一次加锁,加锁成功
lock();
// 第二次加锁,锁已经被占用,阻塞等待
lock();
按照之前对于锁的设定,第二次加锁的时候,就会阻塞等待,直到第一次的锁被释放,才能获取到第二个锁,但是释放第一个锁也是由该线程来完成的,结果这个线程不干了,无法进行解锁操作,这时候就会死锁

Java中的 synchronized 是可重入锁,因此没有上面的问题

在这里插入图片描述
比如以下代码的使用:

class Counter2{
    private int count = 0;
    public void add(){
        synchronized (this) {
            count++;
        }
    }
    public int get(){
        return count;
    }

}
public class Demo19 {
    public static void main(String[] args) throws InterruptedException {
        Counter2 counter2 = new Counter2();
        Thread t1 = new Thread(() ->{
            for (int i = 0; i < 50000; i++) {
                    synchronized (counter2){
                        counter2.add();
                    }
            }
        });
        t1.start();
        t1.join();
        System.out.println("count = "+counter2.get());
    }
}

在这里插入图片描述
死锁是一个非常严重的bug,使代码执行到这一块之后,就卡住
为了解决上述问题,Java的 synchronized 就引入了可重入的概念

在这里插入图片描述
在这里插入图片描述

🏄‍♂️可重入锁实现原理

可重入锁的实现原理,关键在于让锁对象,内部保存,当前是哪个线程持有这把锁,后续有线程针对这个锁加锁的时候,对比一下,锁持有者的线程是否和当前加锁的线程是同一个

在这里插入图片描述

🏄‍♂️JVM怎么区分{ } 是synchronized的{}

{ }这个大括号,只是Java代码角度理解的,JVM看到的是字节码
java => .class 这个过程编译器已经处理了
JVM 执行 .class

Java代码中看到的是{ },字节码中,对应的是不同的指令 { 涉及到加锁指令 } 涉及到解锁指令 像是 if else while 他们的 { } 是不会被编译成,加锁解锁指令的~~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值