zoukankan      html  css  js  c++  java
  • synchronized总结

    synchronized基础用法

    • synchronized可以用于修饰类的实例方法、静态方法和代码块。它保护的是对象(包括类对象)而非代码,只要访问的是同一个对象的synchronized方法,即使是不同的代码,也会被同步顺序访问。
    • 每个对象有一个锁(又叫监视器)和一个锁等待队列,锁只能被一个线程持有,其他试图获得同样锁的线程需要等待,执行synchronized实例方法的过程大概如下:
    1. 尝试获得锁,如果能够获得锁,继续下一步,否则加入锁等待队列,线程的状态变为BLOCKED,阻塞并等待唤醒
    2. 执行被锁住的方法或者代码块
    3. 释放锁,如果等待队列上有等待的线程,从中取一个并唤醒,如果有多个等待的线程,唤醒哪一个是不一定的,不保证公平性
    • 一般在保护变量时,需要在所有访问该变量的方法上加上synchronized。
    • 任何对象都可以作为synchronized锁的对象。

    理解synchronized

    可重入性

    可重入是指:对同一个执行线程,它在获得了锁之后,在调用其他需要同样锁的代码时,可以直接调用。

    可重入是通过记录锁的持有线程和持有数量来实现的,当调用被synchronized保护的代码时,检查对象是否已被锁,如果是,再检查是否被当前线程锁定,如果是,增加持有数量,如果不是被当前线程锁定,才加入等待队列,当释放锁时,减少持有数量,当数量变为0时才释放整个锁。

    内存可见性

    除了保证原子操作外,synchronized还有一个重要的作用,就是保证内存可见性,在释放锁时,所有写入都会写回内存,而获得锁后,都会从内存中读最新数据。

    如果只是简单地操作变量的话,可以用volatile修饰该变量,替代synchronized以减少成本。

    加了volatile之后,Java会在操作对应变量时插入一个cpu指令(又叫内存栅栏),保证读写到内存最新值,而非缓存的值。

    死锁

    死锁就是类似这种现象,比如, 有a, b两个线程,a持有锁A,在等待锁B,而b持有锁B,在等待锁A,a,b陷入了互相等待,最后谁都执行不下去。

    避免死锁的方案:

      1. 应该尽量避免在持有一个锁的同时去申请另一个锁,如果确实需要多个锁,所有代码都应该按照相同的顺序去申请锁。
      2. 使用显式锁接口Lock,它支持尝试获取锁(tryLock)和带时间限制的获取锁方法,使用这些方法可以在获取不到锁的时候释放已经持有的锁,然后再次尝试获取锁或干脆放弃,以避免死锁。

    死锁检查工具:Java自带的jstack命令

     

    同步容器及其注意事项

    同步容器

    Collections类有一些方法,它们可以返回线程安全的同步容器,比如:

    public static <T> Collection<T> synchronizedCollection(Collection<T> c)
    public static <T> List<T> synchronizedList(List<T> list)
    public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m)

    它们是给所有容器方法都加上synchronized来实现安全的。当多个线程并发访问同一个容器对象时,不需要额外的同步操作,也不会出现错误的结果。

     

    加了synchronized,所有方法调用变成了原子操作,但是也不是就绝对安全了,比如:

    复合操作,比如先检查再更新

    例如:

        public V putIfAbsent(K key, V value){
             V old = map.get(key);
             if(old!=null){
                 return old;
             }
             map.put(key, value);
             return null;
         }

    假设map的每个方法都是安全的,但这个复合方法putIfAbsent是安全的吗?显然是否定的,这是一个检查然后再更新的复合操作,在多线程的情况下,可能有多个线程都执行完了检查这一步,都发现Map中没有对应的键,然后就会都调用put,而这就破坏了putIfAbsent方法期望保持的语义。

    伪同步,比如同步错对象。

    那给该方法加上synchronized就能实现安全吗?如下所示:

    复制代码
    public synchronized V putIfAbsent(K key, V value){
        V old = map.get(key);
        if(old!=null){
            return old;
        }
        map.put(key, value);
        return null;
    }
    复制代码

    答案是否定的!为什么呢?同步错对象了。putIfAbsent同步锁住的的是当前类的对象,如果该类还存在其他操作map的实例方法的话,那么它操作map时同步锁住的是map,两者是不同的对象。随意要解决这个问题应该给map加锁,如:

    public V putIfAbsent(K key, V value){
        synchronized(map){
            V old = map.get(key);
             if(old!=null){
                 return old;
             }
             map.put(key, value);
             return null;    
        }
    }

    迭代

    对于同步容器对象,虽然单个操作是安全的,但迭代并不是。遍历的同时容器如果发生了结构性变化,就会抛出ConcurrentModificationException异常,同步容器并没有解决这个问题,如果要避免这个异常,需要在遍历的时候给整个容器对象加锁

    并发容器

    除了以上这些注意事项,同步容器的性能也是比较低的,当并发访问量比较大的时候性能很差。所幸的是,Java中还有很多专为并发设计的容器类,比如:

      • CopyOnWriteArrayList
      • ConcurrentHashMap
      • ConcurrentLinkedQueue
      • ConcurrentSkipListSet
  • 相关阅读:
    HDU 1874 畅通工程续(dijkstra)
    HDU 2112 HDU Today (map函数,dijkstra最短路径)
    HDU 2680 Choose the best route(dijkstra)
    HDU 2066 一个人的旅行(最短路径,dijkstra)
    关于测评机,编译器,我有些话想说
    测评机的优化问题 时间控制
    CF Round410 D. Mike and distribution
    数字三角形2 (取模)
    CF Round410 C. Mike and gcd problem
    CF Round 423 D. High Load 星图(最优最简构建)
  • 原文地址:https://www.cnblogs.com/JackPn/p/9426044.html
Copyright © 2011-2022 走看看