synchronized简单理解

这篇具有很好参考价值的文章主要介绍了synchronized简单理解。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、简述

1.1 synchronized介绍

synchronized是一种互斥锁,也成为同步锁,它的作用是保证在同一时刻,被修饰的代码块或方法只会有一个线程执行,以到达保证并发安全效果。在JDK1.6以前,很多人称之为重量级锁,性能不高。但是在JDK1.6以后,对sychronized进行了一些优化,引入了偏向锁,轻量级锁,以及重量级锁。这个时候,synchrionized会根据线程的竞争程度对锁进行升级和降级。

二、使用

2.1 synchronized使用重要性

容易引发线程安全

什么是线程安全?

当多个线程同事,对一个共享资源进行非原子操作(如:修改某个共享资源得数据时),将会出现线程安全问题。

 比如下面代码,加了synchronized和没有加synchronized有明显的区别

public class ThreadTestByPool {

    //获取CPU个数
    private static int cpuCount = Runtime.getRuntime().availableProcessors();
    private static ThreadPoolExecutor threadPool = new ThreadPoolExecutor(cpuCount, cpuCount * 2, 20,
            TimeUnit.SECONDS, new LinkedBlockingQueue<>(20000));
    static {
        System.out.println("我的cpu颗数: " + cpuCount);
    }
    //成员变量,可被多个线程 共享
    private static long n = 0L;

    public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(10000);
        for (int i = 0; i < 10000; i++) {
            threadPool.execute(() -> {
                try {
                    //synchronized (ThreadTestByPool.class) {
                        n++;
                    //}
                } finally {
                    countDownLatch.countDown();
                }
            });
        }
        //等待所有线程执行完毕以后再打印
        countDownLatch.await();
        System.out.println("n++的结果集为:" + n);
    }

}

如果没有加synchronized,三次打印效果

第一次:

我的cpu颗数: 12
n++的结果集为:9988

第二次:

我的cpu颗数: 12
n++的结果集为:9987

第三次:

我的cpu颗数: 12
n++的结果集为:9981

每次执行的结果集都会不一样,但是执行的结果都是不正确的

去掉上面synchronized注释,加了synchronized以后执行,无论执行了几次,返回的n值都是正确的

返回的结果集:

我的cpu颗数: 12
n++的结果集为:10000

2.2 synchronized使用

2.2.1 synchronized三种使用方式

  • 修饰实例方法:作用相当于给实例加锁
  • 修饰静态方法:作用相当于给对象加锁
  • 修饰代码块:指定加锁对象
public class ThreadTest {

    //给方法加锁
    public synchronized  void test(){

    }
    //给静态方法加锁
    public static synchronized  void staticTest(){

    }

    public void test2(){
        // 代码块
        synchronized (this){

        }
        synchronized (ThreadTest.class){

        }
    }

}

注意点:

  • synchronized关键字不能被继承
  • 定义接口方法不能使用synchronized
  • 构造方法不能使用synchronized

2.2.2 案例

  ThreadPoolUtil线程池工具地址:线程池工具类_java-zh的博客-CSDN博客

public class SyncTest {
    //获取CPU个数
    private static int cpuCount = Runtime.getRuntime().availableProcessors();

    static {
        System.out.println("我的cpu颗数: " + cpuCount);
    }

    //成员变量,可被多个线程 共享
    private static  long a = 0L;
    private static long b = 0L;
    private static long c = 0L;

    public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(10000);
        SyncTest lockobj = new SyncTest();
        for (int i = 0; i < 10000; i++) {
            ThreadPoolUtils.execute(() -> {
                try {
                    lockobj.sync(countDownLatch);
                    sync2(countDownLatch);
                    lockobj.sync3(countDownLatch);
                }finally {
                    countDownLatch.countDown();
                }
            });
        }
        // 等待所有的线程执行完再打印
        countDownLatch.await();
        System.out.println("a=" + a);
        System.out.println("b=" + b);
        System.out.println("c=" + c);
    }

    /**
     * 修饰生源方法,其使用的锁对应时当前类所在的实例对象(或者说当前方法的调用对象),也就是this
     *
     * @param countDownLatch
     */
    private synchronized void sync(CountDownLatch countDownLatch) {
        try {
            a++;
        } finally {
           // countDownLatch.countDown();
        }
    }

    /**
     * 修饰静态方法,使用的锁对象是当前类
     *
     * @param countDownLatch
     */
    private synchronized static void sync2(CountDownLatch countDownLatch) {
        try {
            b++;
        }  finally {
            //countDownLatch.countDown();
        }
    }

    private void sync3(CountDownLatch countDownLatch) {
        try {
            synchronized (countDownLatch) {
                c++;
            }
        }  finally {
            //countDownLatch.countDown();
        }
    }

}

 结果:不管执行几次,得出的结果都是正确的

我的cpu颗数: 12
a=10000
b=10000
c=10000

三、 synchronized锁住的真正资源 

