转载

Java并发[三]

前言

讲解锁等高级主题

正文

一. 死锁及其避免

  1. 死锁出现的原因通常是出现锁环路造成的, 不管是显示的还是隐式的

  2. 通过定义获取锁的顺序来避免死锁: 通过 System.identityHashCode(object) 来返回一个锁对象的 Object.hashCode() 的返回值; 通过这个值来定义锁的顺序, 并在整个应用程序中都按照这个顺序来获取锁, 那么就不会形成锁环路了

Object fromAcct = new Object(); // 这里的锁对象也可以是其他
        Object toAcct = new Object();
        Object tieLock = new Object();
        int fromHash = System.identityHashCode(fromAcct);
        int toHash = System.identityHashCode(toAcct);
        if (fromHash < toHash) {
            synchronized (fromAcct) {
                synchronized (toAcct) {
                    // doSomething
                }
            }
        } else if (fromHash > toHash) {
            synchronized (toAcct) {
                synchronized (fromAcct) {
                    // doSomething
                }
            }
        } else { // 这种情况主要是避免Hash冲突, 此时需要额外的锁
            synchronized (tieLock) {
                synchronized (fromAcct) {
                    synchronized (toAcct) {
                        // doSomething
                    }
                }
            }
        }
  1. 使用开放调用(如果在调用某个方法时, 不需要持有锁, 那么这种调用就被成为开放调用)避免死锁: 这种情况下适用于持有不同锁的方法之间的相互调用(注意锁的级别是在方法上, 而不是代码块上)(相当于形成一个隐式的环); 将锁从方法上转移到需要同步的内部代码块上可以避免死锁

  2. Lock.tryLock() : 可以检测死锁和从死锁中恢复过来; 即指定一个超时时限, 在等待超过该时间后 tryLock() 会返回一个失败信息

  3. 需要注意的几点:

  1. 对象分配操作的开销比同步的开销更低(实际上, 现在 Java 的分配操作已经比 C 语言的 malloc 调用更快
  2. ConcurrentHashMap 的以前实现中(现在实现不同咯)采用分段锁, 使用了一个包含 16 个锁的数组, 每个锁保护散列桶的 1/16 , 这样就能够支持多达 16 个并发的写入器; ConcurrentHashMap 中的 size 将对每个分段进行枚举并将每个分段中的元素数量相加, 而不是维护一个全局计数, 为了避免枚举每个计数, ConcurrentHashMap 为每个分段都维护了一个独立的计数, 并通过每个分段的锁来维护这个值(这个地方有两点需要注意: 一个是 size 的计算是在每个 add , remove 操作都会改变一次, 这样虽然增加了 addremove 的操作, 但是这会使得 size 的计算从 O(n) 变为 O(1) ; 另一个是为每个分段维护一个 size , 而不是使用一个全局的共享 size , 如果使用一个全局的共享 size 的话, ,实际上就破坏了分段锁的分段特性, 因为访问共享 size 的时候, 需要同步)
  • ← Previous Post
原文  https://husteryp.github.io/2018/11/09/Java并发-三/
正文到此结束
Loading...