【多线程】一文详解synchronized

概念和作用

  • 概念:synchronizedjava中的一个关键字,翻译成中文是同步的意思,主要解决了多个线程之间访问资源的同步性
  • 作用:synchronized可以保证被它修饰的方法或者代码块在任意时刻只能有一个线程来执行.

sychronized的特性

互斥

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

可重入

sychronized同步块对于同一条线程来说是可重入的,所以不会出现自己把自己锁死的问题.

class Lock{
    Object lock=new Object();
    public void test(){
        synchronized (lock){
            System.out.println("进入第一个synchronized");
            try { TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}
            synchronized (lock) {
                System.out.println("进入第二个synchronized");
                try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
            }
        }
    }
}
public static void main(String[] args) {
    Lock lock=new Lock();
    lock.test();
}

运行结果
[图片]

可重入锁总结

  • Java中的所有锁是可重入锁,Java没有内置不可重入锁,常见的ReentrantLockSynchronized属于可重入锁
  • 可重入锁指的是可重复可递归调用的锁,在外层使用锁之后,在内层仍然可以使用,并且不发生死锁,这样的锁就叫做可重入锁。与可重入锁相反,不可重入锁不可递归调用,递归调用就发生死锁。
  • 隐式锁(即synchronized关键字使用的锁)默认是可重入锁显式锁(即Lock)也有ReentrantLock这样的可重入锁。

可重入锁的工作原理

可重入锁的工作原理很简单,就是用一个计数器来记录锁被获取的次数一个持有者标识

  • 获取锁:
    一个线程尝试获取锁时,首先会检查当前锁的持有者是否是自己
    • 如果,则将计数器+1,表示再次获取成功
    • 如果不是,则检查锁是否被其他线程持有,如果没有被持有,则将当前线程设置为持有者,并将计数器设为1 ; 如果 已经被其他线程持有,则该线程会被阻塞,直到锁被释放
  • 释放锁:
    • 当持有锁的线程调用释放锁的方法时计数器-1
    • 如果计数器减到0,表示该线程已经完全释放了锁,此时会清空持有者标识,允许其他线程获取该锁

使用sychronized

修饰实例方法

锁定当前对象的实例,适用于需要保护实例变量的情况

public class SynchronizedExample {
    public synchronized void synchronizedMethod() {
        // 线程安全的代码
        System.out.println(Thread.currentThread().getName() + " is executing synchronizedMethod");
    }
}

修饰静态方法

通过在静态方法前加上 synchronized 关键字,可以确保同一时刻只有一个线程可以执行该静态方法。由于静态方法是类级别的,因此锁是针对类

public class SynchronizedExample {
    public static synchronized void synchronizedStaticMethod() {
        // 线程安全的代码
        System.out.println(Thread.currentThread().getName() + " is executing synchronizedStaticMethod");
    }
}

修饰代码块

通过在代码块前加上 synchronized 关键字,可以锁定特定的对象。这种方式提供了更细粒度的锁控制,避免了不必要的锁竞争

public class SynchronizedExample {
    private final Object lock = new Object();

    public void synchronizedBlockMethod() {
        synchronized (lock) {
            // 线程安全的代码
            System.out.println(Thread.currentThread().getName() + " is executing synchronizedBlockMethod");
        }
    }
}

synchronized的底层原理

从字节码的角度来分析

  • synchronized同步代码块:
    windows系统下进行反编译(指令是javap -c)
    可以得到这样的字节码指令:
    在这里插入图片描述

所以,sychronized同步代码块的实现使用的是monitorentermonitorexit这两个指令来实现的;monitorenter用来实现获得锁,而monitorexit用来实现释放锁.

  • synchronized普通同步方法
    windows系统下进行反编译(指令是javap -v)
    可以得到这样的字节码指令:
    [图片]

调用指令将会检查方法ACC_SYCHRONIZED访问标志是否被设置.如果设置了,执行线程会将先持有的monitor锁,然后再执行方法,最后在方法完成(无论是否是正常完成)时释放monitor.

  • synchronized静态同步方法
    windows系统下进行反编译(指令是javap -v)
    可以得到这样的字节码指令:
    在这里插入图片描述

