ConcurrentHashMap 从Java7 到 Java8的改变

一、关于分段锁

1.分段锁发展概况

集合框架很大程度减少了java程序员的重复劳动。在Java多线程环境中,以线程安全的方式使用集合类是一个首先考虑的问题。

能够保证线程安全的哈希表中,ConcurrentHashMap是大家都熟知的,也知道它内部使用了分段锁。然而,进入到Java8时代,分段锁成为了历史。

2.新版本ConcurrentHashMap

在Java8的ConcurrentHashMap中,分段锁仅用来处理对象流。

Java7中,Segment继承于ReentrantLock使用了显示锁,在Segment的实例方法中,每个更新操作内部又使用Unsafe来处理更新。这显然是一种浪费。显示锁、Unsafe 这二者都是可以保证对对象的原子操作。使用一个即可。

Java7中的数据存储在数组 final Segment<K,V>[] segments; 这个一个特定大小的Segment数组,

Segment继承于ReentrantLock 故而可以在更新操作时使用显示锁。

二、Java7的ConcurrentHashMap

java7中,ConcurrentHashMap的内部类Segment.

Segment继承了ReetrantLock,利用了显示锁,同时在更新操作中也使用了Unsafe.双管齐下来保证线程安全。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

static final class Segment<K,V> extends ReentrantLock implements Serializable {

        private static final long serialVersionUID = 2249069246763182397L;

        // tryLock()最多等待时间

        static final int MAX_SCAN_RETRIES =

            Runtime.getRuntime().availableProcessors() > 1 64 1;

        // 分段表,元素通过entryAt/setEntryAt这两个方法提供了瞬时操作。

        transient volatile HashEntry<K,V>[] table;

        // 元素数量,通过锁或可见性地瞬时读取

        transient int count;

        // 修改次数

        transient int modCount;

        // 表大小超出threshold时,重新哈希运算。它的值 = (int)(capacity*loadFactor)

        transient int threshold;

        // 负载因子

        final float loadFactor;

        final V put(K key, int hash, V value, boolean onlyIfAbsent) {

            HashEntry<K,V> node = tryLock() ? null :

                scanAndLockForPut(key, hash, value);

            V oldValue;

            try {

                HashEntry<K,V>[] tab = table;

                int index = (tab.length - 1) & hash;

                HashEntry<K,V> first = entryAt(tab, index);

                for (HashEntry<K,V> e = first;;) {

                    if (e != null) {

                        K k;

                        if ((k = e.key) == key ||

                            (e.hash == hash && key.equals(k))) {

                            oldValue = e.value;

                            if (!onlyIfAbsent) {

                                e.value = value;

                                ++modCount;

                            }

                            break;

                        }

                        e = e.next;

                    }

                    else {

                        if (node != null)

                            node.setNext(first);

                        else

                            node = new HashEntry<K,V>(hash, key, value, first);

                        int c = count + 1;

                        if (c > threshold && tab.length < MAXIMUM_CAPACITY)

                            rehash(node);

                        else

                            setEntryAt(tab, index, node);

                        ++modCount;

                        count = c;

                        oldValue = null;

                        break;

                    }

                }

            finally {

                unlock();

            }

            return oldValue;

        }

        /**

            移除:value为null时,只匹配key,否则,两个都匹配

         */

        final V remove(Object key, int hash, Object value) {

            if (!tryLock())

                scanAndLock(key, hash);

            V oldValue = null;

            try {

                HashEntry<K,V>[] tab = table;

                int index = (tab.length - 1) & hash;

                HashEntry<K,V> e = entryAt(tab, index);

                HashEntry<K,V> pred = null;

                while (e != null) {

                    K k;

                    HashEntry<K,V> next = e.next;

                    if ((k = e.key) == key ||

                        (e.hash == hash && key.equals(k))) {

                        V v = e.value;

                        if (value == null || value == v || value.equals(v)) {

                            if (pred == null)

                              //  这里是cas操作

                                setEntryAt(tab, index, next);

                            else

                                pred.setNext(next);

                            ++modCount;

                            --count;

                            oldValue = v;

                        }

                        break;

                    }

                    pred = e;

                    e = next;

                }

            finally {

                unlock();

            }

            return oldValue;

        }

        final void clear() {

            lock();

            try {

                HashEntry<K,V>[] tab = table;

                for (int i = 0; i < tab.length ; i++)

                // Unsafe 操作   

                setEntryAt(tab, i, null);

                ++modCount;

                count = 0;

            finally {

                unlock();

            }

        }

    }

三、Java8的ConcurrentHashMap

Java8中,ConcurrentHashMap较之前版本有了很大的改变。

使用Node数组替代了Segment数组来存储数据。Node数组中不再使用显示锁,而是Unsafe的乐观锁机制。

Segment予以保留,仅用来处理对象流的读写。

从如下Java8版本的ConcurrentHashMap$Segment源码来看,分段锁,基本弃用了。

1

2

3

4

5

static class Segment<K,V> extends ReentrantLock implements Serializable {

    private static final long serialVersionUID = 2249069246763182397L;

    final float loadFactor;

    Segment(float lf) { this.loadFactor = lf; }

}

  

猜你喜欢

转载自blog.csdn.net/artaganan8/article/details/88624797