Synchronized

欢迎来到狗哥多线程系列连载。本篇是线程相关的第九篇,前八篇分别是:

创建线程到底有几种方式?线程有多少种状态?Runnable 一定在执行任务吗?万字长文,Thread 类源码解析!wait、notify/notifyAll 解析线程之生产者消费者模式狗哥肝了一下午的线程池线程池的拒绝策略线程池的阻塞队列

  • synchronized 是 Java 的一个关键字,它能够将代码块 (方法) 锁起来
  • synchronized 是 互斥锁,同一时间只能有一个线程进入被锁住的代码块(方法)
  • synchronized 通过监视器(Monitor)实现锁。java 一切皆对象,每个对象都有一个监视器(锁标记),而 synchronized 就是使用对象的监视器来将代码块 (方法) 锁定的

为什么用 Synchronized ?

我们加锁的原因是为了线程安全,而线程安全最重要就是保证原子性和可见性

  • 被 Synchronized 修饰的代码块(方法),同一时间只能有一个线程执行,从而保证原子性。
  • synchronized 通过使用监视器,来实现对变量的同步操作,保证了其他线程对变量的可见性。

怎么用 Synchronized ?

  • 修饰普通同步方法:锁是当前实例对象
  • 修饰静态同步方法:锁是当前类的 Class 对象
  • 修饰同步代码块:

修饰普通同步方法

public class BigBigDog {

    // 修饰普通同步方法,普通方法属于实例对象    // 锁是当前实例对象 BigBigDog 的监视器    public synchronized void testCommon(){        // doSomething    }

}

多个实例对象调用不会阻塞,比如:

public class BigBigDog {

    // 修饰普通同步方法,普通方法属于实例对象    // 锁是当前实例对象 BigBigDog 的监视器    public synchronized void testCommon() {        int i = 0;        do {            try {                Thread.sleep(1000);            } catch (InterruptedException e) {                e.printStackTrace();            }            System.out.println("Common function is locked " + i);        } while (i++ 10);    }

}

测试方法:

public class Main {

    public static void main(String[] args) {        BigBigDog bigBigDog = new BigBigDog();        BigBigDog bigBigDog1 = new BigBigDog();        new Thread(bigBigDog::testCommon).start();        new Thread(bigBigDog1::testCommon).start();    }

}

结果:异步运行,因为锁的是实例对象,也就是锁不同,所以并不会阻塞

Common function is locked 0Common function is locked 0Common function is locked 1Common function is locked 1Common function is locked 2Common function is locked 2Common function is locked 3Common function is locked 3···

修饰静态同步方法

public class BigBigDog {

    // 修饰静态同步方法,静态方法属于类(粒度比普通方法大)    // 锁是类的锁(类的字节码文件对象:BigBigDog.class)    public static synchronized void testStatic() {        // doSomething    }

}

synchronized 修饰静态方法获取的是类锁 (类的字节码文件对象),synchronized 修饰普通方法获取的是对象锁。也就是说:获取了类锁的线程和获取了对象锁的线程是不冲突的!测试下:

public class BigBigDog {

    // 修饰普通同步方法,普通方法属于实例对象    // 锁是当前实例对象 BigBigDog 的监视器    public synchronized void testCommon() {        int i = 0;        do {            try {                Thread.sleep(1000);            } catch (InterruptedException e) {                e.printStackTrace();            }            System.out.println("Common function is locked " + i);        } while (i++ 10);    }

    // 修饰静态同步方法,静态方法属于类(粒度比普通方法大)    // 锁是类的锁(类的字节码文件对象:BigBigDog.class)    public static synchronized void testStatic() {        int i = 0;        do {            try {                Thread.sleep(1000);            } catch (InterruptedException e) {                e.printStackTrace();            }            System.out.println("Static function is locked " + i);        } while (i++ 10);    }}
public class Main {

    public static void main(String[] args) {        BigBigDog bigBigDog = new BigBigDog();        new Thread(bigBigDog::testCommon).start();        new Thread(BigBigDog::testStatic).start();    }

}

结果:异步运行,并不冲突。

Common function is locked 0Static function is locked 0Common function is locked 1Static function is locked 1Common function is locked 2Static function is locked 2Common function is locked 3Static function is locked 3

修饰同步代码块

public class BigBigDog {