参考文献:万字长文分析synchroized_wx6402d9406dac7的技术博客_51CTO博客

1、被synchronized修饰的成员方法,在编译的时候,该方法会生成一个'ACC_SYNCHRONIZED'标记

2、被synchronized修饰的静态方法,在编译的时候,该方法也会生成一个'ACC_SYNCHRONIZED'标记

3、被synchronized修饰的代码块,在执行代码块的逻辑之前,会执行'monitorenter'汇编指令获取锁,执行完逻辑以后,紧接着是'monitorexit'指令,此时,锁被当前持有的线程释放。

3.1 真正的资源ObjectMonitor

实际上,不管被标记的ACC_SYNCHRONIZED方法还是被插入的monitorenter/monitorexit指令的同步块,最终JVM底层都是一个逻辑。在进入该方法或同步块时,必须竞争到给定对象对应的ObjectMonitor上的资源(这里的竞争具体表现为:某个线程通过CAS操作来给定对象对应的ObjectMonitor上的owner指针指向自己,CAS操作成功,就代表获取锁成功,CAS失败,就代表未获取到锁)。同时,必须清楚一点,每个对象都有一个ObjectMonitor与之相对应,且唯一。

        每个对象都和一个监听器相关联。当且仅当监听器于某个对象关联时,它才会被锁定。执行monitorenter的线程获得与对象关联的监听器所有权。

  • 如果与对象关联的监听器条目(count值)计数为0,则线程进入监听器并将其条目设置为1。g该线程就是监听器的所有者。
  • 如果线程已拥有对象关联的监听器,它会重新进入监听器,增加其count计数值
  • 如果另一个线程已拥有与锁对象关联的监听器,线程将会阻塞,知道监听器的count为0,然后再次尝试获得所有权

总结:抢锁的操作对应到JVM底层来说,其实就是CAS设置onwer指针指向当前抢锁线程操作,设置成功即抢锁成功,设置失败即抢锁失败。

3.2 锁对象与ObjectMonitor的关系

  1. 执行monitorenter的线程试图获得与指定对象(synchronized块中的锁对象)关联的ObjectMonitor,每个对象都有一个监听器(ObjectMonitor)与之相关联
  2. 当且仅当监听器(ObjectMonitor)和某个对象产生关联时,ObjectMonitor标志已被锁定/持有

如图(下面的图为重量锁):

synchronized简单理解

 解释:

  1. 尝试获取:当前程获取锁时,会进行cas操作,尝试将owner指针指向当前线程,如果获取成功,则进入同步块执行逻辑
  2. 获取失败后:从cxq队首插入,包装了当前线程的node
  3. 当持有锁的线程释放后:首先肯定的就是他将会owner置为null,好让出资源,然后会从EntryList(如果没有从cxq)队列中挑选一个线程抢锁,被选中的线程叫做Heir presumptive,即叫做"假定继承人","假定继承人"尝试获取锁,但synchronized是非公平的,所以"假定继承人"也不一定能获取锁(所以这也是叫"假定继承人"的原因)。
  4. 当持有锁的线程调用Object的wait方法后:则会将当前线程加入到WaitSet中
  5. 当被Object.notify/notifyAll方法唤醒以后:会将对应的线程从WaitSet移动到cxq或EntryList中去。需要注意的是,当调用一个锁对象的wait或notify/notifyAll方法时,如果当前锁的状态是偏向锁或轻量级锁则会先膨胀成重量级锁。

四、锁的升级

参考文献:并发编程笔记:synchronized关键字浅析_zhoutaoping1992的博客-CSDN博客

4.1 锁的状态

无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁

  • 无锁:在没有开启偏向锁或者偏向锁延迟未到或者批量撤销后,对象创建完成后,没有任何线程使用该对象来加锁,此时该对象状态是无锁。对应的Java对象头lock=1,biased_lock=0。(biased_lock=0的含义表示不可偏向)
  • 偏向锁:对象创建完成后处于可偏向状态,某一个对象使用该对象来加锁,那么这个对象就偏向这个线程。对应的java头lock=1,biased_lock=1。
  • 轻量级锁:当有多个线程交替使用某个对象来加锁,并无无竞争的情况下,该对象会成为轻量级锁。对应的Java对象头lock=00
  • 重量级锁:当有多个线程竞争使用某个对象来加锁,该对象会成为重量级锁。对应的JAV对象头lock=10

4.2 JVM相关参数

        UseBiasedLocking 是否使用偏向锁,默认true
BiasedLockingStartupDelay 偏向锁开启延迟时间,默认4000ms
BiasedLockingBulkRebiasThreshold 偏向锁重偏向阈值,默认20次
BiasedLockingBulkRevokeThreshold 偏向锁撤销阈值,默认40次

4.3 synchronized关键字执行流程

如图(不考虑重偏向和批量撤销的情况):