总结:

  1. Java 编译器将 Java 源代码编译为字节码时,synchronized 关键字会被转换为特定的字节码指令。以下是synchronized 相关的字节码指令
  • monitorenter:用于获取锁,当线程执行到 monitorenter 指令时,它会尝试获取与对象关联的监视器monitor)。如果锁被其他线程持有,当前线程将被阻塞,直到锁可用
  • monitorexit:用于释放锁,当线程执行到 monitorexit 指令时,它会释放与对象关联的监视器monitorexit 必须在 monitorenter 成功获取锁后执行,否则会抛出 IllegalMonitorStateException
  1. 异常处理
    在字节码中,synchronized 还处理了异常情况。为了确保在发生异常时也能释放锁,字节码会使用 try-catch 结构:
    如果在 monitorentermonitorexit 之间的代码抛出异常,控制流会跳转到 catch 块,确保 monitorexit 被调用,从而释放锁。

什么是管程(Monitors)

概念

管程(monitors,也称之为**“监视器”),是一种操作系统中的同步机制**,它的引入是为了解决多线程或多进程环境下的并发控制问题。

定义:“管程是一种机制,用于强制并发线程对一组共享变量的互斥访问(或等效操作)。此外,管程还提供了等待线程满足特定条件的机制,并通知其他线程该条件已满足的方法。”

这个定义描述了管程的两个主要功能

  • 互斥访问:管程确保多个线程对共享变量的访问互斥,即同一时间只有一个线程可以访问共享资源,以避免竞态条件数据不一致性问题。
  • 条件等待和通知:管程提供了等待线程满足特定条件的机制,线程可以通过条件变量等待某个条件满足后再继续执行,或者通过条件变量通知其他线程某个条件已经满足。

管程中包含什么

  • 共享变量:管程中包含了共享的变量或数据结构多个线程或进程需要通过管程来访问和修改这些共享资源
  • 互斥锁:互斥锁是管程中的一个关键组成部分,用于确保在同一时间只有一个线程或进程可以进入管程。一旦一个线程或进程进入管程,其他线程或进程必须等待,直到当前线程或进程退出管程。
  • 条件变量:条件变量用于实现线程或进程之间的等待和通知机制。当一个线程或进程需要等待某个条件满足时(比如某个共享资源的状态),它可以通过条件变量进入等待状态。当其他线程或进程满足了这个条件时,它们可以通过条件变量发送信号唤醒等待的线程或进程。
  • 管程接口:管程还包括了一组操作共享资源的接口或方法。这些接口定义了对共享资源的操作,并且在内部实现中包含了互斥锁条件变量管理逻辑其他线程或进程通过调用这些接口来访问共享资源,从而确保了对共享资源的有序访问
#include <iostream>
#include <mutex>
#include <condition_variable>

class Monitor {
private:
    int count;                      // 共享变量
    std::mutex mtx;                 // 互斥锁
    std::condition_variable cond;   // 条件变量
 
public:
    Monitor() : count(0) {}
 
    void enter() {//进入某个管程
        std::unique_lock<std::mutex> lock(mtx);
    }
 
    void exit() {//退出某个管程
        mtx.unlock();
    }
 
    void wait() {//进入等待序列
        count++;
        cond.wait(lock);
        count--;
    }
 
    void notify() {//唤醒某个线程
        if (count > 0) {
            cond.notify_one();
        }
    }
 
    void notifyAll() {//唤醒全部线程
        if (count > 0) {
            cond.notify_all();
        }
    }
};

那么我们之前会存在一个疑问:为什么每个Object对象都可以充当一个锁,现在有了答案

因为:每个Object对象天生都带着一个管程,每个被锁住的对象都会和Monitor关联起来
HotSpot虚拟机中,monitor采用的是ObjectMonitor来实现的.其主要数据结构如下:
在这里插入图片描述
加锁/解锁的过程图:
在这里插入图片描述

总结:每个对象中都有一个指针指向 Monitor对象(也称之为管程或者监视器)的真实地址.每个对象都存在一个monitor与之相关联.当一个monitor被某个线程持有之后,它便处于锁定的状态.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值