Fork me on GitHub

Java多线程——sychronized

概述

关键字synchronized的作用是实现线程间的同步。它的工作是对同步的代码加锁,使得每一次,只能有一个线程进入同步块,从而保证线程间的安全性。

  1. 直接作用于实例方法(普通同步方法):对当前实例加锁,进入同步代码前要获得当前实例的锁。

    public synchronized void test() {...}
    该操作等价于在方法体前后包装了一个synchronized(this),或者说给当前类所在对象加上了锁对象。
  2. 直接作用于静态方法(静态同步方法):对当前类加锁(当前类的Class对象),进入同步代码前要获得当前类的锁。

    public synchronized static void test() {...}

    这个锁操作等价于锁住了当前类的class对象,比如这个类叫作A,那么相当于执行了synchronized(A.class)操作。

synchronized主要就是这两种方式,其余的操作方式都是在这个基础上衍生出来的。例如“代码块加锁”:

synchronized (Object) {
    ...;
}

锁住的其实并不是代码块,而是object这个对象,因此在其他代码中也发生synchronized(object)时就会发生互斥。

其实synchronized(this)也是用这样的方式衍生出来的,只是在方法前面增加synchronized与之有一个区别,就是它的粒度并不是在方法级别,而是在某个指定的代码块级别。

除了用于线程同步、确保线程安全外,synchronized还可以保证线程间的可见性和有序性:

  • 可见性:从可见性角度讲,synchronized可以完全代替volatile的功能。
  • 有序性:被synchronized限制的多线程是串行执行的,限制每次只有一个线程可以访问同步块。

举几个例子

  1.当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。

  2.当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该object中的非synchronized(this)同步代码块。

  3.当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对object中所有其他sychronized(this)同步代码块的访问将阻塞。

  4.第三个例子也可以用synchronized方法来实现。也就是说,当一个线程访问object的一个synchronized(this)同步代码块时,它就获得了这个object的对象锁。结果,其它线程对该object对象所有同步代码部分的访问都被暂时阻塞。(方法m4t2xx的synchronized关键字可以在public后,也可以在public前)

更多例子参考:http://www.cnblogs.com/skywang12345/p/3479202.html

原理

在JVM规范中可以看到 synchronized 在 JVM 里的实现原理,JVM 基于进入和退出 Monitor 对象来实现方法同步和代码块同步,不过两者的实现细节不一样。

代码块同步是使用 monitorenter 和 monitorexit 指令实现的,而方法同步是使用另外一种方式实现的,细节在 JVM 规范里并没有详细说明。

monitorenter指令在编译后插入到同步代码块的开始位置,而 monitorexit 插入到方法结束处和异常处,JVM 保证每个 monitorenter 必有对应的 monitorexit 与之匹配。

  关于monitor指令:

public class Test2 {
    private static Object lock = new Object();
    public static void main(String[] args) {
        int i = 0;
        synchronized(lock) {
            i++;
        }
    }
}

  终端中编译,然后javap反编译class文件:

  箭头指向出,从上到下分别是Enter the monitor associated with object; Exit the monitor associated with object; Be sure to exit monitor; 

  其中红框处,表示 i++ 的指令,iinc 1,1:将第1个slot所在的int类型的本地变量加1;aload_2:将第2个slot所在的本地变量推到栈顶

任何对象都有一个 monitor 与之关联,当且一个 monitor 被持有后,它将处于锁状态。线程执行到 monitorenter 指令时,将会尝试获取对象所对应的 monitor 的所有权,即尝试获取对象的锁。

所以说一个对象只有一把锁。

synchronized用的锁是存在Java对象头里的,关于Java对象头:JVM——深入分析对象的内存布局

参考资料

[1] javap命令. Java特种兵, 3.2.1-javap命令工具

[2] synchronized原理. Java并发编程的艺术, 2.2-synchronized的实现原理与应用

[3] synchronized概念. 实战Java高并发程序设计, 2.7-线程安全的概念与 synchronized

posted @ 2016-07-22 22:24  郑斌blog  阅读(1132)  评论(0编辑  收藏  举报