synchronized简单理解文章来源地址https://www.toymoban.com/news/detail-475761.html

  1. 无锁(lock为01,biased_lock为0,不可偏向状态)状态没有任何路径升级为偏向锁状态
  2. 轻量锁和重量锁在synchronized代码块执行完毕后会将锁对象重置为无锁状态,而偏向锁执行完毕后不会重置锁状态
  3. 升级路径只有无锁或偏向锁升级为轻量级锁和轻量级锁升级为重量级锁
  4. 在偏向锁升级为轻量级锁时,会进行锁撤销操作,将锁专题修改为无锁状态,然后再升级。 

到了这里,关于synchronized简单理解的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 关于对【oracle索引】的理解与简述

    【版权声明】未经博主同意,谢绝转载!(请尊重原创,博主保留追究权) https://blog.csdn.net/m0_69908381/article/details/131094864 出自【进步*于辰的博客】 无论 oracle 、 mysql ,亦或者其他数据库,几乎所有企业级项目都会使用 索引 ,因为这能大大提升程序性能。 oracle 索引如何实现

    2024年02月08日
    浏览(26)
  • 关于对【mysql存储过程】的理解与简述

    【版权声明】未经博主同意,谢绝转载!(请尊重原创,博主保留追究权) https://blog.csdn.net/m0_69908381/article/details/130857854 出自【进步*于辰的博客】 存储过程的细节很多,而在实际工作中又未必都能涉及这些细节,工作时间一长,就可能忘记,于是特来写这篇文章,既是为自

    2024年02月07日
    浏览(27)
  • 关于对Java单例模式的理解与简述

    【版权声明】未经博主同意,谢绝转载!(请尊重原创,博主保留追究权) https://www.cnblogs.com/cnb-yuchen/p/17954739 出自【进步*于辰的博客】 参考笔记一,P28.3、P29.9、P71.1。 目录 1、什么是单例模式? 2、如何实现单例模式? 3、单例模式的两种形式 3.1 形式一:“饿汉式” 3.2 形

    2024年02月03日
    浏览(26)
  • 深入理解电源纹波与噪声并正确测量简述

    板级电源系统又称电源分配网络(Power Delivery Network,PDN),我们可以将此系统分为稳压模块端(VRM)与用电芯片端(Sink)。电源的AC特性主要包括电源纹波与噪声,我们通常将两者混为一谈,其实纹波与噪声是有区别的,二者的产生原因与解决策略迥异。 纹波 纹波是由直流

    2024年02月14日
    浏览(33)
  • 【计算机视觉】简述对EQ-Net的理解

    最近又看了一些点云分割的文章,近两年点云分割的文章是真的少,不知道是不是点云分割算法接近了末端。这篇文章主要提出了一个基于查询方法的统一范式,它解决了一些不仅仅是点云分割的问题,还解决了三维点云分类和三维目标检测的问题。 文章整体结构如上图,可

    2024年02月16日
    浏览(26)
  • 关于对【java中的Lambda表达式】的理解与简述

    【版权声明】未经博主同意,谢绝转载!(请尊重原创,博主保留追究权) https://blog.csdn.net/m0_69908381/article/details/130522535 出自【进步*于辰的博客】 启发博文:《Lambda表达式超详细总结》(转发)。 这是我系统学习Lambda表达式时参考的文章。在下文中,我会引用这篇博文中的

    2024年02月05日
    浏览(62)
  • 关于二进制的原码、补码和反码,以及表示范围、常见位运算符和进制转换的理解与简述

    【版权声明】未经博主同意,谢绝转载!(请尊重原创,博主保留追究权) https://www.cnblogs.com/cnb-yuchen/p/17963363 出自【进步*于辰的博客】 参考笔记一,P3.13、P5.1;笔记三,P43.1/3、P44.1。 注:我暂且没有整理关于二进制、原码、补码和反码等概念的理论,本文中的阐述都基于

    2024年02月02日
    浏览(38)
  • Inline内联函数简单理解

    How to Write it? example- 特点 编译器会将函数调用直接展开为函数体代码 人话: 直接将函数体里面的计算方法直接放到函数调用里,类似于宏替换。和#include 很像,但不相同。 编译后代码体量会变大。 用途 因为调用普通函数需要 开辟栈空间 ,调用完成后要 回收栈空间 如果是内

    2024年03月11日
    浏览(35)
  • 简单理解区块链

    这篇是挖矿篇详细介绍区块链之挖矿-CSDN博客的后置文章,咱们通过之前的解释进一步复习学习区块链叭! 区块链,就是一个又一个区块组成的链条。每一个区块中保存了一定的信息,它们按照各自产生的时间顺序连接成链条。这个链条被保存在所有的服务器中, 只要整个系

    2024年02月05日
    浏览(19)
  • 区块链技术简单理解

    区块链可以借由密码学,串接并保护内容的串联交易记录(又称区块)。在区块链中,区块内容具有难以篡改的特性,

    2023年04月11日
    浏览(24)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包