• 【多线程】synchronized的特性


    synchronized 的特性

    互斥

    synchronized 会起到互斥效果,某个线程执行到某个对象的 synchronized 中时,其他线程如果也执行到同一个对象 synchronized 就会阻塞等待。进入 synchronized 修饰的代码块,相当于 加锁。退出 synchronized 修饰的代码块,相当于 解锁。synchronized用的锁是存在Java对象头里的。synchronized的底层是使用操作系统的mutex lock实现的。

    可重入

    synchronized 同步块对同一条线程来说是可重入的,不会出现死锁。可重入就是一个线程对同一把锁连续加锁两次不会出现死锁。

    我们来看下面的代码👇👇👇

    static class Counter {
    	public int count = 0;
    	synchronized void increase() {
    		count++;
    	}
    	synchronized void increase2() {
    		increase();
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    increase 和 increase2 两个方法都加了 synchronized, 此处的 synchronized 都是针对 this 当前对象加锁的。在调用 increase2 的时候, 先加了一次锁, 执行到 increase 的时候, 又加了一次锁. (上个锁还没释放, 相当于连续加两次锁),因为synchronized是可重入锁,所以上面的代码不会出现死锁问题。

    在可重入锁的内部,包含了 “线程持有者” 和 “计数器” 两个信息。如果某个线程加锁的时候,发现锁已经被人占用,但是恰好占用的正是自己,那么仍然可以继续获取到锁,并让计数器自增。解锁的时候计数器递减为 0 的时候,才真正释放锁。 (才能被别的线程获取到)

    synchronized的使用

    直接修饰普通方法:锁的 SynchronizedDemo 对象

    public class SynchronizedDemo {
    	public synchronized void fun() {
            //......
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    修饰静态方法:锁的 SynchronizedDemo 对象

    public class SynchronizedDemo {
    	public synchronized static void fun() {
            //......
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    修饰代码块:明确指定锁哪个对象

    public class SynchronizedDemo {
    	public void method() {
            //锁的是当前对象
    		synchronized (this) {
                //......
    		}
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    public class SynchronizedDemo {
    	public void method() {
            //锁的是类对象
    		synchronized (SynchronizedDemo.class) {
                //......
    		}
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    加锁过程

    JVM将synchronized锁分为:无锁、偏向锁、轻量级锁、重量级锁四种状态。会根据情况,进行依次升级。
    在这里插入图片描述

    1.偏向锁

    第一个尝试加锁的线程,优先进入偏向锁状态。

    偏向锁不是真的加锁,只是在对象头中做一个偏向锁的标记,记录这个锁属于哪个线程,后续如果没有所来竞争就不进行同步操作,避免了加解锁的开销。如果有其他线程来竞争就会取消偏向锁状态,进入轻量级锁的状态。偏向锁的本质是延迟加锁,能不加锁就不加锁,避免不必要的加解锁开销。但是标记还是要做,不然就无法区分何时需要正真需要加锁。

    2.轻量级锁

    随着其他线程的竞争,偏向锁状态消除,进入轻量级锁状态(自适应的自旋锁)此处的轻量级锁通过CAS来实现的

    通过CAS检查并更新一块内存(比如null=>该线程被调用),如果更新成功则认为加锁成功,如果更新失败,则认为该锁被占用,继续自旋式等待。(自旋锁是一直让CPU空转,比较浪费CPU资源,因此这里的自旋不会一直进行,而是达到一定次数或时间就会停止自旋,这也就是“自适应”)

    3.重量级锁

    如果锁竞争进一步激烈,自旋锁就不能快速获取锁的状态了,进而就会升级为重量级锁(此处的重量级锁就是用到内核提供的mutex)

    执行加锁操作,先进入内核态,在内核态判断当前锁是否已经被占用,如果该锁没有被占用,则加锁成功,并切换回用户态。如果该所被占用,则加锁失败,此时线程进入锁的等待队列,挂起,等待被操作系统唤醒。

    总结:

    1.synchronized可以保证内存可见性

    2.synchronized初始使用乐观锁策略。 当发现锁竞争比较频繁的时候,就会自动切换成悲观锁策略。

    3.synchronized不是读写锁

    4.synchronized开始是一个轻量级锁,如果锁冲突比较严重,就会变成重量级锁。

    5.synchronized中的轻量级锁策略大概率就是通过自旋锁的方式实现的。

    6.synchronized是非公平锁。

    7.synchronized是可重入锁。

  • 相关阅读:
    QT with OpenGL(Shadow Mapping)(面光源篇)
    第23篇 基于Qt实现PID温度加热控制系统
    CentOS、银河麒麟高级服务器版V10安装mysql5.7
    2:开发环境搭建-Java Web
    如何理解 Spring Boot 中的 Starter?
    ACL原理与配置(一、前言 二、ACL概述 三、ACL的组成实验演示通配符(1)通配符(2) ACL的分类与标识ACL基本配置 ACL高级配置​编辑)
    Amazon 消息订阅对接
    java高校多媒体设备运维管理系统服务端计算机毕业设计MyBatis+系统+LW文档+源码+调试部署
    9-Dubbo架构设计与底层原理-集群容错之 Directory
    【C++20】模块
  • 原文地址:https://blog.csdn.net/qq_58032742/article/details/134299036