深入理解synchronized锁和使用场景

synchronized原理

在java中,提供了synchronized实现共享对象的并发访问。当一个线程试图访问同步代码块时,它首先必须得到锁,退出或抛出异常时必须释放锁。通常有三种使用方式,如下:

  1. 对于普通同步方法,锁是当前实例对象。
  2. 对于静态同步方法,锁是当前类的Class对象。
  3. 对于同步方法块,锁是Synchonized括号里配置的对象。

JMM理解synchronized

在内存模式(JMM)中,synchronized的happens-before关系为,对一个synchronized的释放锁happens-before对于同一个synchronized锁的获取锁。实现的内存语义是,在一个线程释放synchronized锁时,JMM会把该线程使用的共享变量从该线程的本地缓存刷新到主内存;当其他线程获取synchronized锁时,JMM会把获取锁的线程中的本地缓存设置为无效,然后从主内存中获取共享变量。

synchronized实现原理

synchronized是在JVM虚拟机中实现的,JVM基于进入和退出Monitor对象来实现方法同步和代码块同步。代码块同步是使用monitorenter和monitorexit指令实现的。任何对象都有一个monitor与之关联,当且一个monitor被持有后,它将处于锁定状态。线程执行到monitorenter指令时,将会尝试获取对象所对应的monitor的所有权,即尝试获得对象的锁。

在java中,synchronized用的锁是存在Java对象头里的。synchronized锁有四种状态,分别为无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态,这几个状态会随着竞争情况逐渐升级。锁可以升级但不能降级,意味着偏向锁升级成轻量级锁后不能降级成偏向锁。其深入的原理可以参考深入理解Java并发之synchronized实现原理,锁的特性如下:

  • 偏向锁

在对象头中记录线程id,锁标志为偏向锁。加锁和解锁不需要额外的消耗,通常适用于一个线程占用共享变量。

  • 轻量级锁

在对象头中存放的是指向方栈桢中锁记录的指针,锁标志为轻量级锁。线程竞争锁失败时,进行自旋获取锁,不会阻塞。

  • 重量级锁

在对象头中存放的是指向方栈桢中锁记录的指针,锁标志为重量级锁。线程竞争锁失败时,竞争线程会阻塞。

synchronized使用场景

  • 线程安全类

在java8源码中,提供了Hashtable,Vector和Stack线程安全的集合类,都是基于synchronized实现。以Hashtable为例,在散列表的基础上,对集合的操作方法都添加了synchronized修饰。Vector和Stack都是基于数组列表实现,也使用synchronized实现其同步访问。以Hashtable为例,其部分源码如下:

public class Hashtable<K,V>
    extends Dictionary<K,V>
    implements Map<K,V>, Cloneable, java.io.Serializable {

    /**
     * 存放数据的数组
     */
    private transient Entry<?,?>[] table;

    /**
     * 元素个数
     */
    private transient int count;

    /**
     * 扩容个数限制
     */
    private int threshold;

    /**
     * 负载因子
     */
    private float loadFactor;

    /**
     * 修改次数
     */
    private transient int modCount = 0;


    /**
     * synchronized 修饰地获取元素
     */
    @SuppressWarnings("unchecked")
    public synchronized V get(Object key) {
        Entry<?,?> tab[] = table;
        int hash = key.hashCode();
        int index = (hash & 0x7FFFFFFF) % tab.length;
        for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
            if ((e.hash == hash) && e.key.equals(key)) {
                return (V)e.value;
            }
        }
        return null;
    }


    /**
     * synchronized 修饰地添加元素
     */
    public synchronized V put(K key, V value) {
        // Make sure the value is not null
        if (value == null) {
            throw new NullPointerException();
        }

        // Makes sure the key is not already in the hashtable.
        Entry<?,?> tab[] = table;
        int hash = key.hashCode();
        int index = (hash & 0x7FFFFFFF) % tab.length;
        @SuppressWarnings("unchecked")
        Entry<K,V> entry = (Entry<K,V>)tab[index];
        for(; entry != null ; entry = entry.next) {
            if ((entry.hash == hash) && entry.key.equals(key)) {
                V old = entry.value;
                entry.value = value;
                return old;
            }
        }

        addEntry(hash, key, value, index);
        return null;
    }
}
  • 双重检查锁

在java中,使用synchronized的双重检查锁方式实现java属性线程安全初始化的场景。双重检查锁在Dubbo 的源码中,会多次见到。下面以Dubbo 中服务导出的doLocalExport 方法为例,源代码如下:

private <T> ExporterChangeableWrapper<T> doLocalExport(final Invoker<T> originInvoker) {
    String key = getCacheKey(originInvoker);

    ExporterChangeableWrapper<T> exporter = (ExporterChangeableWrapper<T>) bounds.get(key);
    // 第一次null判断
    if (exporter == null) {
        // 加上synchronized 锁
        synchronized (bounds) {
            exporter = (ExporterChangeableWrapper<T>) bounds.get(key);
            // 第二次null判断
            if (exporter == null) {
                final Invoker<?> invokerDelegete = new InvokerDelegete<T>(originInvoker, getProviderUrl(originInvoker));
                // 初始化exporter 
                exporter = new ExporterChangeableWrapper<T>((Exporter<T>) protocol.export(invokerDelegete), originInvoker);
                bounds.put(key, exporter);
            }
        }
    }
    return exporter;
}

 

发布了37 篇原创文章 · 获赞 2 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/new_com/article/details/104557370