Java中的synchronized

锁的概念大家都比较熟悉了,当某个线程A已经持有了一个锁,当线程B尝试进入被这个锁保护的代码段的时候.就会被阻塞.而锁的操作粒度是”线程”,而不是调用。每个Java对象都可以用做一个实现同步的互斥锁(synchronized),这些锁被称为内置锁。线程进入同步代码块或方法时自动获得内置锁,退出同步代码块或方法时自动释放该内置锁。进入同步代码块或者同步方法是获得内置锁的唯一途径。

synchronized的使用本质上有以下三种类型

  • 对于同步方法,锁是当前实例对象。
  • 对于静态同步方法,锁是当前对象的Class对象。
  • 对于同步方法块,锁是Synchonized括号里配置的对象。

使用样式上,有以下5种使用方法

  • 在方法上进行同步, 分为(1)instance method/(2)static method, 这两个的区别后面说
  • 在内部块上进行同步, 分为(3)synchronize(this), (4)synchonrize(XXX.class), (5)synchonrize(mutex)
public class SyncMethod {
    private int value = 0;
    private final Object mutex = new Object();

    public synchronized int incAndGet0() {
       return ++value;
    }

    public int incAndGet1() {
        synchronized(this){
            return ++value;
        }
    }

    public int incAndGet2() {
       synchronized(SyncMethod.class){
            return ++value;
        }
    }

    public int incAndGet3() {
       synchronized(mutex){
            return ++value;
        }
    }

    public static synchonrize int incAndGet4() {
       synchronized(mutex){
            return ++value;
        }
    }
}

作为修饰符加在方法声明上, synchronized修饰非静态方法时表示锁住了调用该方法的堆对象
修饰静态方法时表示锁住了这个类在方法区中的类对象X.class
synchronized(X.class) 使用类对象作为monitor.
synchronized(this)和synchronized(mutex) 都是对象锁, 同一时间每个实例都保证只能有一个线程能访问块中资源.

 

sychronized的对象最好选择引用不会变化的对象(例如被标记为final,或初始化后永远不会变), 虽然synchronized是在对象上加锁, 但是它首先要通过引用来定位对象, 如果引用会变化, 可能带来意想不到的后果。JDK1.5后提供了Lock,那么什么时候使用synchronized,什么时候使用Lock?
ReentrantLock的优势在于为程序员提供了更多的选择和更好地扩展性,比如公平性锁和非公平性锁,读写锁,CountLatch等。Lock实现了synchronized的所有功能,同时提供了更加高级和更加细粒度的控制。比如ReentrantLock就有如下几项:等待可中断、可实现公平锁,以及锁可以绑定多个条件。从以往的比较来看,ReentrantLock的性能比较synchronized相对要好一些。随着JDK6以及后续一些版本的优化(引入偏向锁,自旋锁,轻量级锁,重量级锁),他们的差别已经很小了。至少从官方来看还是比较倾向于使用synchronized。

 

问题1:synchronized会不会发生死锁?

 

问题2:为什么synchronized需要绑定对象?
JVM规范规定JVM基于进入和退出Monitor对象来实现方法同步和代码块同步,但两者的实现细节不一样。代码块同步是使用monitorenter和monitorexit指令实现,而方法同步是使用另外一种方式实现的,细节在JVM规范里并没有详细说明,但是方法的同步同样可以使用这两个指令来实现。monitorenter指令是在编译后插入到同步代码块的开始位置,而monitorexit是插入到方法结束处和异常处, JVM要保证每个monitorenter必须有对应的monitorexit与之配对。任何对象都有一个 monitor 与之关联,当且一个monitor 被持有后,它将处于锁定状态。线程执行到 monitorenter 指令时,将会尝试获取对象所对应的 monitor 的所有权,即尝试获得对象的锁。那么,这里就和对象相关了,也就是说对象里一定有地方保存了当前对象的锁定状态。实际上 Java对象的内存布局如下:对象头(Header),实例数据(Instance Data)和对齐填充(Padding)。对象头(Header)64位系统上占用16bytes(不考虑jvm压缩), Mark Word 用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等等。例如在32位的HotSpot虚拟机中对象未被锁定的状态下,Mark Word的32个Bits空间中的25Bits用于存储对象哈希码(HashCode),4Bits用于存储对象分代年龄,2Bits用于存储锁标志位,1Bit固定为0。

 

问题3:什么是类对象?
其实从某种意义上说,在java中有两种对象:实例对象和Class对象。实例对象就是我们平常定义的一个类的实例,而Class对象是没办法用new关键字得到的,因为它是jvm生成用来保存对应类的信息的,换句话说,当我们定义好一个类文件并编译成.class字节码后,编译器同时为我们创建了一个Class对象并将它保存.class文件中。Class对象是jvm用来保存对象实例对象的相关信息的,除此之外,我们完全可以把Class对象看成一般的实例对象,事实上所有的Class对象都是类Class的实例。得到一个实例对象对应的Class对象有以下三种方式:

Class c = meihuan.getClass(); 
Class c = Class.forName("meihuan"); 
Class c = meihuan.class;

 

posted on 2016-08-02 15:55  mh-lhw  阅读(1171)  评论(0编辑  收藏  举报

导航