|
1 | 1 | # 并发容器 |
2 | 2 |
|
| 3 | +## CopyOnWriteArrayList |
| 4 | + |
| 5 | +### 1. 读写分离 |
| 6 | + |
| 7 | +**写操作在一个复制的数组**上进行,**读操作还是在原数组**中进行,**读写分离**,互不影响。 |
| 8 | + |
| 9 | +写操作需要加锁,防止并发写入时导致写入数据丢失。 |
| 10 | + |
| 11 | +写操作结束之后需要把原数组指向新的复制数组。 |
| 12 | + |
| 13 | +```java |
| 14 | +//写操作: |
| 15 | +//通过过创建底层数组的新副本来实现的。 |
| 16 | +//当 List 需要被修改的时候,并不修改原有内容,而是对原有数据进行一次复制,将修改的内容写入副本。 |
| 17 | +//写完之后,把原数组指向新的复制数组。 |
| 18 | +//这样可以保证写操作实在一个复制的数组上进行,而读操作还是在原数组中进行,不会影响读操作。 |
| 19 | +public boolean add(E e) { |
| 20 | + //加锁 |
| 21 | + final ReentrantLock lock = this.lock; |
| 22 | + lock.lock(); |
| 23 | + try { |
| 24 | + Object[] elements = getArray(); |
| 25 | + int len = elements.length; |
| 26 | + // newElements 是一个复制的数组 |
| 27 | + Object[] newElements = Arrays.copyOf(elements, len + 1); |
| 28 | + newElements[len] = e; |
| 29 | + // 写操作在一个复制的数组上进行 |
| 30 | + setArray(newElements); |
| 31 | + return true; |
| 32 | + } finally { |
| 33 | + lock.unlock(); |
| 34 | + } |
| 35 | +} |
| 36 | + |
| 37 | +final void setArray(Object[] a) { |
| 38 | + array = a; |
| 39 | +} |
| 40 | +``` |
| 41 | + |
| 42 | +```java |
| 43 | +//读操作 |
| 44 | +//读操作没有任何同步控制和锁操作, |
| 45 | +//因为内部数组 array 不会被修改。 |
| 46 | +private transient volatile Object[] array; |
| 47 | + |
| 48 | +public E get(int index) { |
| 49 | + return get(getArray(), index); |
| 50 | +} |
| 51 | + |
| 52 | +@SuppressWarnings("unchecked") |
| 53 | +private E get(Object[] a, int index) { |
| 54 | + return (E) a[index]; |
| 55 | +} |
| 56 | + |
| 57 | +final Object[] getArray() { |
| 58 | + return array; |
| 59 | +} |
| 60 | +``` |
| 61 | + |
| 62 | +### 2. 适用场景 |
| 63 | + |
| 64 | +CopyOnWriteArrayList 在写操作的同时允许读操作,大大提高了读操作的性能,很适合**读多写少**的应用场景。 |
| 65 | + |
| 66 | +CopyOnWriteArrayList 有其缺陷: |
| 67 | + |
| 68 | +- 内存占用:在写操作时需要复制一个新的数组,使得内存占用为原来的两倍左右; |
| 69 | +- 数据不一致:读操作不能读取实时性的数据,因为部分写操作的数据还未同步到读数组中。 |
| 70 | + |
| 71 | +所以 CopyOnWriteArrayList 不适合**内存敏感**以及对**实时性要求很高**的场景。 |
| 72 | + |
3 | 73 | ## ConcurrentHashMap |
4 | 74 |
|
5 | 75 | ### 1. 存储结构 |
@@ -162,72 +232,22 @@ static final <K,V> Node<K,V> tabAt(Node<K,V>[] tab, int i) { |
162 | 232 |
|
163 | 233 | (5)其余情况把新的 Node 节点按链表或红黑树的方式插入到合适位置,这个过程采用内置锁实现并发。 |
164 | 234 |
|
165 | | -## CopyOnWriteArrayList |
166 | | - |
167 | | -### 1. 读写分离 |
168 | | - |
169 | | -**写操作在一个复制的数组**上进行,**读操作还是在原数组**中进行,**读写分离**,互不影响。 |
170 | | - |
171 | | -写操作需要加锁,防止并发写入时导致写入数据丢失。 |
172 | | - |
173 | | -写操作结束之后需要把原数组指向新的复制数组。 |
174 | | - |
175 | | -```java |
176 | | -//写操作: |
177 | | -//通过过创建底层数组的新副本来实现的。 |
178 | | -//当 List 需要被修改的时候,并不修改原有内容,而是对原有数据进行一次复制,将修改的内容写入副本。 |
179 | | -//写完之后,把原数组指向新的复制数组。 |
180 | | -//这样可以保证写操作实在一个复制的数组上进行,而读操作还是在原数组中进行,不会影响读操作。 |
181 | | -public boolean add(E e) { |
182 | | - //加锁 |
183 | | - final ReentrantLock lock = this.lock; |
184 | | - lock.lock(); |
185 | | - try { |
186 | | - Object[] elements = getArray(); |
187 | | - int len = elements.length; |
188 | | - // newElements 是一个复制的数组 |
189 | | - Object[] newElements = Arrays.copyOf(elements, len + 1); |
190 | | - newElements[len] = e; |
191 | | - // 写操作在一个复制的数组上进行 |
192 | | - setArray(newElements); |
193 | | - return true; |
194 | | - } finally { |
195 | | - lock.unlock(); |
196 | | - } |
197 | | -} |
198 | | - |
199 | | -final void setArray(Object[] a) { |
200 | | - array = a; |
201 | | -} |
202 | | -``` |
203 | | - |
204 | | -```java |
205 | | -//读操作 |
206 | | -//读操作没有任何同步控制和锁操作, |
207 | | -//因为内部数组 array 不会被修改。 |
208 | | -private transient volatile Object[] array; |
| 235 | +### 5. 和 Hashtable 的区别 |
209 | 236 |
|
210 | | -public E get(int index) { |
211 | | - return get(getArray(), index); |
212 | | -} |
| 237 | +**底层数据结构:** |
213 | 238 |
|
214 | | -@SuppressWarnings("unchecked") |
215 | | -private E get(Object[] a, int index) { |
216 | | - return (E) a[index]; |
217 | | -} |
| 239 | +- JDK1.7 的ConcurrentHashMap底层采用**分段的数组+链表**实现, JDK1.8 的ConcurrentHashMap底层采用的数据结构与JDK1.8 的HashMap的结构一样,**数组+链表/红黑二叉树**。 |
| 240 | +- Hashtable和JDK1.8 之前的HashMap的底层数据结构类似都是采用**数组+链表**的形式, 数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的。 |
218 | 241 |
|
219 | | -final Object[] getArray() { |
220 | | - return array; |
221 | | -} |
222 | | -``` |
| 242 | +**实现线程安全的方式** |
223 | 243 |
|
224 | | -### 2. 适用场景 |
| 244 | +- JDK1.7的ConcurrentHashMap(分段锁)对整个桶数组进行了分割分段(Segment), 每一把锁只锁容器其中一部分数据,多线程访问容器里不同数据段的数据,就不会存在锁竞争,提高并发访问度。 JDK 1.8 采用**数组+链表/红黑二叉树**的数据结构来实现,并发控制使用**synchronized和CAS**来操作。 |
| 245 | +- Hashtable:使用 synchronized 来保证线程安全,效率非常低下。 当一个线程访问同步方法时,其他线程也访问同步方法,可能会进入阻塞或轮询状态, 如使用 put 添加元素,另一个线程不能使用 put 添加元素,也不能使用 get,竞争会越来越激烈。 |
225 | 246 |
|
226 | | -CopyOnWriteArrayList 在写操作的同时允许读操作,大大提高了读操作的性能,很适合**读多写少**的应用场景。 |
| 247 | +Hashtable 全表锁 |
227 | 248 |
|
228 | | -CopyOnWriteArrayList 有其缺陷: |
| 249 | + |
229 | 250 |
|
230 | | -- 内存占用:在写操作时需要复制一个新的数组,使得内存占用为原来的两倍左右; |
231 | | -- 数据不一致:读操作不能读取实时性的数据,因为部分写操作的数据还未同步到读数组中。 |
| 251 | +ConcurrentHashMap 分段锁 |
232 | 252 |
|
233 | | -所以 CopyOnWriteArrayList 不适合**内存敏感**以及对**实时性要求很高**的场景。 |
| 253 | + |
0 commit comments