    public void test3() {        // 修饰代码块,锁是括号内的对象        // 这里的 this 是当前实例对象 BigBigDog 的监视器        synchronized (this) {            // doSomething        }    }}
public class BigBigDog {

    // 使用 object 的监视器作为锁    private final Object object = new Object();

    public void test4() {        // 修饰代码块,锁是括号内的对象        // 这里是当前实例对象 object 的监视器        synchronized (object) {            // doSomething        }    }

}

除了第一种以 this 当前对象的监视器为锁的情况。对于同步代码块,Java 还支持它持有任意对象的锁,比如第二种的 object 。那么这两者有何区别?这两者并无本质区别,但是为了代码的可读性。还是更加建议用第一种(第二种,无缘无故定义一个对象)

Synchronized 的原理

有以下代码:test 是静态同步方法,test1 是普通同步方法,test2 则是同步代码块。

public class SynchronizedTest {

    // 修饰静态方法    public static synchronized void test() {        // doSomething    }

    // 修饰方法    public synchronized void test1(){    }

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

通过命令看下 synchronized 关键字到底做了什么事情:首先用 cd 命令切换到 SynchronizedTest.java 类所在的路径,然后执行 javac SynchronizedTest.java,于是就会产生一个名为 SynchronizedTest.class 的字节码文件,然后我们执行 javap -c SynchronizedTest.class,就可以看到对应的反汇编内容,如下:

Z:\IDEAProject\review\review_java\src\main\java\com\nasus\thread\lock>javac -encoding UTF-8 SynchronizedTest.java

Z:\IDEAProject\review\review_java\src\main\java\com\nasus\thread\lock>javap -c SynchronizedTest.classCompiled from "SynchronizedTest.java"public class com.nasus.thread.lock.SynchronizedTest {  public com.nasus.thread.lock.SynchronizedTest();    Code:       0: aload_0       1: invokespecial #1                  // Method java/lang/Object."":()V       4: return  public static synchronized void test();    Code:       0: return  public synchronized void test1();    Code:       0: return  public void test2();    Code:       0: aload_0       1: dup       2: astore_1       3: monitorenter  // 监视器进入,获取锁       4: aload_1       5: monitorexit  // 监视器退出,释放锁       6: goto          14       9: astore_2      10: aload_1      11: monitorexit  // 监视器退出,释放锁      12: aload_2      13: athrow      14: return    Exception table:       from    to  target type           4     6     9   any           9    12     9   any}

test2 同步代码块解析

主要看 test2 同步代码块的反编译内容,可以看出 synchronized 多了 monitorenter 和 monitorexit 指令。把执行 monitorenter 理解为加锁,执行 monitorexit 理解为释放锁,每个对象维护着一个记录着被锁次数的计数器。未被锁定的对象的该计数器为 0

那这里为啥只有一次 monitorenter 却有两次 monitorexit ?

  • JVM 要保证每个 monitorenter 必须有与之对应的 monitorexit,monitorenter 指令被插入到同步代码块的开始位置,而 monitorexit 需要插入到方法正常结束处和异常处两个地方,这样就可以保证抛异常的情况下也能释放锁

执行 monitorenter 的线程尝试获得 monitor 的所有权,会发生以下这三种情况之一:

a. 如果该 monitor 的计数为 0,则线程获得该 monitor 并将其计数设置为 1。然后,该线程就是这个 monitor 的所有者。b. 如果线程已经拥有了这个 monitor ,则它将重新进入,并且累加计数。c. 如果其他线程已经拥有了这个 monitor,那个这个线程就会被阻塞,直到这个 monitor 的计数变成为 0,代表这个 monitor 已经被释放了,于是当前这个线程就会再次尝试获取这个 monitor。

monitorexit

monitorexit 的作用是将 monitor 的计数器减 1,直到减为 0 为止。代表这个 monitor 已经被释放了,已经没有任何线程拥有它了,也就代表着解锁,所以,其他正在等待这个 monitor 的线程,此时便可以再次尝试获取这个 monitor 的所有权。

test1 普通同步方法

它并不是依靠 monitorenter 和 monitorexit 指令实现的,从上面的反编译内容可以看到,synchronized 方法和普通方法大部分是一样的,不同在于,这个方法会有一个叫作 ACC_SYNCHRONIZED 的 flag 修饰符,来表明它是同步方法。(在这看不出来需要看 JVM 底层实现)

当某个线程要访问某个方法的时候,会首先检查方法是否有 ACC_SYNCHRONIZED 标志,如果有则需要先获得 monitor 锁,然后才能开始执行方法,方法执行之后再释放 monitor 锁

PS:想要进一步深入了解 synchronized 就必须了解 monitor 对象,对象有自己的对象头,存储了很多信息,其中一个信息标示是被哪个线程持有。可以参考这篇博客:@chenssy 大神写的很好,建议拜读下。

https://blog.csdn.net/chenssy/article/details/54883355

-END-

如果看到这里,喜欢这篇文章的话,请帮点个好看。微信搜索「一个优秀的废人」,关注后回复「 1024」送你一套完整的 java 教程(包括视频)。回复「 电子书」送你全编程领域电子书 (不只Java)。

教程节选

synchronized不能锁静态变量_肝了一下午的 Synchronized 解析!相关推荐

  1. synchronized不能锁静态变量_多线程编程不可错过——彻底理解synchronized

    持续分享互联网研发技术,欢迎关注我.本人是一线架构师,有问题可以沟通. 1. synchronized简介 在学习知识前,我们先来看一个现象: public class SynchronizedDem ...

  2. synchronized不能锁静态变量_面试官:请说一下对象锁和类锁的区别

    有锁才有自由 生活中不存在绝对的自由,绝对的自由通常对应的无序和混沌,只有在道德.法律.伦理的约束下的相对自由,才能使人感受到自由. 而在多线程编程中,锁是至关重要的,锁就是道德,就是法律约束,没有锁 ...

  3. java 线程 静态变量_线程之间是否共享静态变量?

    问题 我的老师在一个关于线程的上层java课上说了一些我不确定的东西. 他表示以下代码不一定会更新2910104598变量.根据他的说法,两个线程不一定共享静态变量,特别是在每个线程(主线程与Read ...

  4. @value 静态变量_你理解 PHP 中的静态方法吗?

    它仍然是一个有争议的话题.让我们尝试了解原因. 函数中的静态变量 您可能已经看过以下代码: function testStatic() {static $x = 0;$x++;return $x; } ...

  5. @value 静态变量_面试官:为什么静态方法不能调用非静态方法和变量?

    这个可能很多人之前学习jvm的时候都会遇到,属于一个小问题,写这篇文章的原因是我在看java相关的面试题目中遇到的,因此顺手总结一下: 一.例子 我们先看效果: 我们在静态方法main中调用非静态变量 ...

  6. python局部静态变量_全局变量、局部变量和静态变量

    全局变量和局部变量在写代码时需要区分清楚,不然会出大问题.不同语言定义不同范围的变量的写法有很大的区别. 那么静态变量是在什么场景下用到呢?我们来假设这样一个场景:在函数内部定义的变量,当程序执行到它 ...

  7. c语言指针访问 静态变量_使用C中的指针访问变量的值

    c语言指针访问 静态变量 As we know that a pointer is a special type of variable that is used to store the memor ...

  8. python定义静态变量_对Pyhon实现静态变量全局变量的方法详解

    python不能像C++一样直接定义一个static变量或者通过extern来导入别的库的变量而实现数据共享,但是python的思想是通过模块化来解决这个问题,就是通过模块来实现全局变量. 首先新建一 ...

  9. java静态方法调用非静态变量_[java]静态方法访问非静态方法的方法

    是不是是有点拗口哈,在刚开始的开发中,经常会碰到"无法访问非静态方法",如下: Test.java public Test{ public void notStatic(){ Sy ...

最新文章

  1. 新零售模式开启,2018杭州无人店展览会
  2. python中点的作用_一分钟了解Python中“*”的作用
  3. MySQL-索引优化篇(2)_使用索引扫描来优化排序
  4. 【bzoj4592】[Shoi2015]脑洞治疗仪
  5. 纠正一个错误,分布式系统关注点第17篇
  6. jooq权限配置_将jOOQ与Spring结合使用:配置
  7. php private ,public protected三者的区别
  8. 【Android重量级】高仿大众点评源码
  9. Java POI Word 写文档
  10. CSDN积分获取方法
  11. neo4j图数据库导入scv文件
  12. ADT下载地址,完整版
  13. linux generic netlink实现机制:注册、创建
  14. stm32f4红外传感器模块 和人体感应模块HCSR501
  15. LoRa的码片速率chip rate
  16. Python实现计算机的自动化控制!
  17. Linux操作系统——切换到root用户及其他用户
  18. 简报 | QuarkChain北京站见面会即将召开;HiCTO创始人/前百姓网CTO潘晓良加入CarBlock;LinkEye上线FCoin受关注;Coinbase推出托管服务
  19. Hadoop自动化安装脚本
  20. 信息学奥赛一本通1358 中缀表达式值(expr) ()

热门文章

  1. 11、流程控制语句详解,IF,CASE,LOOP,LEAVE, ITERATE,REPEAT,WHILE
  2. ACM之【文件操作】
  3. Acwing第 28 场周赛【完结】
  4. Synchronize对象属性改变
  5. Redis集群的重启
  6. php学历低,学历低学起php来难不难
  7. java怎么实现人物的行走,js键盘事件实现人物的行走
  8. python的turtle画曲线_python的turtle模块画折线图
  9. 女朋友都能看懂,Spring如何解决循环依赖?
  10. 蓝桥杯-操作格子(java)