关于旅游网站开发的研究方法,成都网站建设益友网络,提供建设服务的网络公司,黄陂机械加工网文章目录 简介一、Hash表#x1f6a3;1、ConcurrentHashMap1.1 内部实现原理1.2 并发操作方法1.3 ConcurrentHashMap与Hashtable的比较 二、集合#x1f6a3;2、CopyOnWriteArrayList2.1 内部实现原理2.2 Copy-On-Write(COW)设计思想2.3 实操 三、Map#x1f6a3;3、Concurr… 文章目录 简介一、Hash表1、ConcurrentHashMap1.1 内部实现原理1.2 并发操作方法1.3 ConcurrentHashMap与Hashtable的比较 二、集合2、CopyOnWriteArrayList2.1 内部实现原理2.2 Copy-On-Write(COW)设计思想2.3 实操 三、Map3、ConcurrentSkipListMap3.1 跳表Skip List3.2 并发操作方法 四、队列4、ConcurrentLinkedQueue4.1 内部实现原理4.2 并发操作方法 5、BlockingQueue阻塞队列5.1 内部实现原理5.2 阻塞队列和非阻塞队列5.3 使用场景和示例 6、ConcurrentLinkedDeque双端队列6.1 内部实现原理6.2 并发操作方法 简介 线程安全数据类型通常提供了一些同步机制来保证数据的一致性。这些机制可以包括锁、互斥量、原子操作、无锁算法等。会在多个线程同时访问数据时进行同步操作以保证每个操作的原子性和正确性。
一、Hash表
1、ConcurrentHashMap 我们知道HashMap是非线程安全的在并发环境下容易导致数据错误。ConcurrentHashMap是Java1.5版本推出的线程安全容器它适用于以下场景点
高并发读写当多个线程需要同时读写哈希表时ConcurrentHashMap能够提供更好的并发性能。大规模数据当哈希表中的数据量较大时ConcurrentHashMap的分段锁机制能够减小锁的粒度提高并发更新的效率。
1.1 内部实现原理 ConcurrentHashMap的内部实现原理主要包括以下几点
分段锁机制JDK1.6ConcurrentHashMap将整个哈希表分成多个段Segment每个段维护着一个独立的锁。不同线程对不同段的操作可以并发进行从而提高了并发性能。CASJDK1.8在1.8的时候摒弃了segment臃肿的设计直接针对的是Node[] tale数组中的每一个桶进一步减小了锁粒度插入时使用CAS自旋锁的方式将值插入。数组链表/红黑树每个段内部使用数组链表或红黑树的数据结构来存储键值对。当链表长度超过阈值时链表会转换为红黑树以提高查找的效率。CAS操作ConcurrentHashMap使用CAS乐观锁Compare and Swap操作来实现并发更新避免了使用传统的锁机制带来的性能开销。
插入数据流程 1.做插入操作时首先进入乐观锁CAS 2.然后在乐观锁中判断容器是否初始化 如果没初始化则初始化容器 3.如果已经初始化则判断该hash位置的节点是否为空如果为空则通过CAS操作进行插入 4.如果该节点不为空再判断容器是否在扩容中如果在扩容则帮助其扩容。 5.如果没有扩容则进行最后一步先加锁然后找到hash值相同的那个节点(hash冲突) 6.循环判断这个节点上的链表决定做覆盖操作还是插入操作。 7.循环结束插入完毕。 1.2 并发操作方法 ConcurrentHashMap提供了一系列的并发操作方法常用的包括
put(key, value)向ConcurrentHashMap中插入键值对。get(key)根据键获取对应的值。remove(key)根据键移除对应的键值对。size()返回ConcurrentHashMap中键值对的数量。
import java.util.concurrent.ConcurrentHashMap;public class ConcurrentHashMapExample {public static void main(String[] args) {// 创建ConcurrentHashMap实例ConcurrentHashMapString, Integer map new ConcurrentHashMap();// 并发插入键值对map.put(key1, 1);map.put(key2, 2);map.put(key3, 3);// 并发读取键值对int value1 map.get(key1);int value2 map.get(key2);int value3 map.get(key3);// 并发移除键值对map.remove(key1);// 获取键值对数量int size map.size();System.out.println(value1: value1);System.out.println(value2: value2);System.out.println(value3: value3);System.out.println(size: size);}
}1.3 ConcurrentHashMap与Hashtable的比较 同ConcurrentHashMap一样的线程安全容器还有HashtableConcurrentHashMap从JDK 1.5开始引入而Hashtable则从JDK 1.0就已经存在。
特性ConcurrentHashMapHashtable线程安全性高并发环境下提供线程安全操作高并发环境下提供线程安全操作通过synchronized关键字实现锁粒度分段锁Segment整个哈希表的锁性能在高并发环境下具有更好的性能和可伸缩性在高并发环境下性能较差因为整个哈希表被单个锁保护可能导致竞争瓶颈迭代器弱一致性ConcurrentHashMap的迭代器提供弱一致性不一定能反映最新的修改Hashtable的迭代器是强一致性的反映最新的修改允许null键和null值允许不允许继承关系实现了ConcurrentMap接口继承自AbstractMap类继承自Dictionary类不推荐在新代码中使用 多线程环境下推荐使用ConcurrentHashMap而不是HashTable。 二、集合
2、CopyOnWriteArrayList ArrayList也不是线程安全的数据类型想要在并发环境下使用List类型的变量可以使用CopyOnWriteArrayList它适用于读多写少的环境支持并发读可以保证数据的最终一致性不保证实时性不保证每次读的数据都是最新的 特点
支持高并发读取CopyOnWriteArrayList允许多个线程同时读取数据读取操作不需要加锁因此可以实现高效的并发读取。写操作的代价较高当进行写操作时CopyOnWriteArrayList会创建一个新的数组并将原有数组的内容复制到新数组中然后进行写操作。因此写操作的代价较高适用于读操作远远多于写操作的场景。
2.1 内部实现原理 CopyOnWriteArrayList的内部实现原理主要包括以下几点
数组CopyOnWriteArrayList内部使用数组来存储元素。写时复制当进行写操作时CopyOnWriteArrayList会创建一个新的数组并将原有数组的内容复制到新数组中然后进行写操作。这种写时复制的机制保证了读操作的线程安全性。
2.2 Copy-On-Write(COW)设计思想 如果简单的使用读写锁的话在写锁被获取之后读写线程被阻塞只有当写锁被释放后读线程才有机会获取到锁从而读到最新的数据站在读线程的角度来看即读线程任何时候都是获取到最新的数据满足数据实时性但是读取速度会被限制。 COW牺牲数据实时性满足数据的最终一致性以获取更快的读取。 COW通俗的理解是当我们往一个容器添加元素的时候不直接往当前容器添加而是先将当前容器进行Copy复制出一个新的容器然后新的容器里添加元素添加完元素之后再将原容器的引用指向新的容器。 对CopyOnWrite容器进行并发的读的时候不需要加锁因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想延时更新的策略是通过在写的时候针对的是不同的数据容器来实现的放弃数据实时性达到数据的最终一致性。
2.3 实操 CopyOnWriteArrayList提供了一系列的并发操作方法常用的包括
add(element)向CopyOnWriteArrayList的末尾添加元素。get(index)根据索引获取对应的元素。remove(index)根据索引移除对应的元素。size()返回CopyOnWriteArrayList中元素的数量。
import java.util.concurrent.CopyOnWriteArrayList;public class CopyOnWriteArrayListExample {public static void main(String[] args) {// 创建CopyOnWriteArrayList实例CopyOnWriteArrayListString list new CopyOnWriteArrayList();// 并发添加元素list.add(element1);list.add(element2);list.add(element3);// 并发读取元素String element1 list.get(0);String element2 list.get(1);String element3 list.get(2);// 并发移除元素list.remove(0);// 获取元素数量int size list.size();System.out.println(element1: element1);System.out.println(element2: element2);System.out.println(element3: element3);System.out.println(size: size);}
}三、Map
3、ConcurrentSkipListMap ConcurrentSkipListMap是Java中的线程安全的有序映射表实现它具有以下特点 有序映射、范围查询ConcurrentSkipListMap维护了一个有序的键值对映射关系根据键的顺序它可以提供范围查询、按键排序等功能。 并发安全多个线程可以同时对其进行读取和写入操作而不需要额外的同步措施。它使用了一些并发控制机制如CASCompare and Swap操作和锁分段技术来保证并发访问的正确性和一致性。 高效性能它内部使用了跳表数据结构通过层级索引的方式提供了快速的查找和插入操作。同时它采用了锁分段技术不同的线程可以并发地访问不同的段减少了锁的竞争提高了并发性能。 不允许空键ConcurrentSkipListMap不允许插入空键null key因为它使用键的顺序来维护有序性。如果需要使用空键可以考虑使用ConcurrentHashMap。
3.1 跳表Skip List ConcurrentSkipListMap的内部实现原理主要依赖于跳表Skip List数据结构来实现高效的并发操作和有序性。 跳表Skip List是一种基于链表的数据结构通过在原始链表上建立多级索引以提高查找效率特别适用于需要频繁的插入和查找操作的有序集合。它的实现相对简单并且在实际应用中具有广泛的应用如数据库索引、缓存实现等。 结构组成跳表由多个层级组成每个层级都是一个有序的链表。最底层是原始链表每个元素按照顺序连接。上面的每个层级都是原始链表的子集其中的元素通过指针连接到下一层级的元素。 索引层级跳表的每个层级都是原始链表的一个子集。顶层包含最少的元素而底层包含所有的元素。每个元素在每个层级中都有一个指针指向下一个层级中与其相邻的元素。 跳跃操作跳表的名称来源于它的跳跃操作。通过索引层级跳表可以在查找时跳过一些元素从而快速定位目标元素。这种跳跃操作类似于二分查找但可以在更高的层级上进行跳跃。 插入和删除操作插入和删除操作在跳表中相对容易。在插入元素时需要在每个层级中找到正确的位置并更新相应的指针。删除操作类似需要更新相应的指针。这些操作的时间复杂度通常为O(log n)其中n是元素的数量。 查找操作跳表的查找操作非常高效。通过跳跃操作可以在O(log n)的时间复杂度内找到目标元素。跳表的查找效率与平衡二叉搜索树相当但实现起来相对简单。 空间复杂度跳表的空间复杂度为O(n)其中n是元素的数量。这是因为跳表需要额外的索引层级来提高查找效率。
3.2 并发操作方法 ConcurrentSkipListMap提供了一系列的并发操作方法常用的包括
put(key, value)向映射表中添加键值对。get(key)根据键获取对应的值。remove(key)根据键移除对应的键值对。size()返回映射表中键值对的数量。
import java.util.concurrent.ConcurrentSkipListMap;public class ConcurrentSkipListMapExample {public static void main(String[] args) {// 创建ConcurrentSkipListMap实例ConcurrentSkipListMapInteger, String map new ConcurrentSkipListMap();// 并发添加键值对map.put(3, value3);map.put(1, value1);map.put(2, value2);// 并发获取值String value1 map.get(1);String value2 map.get(2);// 并发移除键值对map.remove(3);// 获取映射表大小int size map.size();System.out.println(value1: value1);System.out.println(value2: value2);System.out.println(size: size);}
}四、队列
4、ConcurrentLinkedQueue ConcurrentLinkedQueue是基于链表实现的数据安全队列。
支持高并发操作ConcurrentLinkedQueue使用了无锁的算法可以实现高效的并发操作。无界队列ConcurrentLinkedQueue没有容量限制可以根据需要动态地添加元素。
4.1 内部实现原理 ConcurrentLinkedQueue的内部实现原理主要包括以下几点
链表ConcurrentLinkedQueue内部使用链表来存储元素。CAS操作ConcurrentLinkedQueue使用CASCompare and Swap操作来实现并发操作避免了使用传统的锁机制带来的性能开销。
链表结点结构
private static class NodeE {volatile E item;volatile NodeE next;.......
}Node节点主要包含了两个域一个是数据域item另一个是next指针用于指向下一个节点从而构成链式队列。并且都是用volatile进行修饰的以保证内存可见性。另外ConcurrentLinkedQueue含有这样两个成员变量说明ConcurrentLinkedQueue通过持有头尾指针进行管理队列。
private transient volatile NodeE head;
private transient volatile NodeE tail;CAS操作 ConcurrentLinkedQueue对Node的CAS操作有这样几个
//更改Node中的数据域item
boolean casItem(E cmp, E val) {return UNSAFE.compareAndSwapObject(this, itemOffset, cmp, val);
}
//更改Node中的指针域next
void lazySetNext(NodeE val) {UNSAFE.putOrderedObject(this, nextOffset, val);
}
//更改Node中的指针域next
boolean casNext(NodeE cmp, NodeE val) {return UNSAFE.compareAndSwapObject(this, nextOffset, cmp, val);
}4.2 并发操作方法 ConcurrentLinkedQueue提供了一系列的并发操作方法常用的包括
offer(element)向队列尾部添加元素。poll()从队列头部获取并移除元素。peek()获取队列头部的元素但不移除。size()返回队列中元素的数量。
import java.util.concurrent.ConcurrentLinkedQueue;public class ConcurrentLinkedQueueExample {public static void main(String[] args) {// 创建ConcurrentLinkedQueue实例ConcurrentLinkedQueueString queue new ConcurrentLinkedQueue();// 并发添加元素queue.offer(element1);queue.offer(element2);queue.offer(element3);// 并发获取并移除元素String element1 queue.poll();String element2 queue.poll();// 并发获取队列头部元素String head queue.peek();// 获取队列大小int size queue.size();System.out.println(element1: element1);System.out.println(element2: element2);System.out.println(head: head);System.out.println(size: size);}
}5、BlockingQueue阻塞队列 BlockingQueue是Java中的线程安全的阻塞队列实现它具有以下特点
支持并发操作BlockingQueue可以在多个线程之间安全地传递数据。阻塞操作当队列为空时获取元素的操作会被阻塞当队列已满时添加元素的操作会被阻塞。先进先出全自动不用管什么时候阻塞 5.1 内部实现原理 BlockingQueue的内部实现原理主要依赖于同步器如ReentrantLock、Condition、Semaphore等来实现阻塞操作的控制。
5.2 阻塞队列和非阻塞队列 阻塞队列适用于需要线程之间同步和协作的场景而非阻塞队列适用于对并发性能要求较高的场景。 阻塞队列Blocking Queue是一种线程安全的队列当队列为空时从队列中获取元素的操作会被阻塞直到队列中有可用元素当队列已满时向队列中添加元素的操作会被阻塞直到队列有空闲位置。阻塞队列提供了一种简单而有效的方式来实现线程之间的同步和协作。 常见的阻塞队列实现包括ArrayBlockingQueue和LinkedBlockingQueue。ArrayBlockingQueue使用数组实现具有固定的容量LinkedBlockingQueue使用链表实现可以选择是否有容量限制。 阻塞队列的特点
线程安全阻塞队列是线程安全的多个线程可以同时进行入队和出队操作而不需要额外的同步措施。阻塞操作当队列为空时获取元素的操作会被阻塞直到队列中有可用元素当队列已满时添加元素的操作会被阻塞直到队列有空闲位置。同步和协作阻塞队列提供了一种简单的机制来进行线程之间的同步和协作。线程可以在队列上等待或者唤醒其他线程以实现线程之间的协调。 非阻塞队列Non-blocking Queue是一种不会阻塞线程的队列实现当队列满时添加元素的操作会立即返回失败当队列为空时获取元素的操作会立即返回空值。非阻塞队列通常使用原子操作和无锁算法来实现。 常见的非阻塞队列实现包括ConcurrentLinkedQueue和LinkedTransferQueue。ConcurrentLinkedQueue使用链表实现适用于高并发场景LinkedTransferQueue是LinkedBlockingQueue的扩展提供了更高级的操作和功能。 非阻塞队列的特点
非阻塞操作非阻塞队列的操作不会阻塞线程当队列满时添加元素的操作会立即返回失败当队列为空时获取元素的操作会立即返回空值。并发性能非阻塞队列通常使用原子操作和无锁算法实现可以在高并发环境下提供较好的性能。无等待性一些非阻塞队列实现提供无等待wait-free的保证即任意时刻都有至少一个线程可以继续进行操作而不会被其他线程阻塞。
5.3 使用场景和示例 BlockingQueue提供了一系列的并发操作方法常用的包括
put(element)向队列尾部添加元素如果队列已满则阻塞等待。take()从队列头部获取并移除元素如果队列为空则阻塞等待。offer(element, timeout, unit)向队列尾部添加元素如果队列已满则阻塞一段时间超时后返回false。poll(timeout, unit)从队列头部获取并移除元素如果队列为空则阻塞一段时间超时后返回null。 import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;public class BlockingQueueExample {public static void main(String[] args) {// 创建BlockingQueue实例BlockingQueueString queue new ArrayBlockingQueue(3);// 生产者线程Thread producerThread new Thread(() - {try {// 向队列中添加元素queue.put(element1);queue.put(element2);queue.put(element3);System.out.println(Producer: elements added to the queue.);} catch (InterruptedException e) {e.printStackTrace();}});// 消费者线程Thread consumerThread new Thread(() - {try {// 从队列中获取元素String element1 queue.take();String element2 queue.take();String element3 queue.take();System.out.println(Consumer: elements taken from the queue.);} catch (InterruptedException e) {e.printStackTrace();}});// 启动生产者和消费者线程producerThread.start();consumerThread.start();}
}6、ConcurrentLinkedDeque双端队列 ConcurrentLinkedDeque是Java中的线程安全的双端队列实现它具有以下特点
支持高并发操作ConcurrentLinkedDeque使用了无锁的算法可以实现高效的并发操作。双端队列ConcurrentLinkedDeque可以在队列的两端进行元素的插入和删除操作。
6.1 内部实现原理 ConcurrentLinkedDeque的内部实现原理主要依赖于链表数据结构来实现高效的并发操作。
6.2 并发操作方法 ConcurrentLinkedDeque提供了一系列的并发操作方法常用的包括
addFirst(element)在队列的头部添加元素。addLast(element)在队列的尾部添加元素。removeFirst()移除并返回队列头部的元素。removeLast()移除并返回队列尾部的元素。peekFirst()返回队列头部的元素但不移除。peekLast()返回队列尾部的元素但不移除。size()返回队列中元素的数量。
import java.util.concurrent.ConcurrentLinkedDeque;public class ConcurrentLinkedDequeExample {public static void main(String[] args) {// 创建ConcurrentLinkedDeque实例ConcurrentLinkedDequeInteger deque new ConcurrentLinkedDeque();// 并发添加元素deque.addFirst(3);deque.addLast(1);deque.addLast(2);// 并发移除元素int first deque.removeFirst();int last deque.removeLast();// 获取队列头部和尾部的元素int peekFirst deque.peekFirst();int peekLast deque.peekLast();// 获取队列大小int size deque.size();System.out.println(First: first);System.out.println(Last: last);System.out.println(Peek First: peekFirst);System.out.println(Peek Last: peekLast);System.out.println(Size: size);}
}