「java线程安全链表」并发安全的链表
本篇文章给大家谈谈java线程安全链表,以及并发安全的链表对应的知识点,希望对各位有所帮助,不要忘了收藏本站喔。
本文目录一览:
- 1、java对链表加锁并锁住其中的所有元素,
- 2、我想用JAVA实现一个链表的插入~删除等操作~ (要再界面上操作~~~ 就是我按下一个键能进行插入删除的操作的
- 3、java8 中concurrenthashmap数据结构和HashMap一样,且线程安全 为什么还要HashMap
- 4、ConcurrentHashMap如何实现高效地线程安全?
- 5、java集合 有序无序,线程是否安全
java对链表加锁并锁住其中的所有元素,
办法倒是有
自己写一个类, 包含一个list属性, 写几个add,get delete等方法,这些方法就是去操作list里面的元素.同时这些方法是加锁,线程安全的
当然了,链表容器本来就有线程安全的,vector等,可以直接用
我想用JAVA实现一个链表的插入~删除等操作~ (要再界面上操作~~~ 就是我按下一个键能进行插入删除的操作的
编辑词条JAVA容器
解释一:
容器(Container)
Spring 提供容器功能,容器可以管理对象的生命周期、对象与对象之间的依赖关系,您可以使用一个配置文件(通常是XML),在上面定义好对象的名称、如何产生(Prototype 方式或Singleton 方式)、哪个对象产生之后必须设定成为某个对象的属性等,在启动容器之后,所有的对象都可以直接取用,不用编写任何一行程序代码来产生对象,或是建立对象与对象之间的依赖关系。
换个更直白点的说明方式:容器是一个Java 所编写的程序,原先必须自行编写程序以管理对象关系,现在容器都会自动帮您作好。
常用容器:WebSphere,WebLogic,Resin,Tomcat
----------------------------------
解释二:
容器类
Java容器类包含List、ArrayList、Vector及map、HashTable、HashMap
ArrayList和HashMap是异步的,Vector和HashTable是同步的,所以Vector和HashTable是线程安全的,而 ArrayList和HashMap并不是线程安全的。因为同步需要花费机器时间,所以Vector和HashTable的执行效率要低于 ArrayList和HashMap。
Collection
├List 接口
│├LinkedList 链表
│├ArrayList 顺序结构动态数组类
│└Vector 向量
│ └Stack 栈
└Set
Map
├Hashtable
├HashMap
└WeakHashMap List接口
List是有序的Collection,使用此接口能够精确的控制每个元素插入的位置。用户能够使用索引(元素在List中的位置,类似于数组下标)来访问List中的元素,这类似于Java的数组。
和下面要提到的Set不同,List允许有相同的元素。
除了具有Collection接口必备的iterator()方法外,List还提供一个listIterator()方法,返回一个 ListIterator接口,和标准的Iterator接口相比,ListIterator多了一些add()之类的方法,允许添加,删除,设定元素, 还能向前或向后遍历。
实现List接口的常用类有LinkedList,ArrayList,Vector和Stack。
ArrayList类
ArrayList实现了可变大小的数组。它允许所有元素,包括null。ArrayList没有同步。
size,isEmpty,get,set方法运行时间为常数。但是add方法开销为分摊的常数,添加n个元素需要O(n)的时间。其他的方法运行时间为线性。
每个ArrayList实例都有一个容量(Capacity),即用于存储元素的数组的大小。这个容量可随着不断添加新元素而自动增加,但是增长算法 并没有定义。当需要插入大量元素时,在插入前可以调用ensureCapacity方法来增加ArrayList的容量以提高插入效率。
和LinkedList一样,ArrayList也是非同步的(unsynchronized)。
Map接口
请注意,Map没有继承Collection接口,Map提供key到value的映射。一个Map中不能包含相同的key,每个key只能映射一个 value。Map接口提供3种集合的视图,Map的内容可以被当作一组key集合,一组value集合,或者一组key-value映射。
HashMap类
HashMap和Hashtable类似,不同之处在于HashMap是非同步的,并且允许null,即null value和null key。,但是将HashMap视为Collection时(values()方法可返回Collection),其迭代子操作时间开销和HashMap 的容量成比例。因此,如果迭代操作的性能相当重要的话,不要将HashMap的初始化容量设得过高,或者load factor过低。
Collection接口
Collection是最基本的集合接口,一个Collection代表一组Object,即Collection的元素(Elements)。一些Collection允许相同的元素而另一些不行。一些能排序而另一些不行。Java SDK不提供直接继承自Collection的类,Java SDK提供的类都是继承自Collection的“子接口”如List和Set。
所有实现Collection接口的类都必须提供两个标准的构造函数:无参数的构造函数用于创建一个空的Collection,有一个Collection参数的构造函数用于创建一个新的Collection,这个新的Collection与传入的Collection有相同的元素。后一个构造函数允许用户复制一个Collection。
如何遍历Collection中的每一个元素?不论Collection的实际类型如何,它都支持一个iterator()的方法,该方法返回一个迭代子,使用该迭代子即可逐一访问Collection中每一个元素。典型的用法如下:
Iterator it = collection.iterator(); // 获得一个迭代子
while(it.hasNext()) {
Object obj = it.next(); // 得到下一个元素
}
由Collection接口派生的两个接口是List和Set。
Hashtable类
Hashtable继承Map接口,实现一个key-value映射的哈希表。任何非空(non-null)的对象都可作为key或者value。
添加数据使用put(key, value),取出数据使用get(key),这两个基本操作的时间开销为常数。
Hashtable通过initial capacity和load factor两个参数调整性能。通常缺省的load factor 0.75较好地实现了时间和空间的均衡。增大load factor可以节省空间但相应的查找时间将增大,这会影响像get和put这样的操作。
使用Hashtable的简单示例如下,将1,2,3放到Hashtable中,他们的key分别是”one”,”two”,”three”:
Hashtable numbers = new Hashtable();
numbers.put(“one”, new Integer(1));
numbers.put(“two”, new Integer(2));
numbers.put(“three”, new Integer(3));
要取出一个数,比如2,用相应的key:
Integer n = (Integer)numbers.get(“two”);
System.out.println(“two = ” + n);
由于作为key的对象将通过计算其散列函数来确定与之对应的value的位置,因此任何作为key的对象都必须实现hashCode和equals方法。hashCode和equals方法继承自根类Object,如果你用自定义的类当作key的话,要相当小心,按照散列函数的定义,如果两个对象相同,即obj1.equals(obj2)=true,则它们的hashCode必须相同,但如果两个对象不同,则它们的hashCode不一定不同,如果两个不同对象的hashCode相同,这种现象称为冲突,冲突会导致操作哈希表的时间开销增大,所以尽量定义好的hashCode()方法,能加快哈希表的操作。
如果相同的对象有不同的hashCode,对哈希表的操作会出现意想不到的结果(期待的get方法返回null),要避免这种问题,只需要牢记一条:要同时复写equals方法和hashCode方法,而不要只写其中一个。
Hashtable是同步的。
HashMap类
HashMap和Hashtable类似,不同之处在于HashMap是非同步的,并且允许null,即null value和null key。,但是将HashMap视为Collection时(values()方法可返回Collection),其迭代子操作时间开销和HashMap的容量成比例。因此,如果迭代操作的性能相当重要的话,不要将HashMap的初始化容量设得过高,或者load factor过低。
WeakHashMap类
WeakHashMap是一种改进的HashMap,它对key实行“弱引用”,如果一个key不再被外部所引用,那么该key可以被GC回收。
总结
如果涉及到堆栈,队列等操作,应该考虑用List,对于需要快速插入,删除元素,应该使用LinkedList,如果需要快速随机访问元素,应该使用ArrayList。
如果程序在单线程环境中,或者访问仅仅在一个线程中进行,考虑非同步的类,其效率较高,如果多个线程可能同时操作一个类,应该使用同步的类。
要特别注意对哈希表的操作,作为key的对象要正确复写equals和hashCode方法。
尽量返回接口而非实际的类型,如返回List而非ArrayList,这样如果以后需要将ArrayList换成LinkedList时,客户端代码不用改变。这就是针对抽象编程。
同步性
Vector是同步的。这个类中的一些方法保证了Vector中的对象是线程安全的。而ArrayList则是异步的,因此ArrayList中的对象并不是线程安全的。因为同步的要求会影响执行的效率,所以如果你不需要线程安全的集合那么使用ArrayList是一个很好的选择,这样可以避免由于同步带来的不必要的性能开销。
数据增长
从内部实现机制来讲ArrayList和Vector都是使用数组(Array)来控制集合中的对象。当你向这两种类型中增加元素的时候,如果元素的数目超出了内部数组目前的长度它们都需要扩展内部数组的长度,Vector缺省情况下自动增长原来一倍的数组长度,ArrayList是原来的50%,所以最后你获得的这个集合所占的空间总是比你实际需要的要大。所以如果你要在集合中保存大量的数据那么使用Vector有一些优势,因为你可以通过设置集合的初始化大小来避免不必要的资源开销。
使用模式
在ArrayList和Vector中,从一个指定的位置(通过索引)查找数据或是在集合的末尾增加、移除一个元素所花费的时间是一样的,这个时间我们用O(1)表示。但是,如果在集合的其他位置增加或移除元素那么花费的时间会呈线形增长:O(n-i),其中n代表集合中元素的个数,i代表元素增加或移除元素的索引位置。为什么会这样呢?以为在进行上述操作的时候集合中第i和第i个元素之后的所有元素都要执行位移的操作。这一切意味着什么呢?
这意味着,你只是查找特定位置的元素或只在集合的末端增加、移除元素,那么使用Vector或ArrayList都可以。如果是其他操作,你最好选择其他的集合操作类。比如,LinkList集合类在增加或移除集合中任何位置的元素所花费的时间都是一样的?O(1),但它在索引一个元素的使用缺比较慢-O(i),其中i是索引的位置.使用ArrayList也很容易,因为你可以简单的使用索引来代替创建iterator对象的操作。LinkList也会为每个插入的元素创建对象,所有你要明白它也会带来额外的开销。
最后,在《Practical Java》一书中Peter Haggar建议使用一个简单的数组(Array)来代替Vector或ArrayList。尤其是对于执行效率要求高的程序更应如此。因为使用数组(Array)避免了同步、额外的方法调用和不必要的重新分配空间的操作。
理解集合类
集合类存放于java.util包中。
集合类存放的都是对象的引用,而非对象本身,出于表达上的便利,我们称集合中的对象就是指集合中对象的引用(reference)。
集合类型主要有3种:set(集)、list(列表)和map(映射)。
(1)集
集(set)是最简单的一种集合,它的对象不按特定方式排序,只是简单的把对象加入集合中,就像往口袋里放东西。
对集中成员的访问和操作是通过集中对象的引用进行的,所以集中不能有重复对象。
集也有多种变体,可以实现排序等功能,如TreeSet,它把对象添加到集中的操作将变为按照某种比较规则将其插入到有序的对象序列中。它实现的是SortedSet接口,也就是加入了对象比较的方法。通过对集中的对象迭代,我们可以得到一个升序的对象集合。
(2)列表
列表的主要特征是其对象以线性方式存储,没有特定顺序,只有一个开头和一个结尾,当然,它与根本没有顺序的集是不同的。
列表在数据结构中分别表现为:数组和向量、链表、堆栈、队列。
关于实现列表的集合类,是我们日常工作中经常用到的,将在后边的笔记详细介绍。
(3)映射
映射与集或列表有明显区别,映射中每个项都是成对的。映射中存储的每个对象都有一个相关的关键字(Key)对象,关键字决定了对象在映射中的存储位置,检索对象时必须提供相应的关键字,就像在字典中查单词一样。关键字应该是唯一的。
关键字本身并不能决定对象的存储位置,它需要对过一种散列(hashing)技术来处理,产生一个被称作散列码(hash code)的整数值,散列码通常用作一个偏置量,该偏置量是相对于分配给映射的内存区域起始位置的,由此确定关键字/对象对的存储位置。理想情况下,散列处理应该产生给定范围内均匀分布的值,而且每个关键字应得到不同的散列码。
集合类简介
java.util中共有13个类可用于管理集合对象,它们支持集、列表或映射等集合,以下是这些类的简单介绍
集:
HashSet: 使用HashMap的一个集的实现。虽然集定义成无序,但必须存在某种方法能相当高效地找到一个对象。使用一个HashMap对象实现集的存储和检索操作是在固定时间内实现的.
TreeSet: 在集中以升序对对象排序的集的实现。这意味着从一个TreeSet对象获得第一个迭代器将按升序提供对象。TreeSet类使用了一个TreeMap.
列表:
Vector: 实现一个类似数组一样的表,自动增加容量来容纳你所需的元素。使用下标存储和检索对象就象在一个标准的数组中一样。你也可以用一个迭代器从一个Vector中检索对象。Vector是唯一的同步容器类??当两个或多个线程同时访问时也是性能良好的。
Stack: 这个类从Vector派生而来,并且增加了方法实现栈??一种后进先出的存储结构。
LinkedList: 实现一个链表。由这个类定义的链表也可以像栈或队列一样被使用。
ArrayList: 实现一个数组,它的规模可变并且能像链表一样被访问。它提供的功能类似Vector类但不同步。
映射:
HashTable: 实现一个映象,所有的键必须非空。为了能高效的工作,定义键的类必须实现hashcode()方法和equal()方法。这个类是前面java实现的一个继承,并且通常能在实现映象的其他类中更好的使用。
HashMap: 实现一个映象,允许存储空对象,而且允许键是空(由于键必须是唯一的,当然只能有一个)。
WeakHashMap: 实现这样一个映象:通常如果一个键对一个对象而言不再被引用,键/对象对将被舍弃。这与HashMap形成对照,映象中的键维持键/对象对的生命周期,尽管使用映象的程序不再有对键的引用,并且因此不能检索对象。
TreeMap: 实现这样一个映象,对象是按键升序排列的。
Set和List都是由公共接口Collection扩展而来,所以它们都可以使用一个类型为Collection的变量来引用。这就意味着任何列表或集构成的集合都可以用这种方式引用,只有映射类除外(但也不是完全排除在外,因为可以从映射获得一个列表。)所以说,把一个列表或集传递给方法的标准途径是使用Collection类型的参数。
另外,站长团上有产品团购,便宜有保证
java8 中concurrenthashmap数据结构和HashMap一样,且线程安全 为什么还要HashMap
java阻塞队列应用于生产者消费者模式、消息传递、并行任务执行和相关并发设计的大多数常见使用上下文。
BlockingQueue在Queue接口基础上提供了额外的两种类型的操作,分别是获取元素时等待队列变为非空和添加元素时等待空间变为可用。
BlockingQueue新增操作的四种形式:
请点击输入图片描述
插入操作是指向队列中添加一个元素,至于元素存放的位置与具体队列的实现有关。移除操作将会移除队列的头部元素,并将这个移除的元素作为返回值反馈给调用者。检查操作是指返回队列的头元素给调用者,队列不对这个头元素进行删除处理。
抛出异常形式的操作,在队列已满的情况下,调用add方法将会抛出IllegalStateException异常。如果调用remove方法时,队列已经为空,则抛出一个NoSuchElementException异常。(实际上,remove方法还可以附带一个参数,用来删除队列中的指定元素,如果这个元素不存在,也会抛出NoSuchElementException异常)。如果调用element检查头元素,队列为空时,将会抛出NoSuchElementException异常。
特殊值操作与抛出异常不同,在出错的时候,返回一个空指针,而不会抛出异常。
阻塞形式的操作,调用put方法时,如果队列已满,则调用线程阻塞等待其它线程从队列中取出元素。调用take方法时,如果阻塞队列已经为空,则调用线程阻塞等待其它线程向队列添加新元素。
超时形式操作,在阻塞的基础上添加一个超时限制,如果等待时间超过指定值,抛出InterruptedException。
阻塞队列实现了Queue接口,而Queue接口实现了Collection接口,因此BlockingQueue也提供了remove(e)操作,即从队列中移除任意指定元素,但是这个操作往往不会按预期那样高效的执行,所以应当尽量少的使用这种操作。
阻塞队列与并发队列(例如ConcurrentLinkQueue)都是线程安全的,但使用的场合不同。
Graphic3-1给出了阻塞队列的接口方法,Graphic3-2给出了阻塞队列的实现类结构。
Graphic 3-1 BlockingQueue接口
Graphic3-2阻塞队列的实现类
请点击输入图片描述
3.1.1 ArrayBlockingQueue类
一个以数组为基础的有界阻塞队列,此队列按照先进先出原则对元素进行排序。队列头部元素是队列中存在时间最长的元素,队列尾部是存在时间最短的元素,新元素将会被插入到队列尾部。队列从头部开始获取元素。
ArrayBlockingQueue是“有界缓存区”模型的一种实现,一旦创建了这样的缓存区,就不能再改变缓冲区的大小。ArrayBlockingQueue的一个特点是,必须在创建的时候指定队列的大小。当缓冲区已满,则需要阻塞新增的插入操作,同理,当缓冲区已空需要阻塞新增的提取操作。
ArrayBlockingQueue是使用的是循环队列方法实现的,对ArrayBlockingQueue的相关操作的时间复杂度,可以参考循环队列进行分析。
3.1.2 LinkedBlockingQueue
一种通过链表实现的阻塞队列,支持先进先出。队列的头部是队列中保持时间最长的元素,队列的尾部是保持时间最短的元素。新元素插入队列的尾部。可选的容量设置可以有效防止队列过于扩张造成系统资源的过多消耗,如果不指定队列容量,队列默认使用Integer.MAX_VALUE。LinkedBlockingQueue的特定是,支持无限(理论上)容量。
3.1.3 PriorityBlockingQueue
PriorityBlockingQueue是一种基于优先级进行排队的无界队列。队列中的元素按照其自然顺序进行排列,或者根据提供的Comparator进行排序,这与构造队列时,提供的参数有关。
使用提取方法时,队列将返回头部,具有最高优先级(或最低优先级,这与排序规则有关)的元素。如果多个元素具有相同的优先级,则同等优先级间的元素获取次序无特殊说明。
优先级队列使用的是一种可扩展的数组结构,一般可以认为这个队列是无界的。当需要新添加一个元素时,如果此时数组已经被填满,优先队列将会自动扩充当前数组(一般认为是,先分配一个原数组一定倍数空间的数组,之后将原数组中的元素拷贝到新分配的数组中,释放原数组的空间)。
如果使用优先级队列的iterator变量队列时,不保证遍历次序按照优先级大小进行。因为优先级队列使用的是堆结构。如果需要按照次序遍历需要使用Arrays.sort(pq.toArray())。关于堆结构的相关算法,请查考数据结构相关的书籍。
在PriorityBlockingQueue的实现过程中聚合了PriorityQueue的一个实例,并且优先队列的操作完全依赖与PriorityQueue的实现。在PriorityQueue中使用了一个一维数组来存储相关的元素信息。一维数组使用最小堆算法进行元素添加。
Graphic3-3PriorityBlockingQueue的类关系
请点击输入图片描述
3.1.4 DelayQueue
一个无界阻塞队列,只有在延时期满时才能从中提取元素。如果没有元素到达延时期,则没有头元素。
3.2 并发集合
在多线程程序中使用的集合类,与普通程序中使用的集合类是不同的。因为有可能多个线程同时访问或修改同一集合,如果使用普通集合,很可能造成相应操作出现差错,甚至崩溃。Java提供了用于线程访问安全的集合。(前面讨论的BlockingQueue也是这里集合中的一种)。下面针对这些集合,以及集合中使用的相应算法进行探讨。在设计算法时,仅对相应算法进行简要说明,如果读者需要深入了解这些算法的原理,请参考其他的高级数据结构相关的书籍。
3.2.1 ConcurrentMap接口
ConcurrentMap接口在Map接口的基础上提供了一种线程安全的方法访问机制。ConcurrentMap接口额外提供了多线程使用的四个方法,这四个方法实际是对Map已有方法的一个组合,并对这种组合提供一种原子操作。Graphic3-4给出了ConcurrentMap相关的操作。Graphic3-5给出了ConcurrentMap的实现类关系图。
从Graphic3-5中可以看出ConcurrentNavigableMap继承自ConcurrentMap,ConcurrentNavigableMap是一种SortedMap,就是说,映射中的元素会根据键值进行排序的。在java.util类库中,有两个类实现了SortedMap接口,分别是TreeMap和ConcurrentSkipListMap。TreeMap使用的是红黑树结构。而ConcurrentSkipListMap使用作为底层实现的SkipList(翻译为跳表)数据结构。此外ConcurrentHashMap实现了ConcurrentMap接口,使用的是HashMap方法。
Graphic3-4 ConcurrentMap
Graphic3-5 实现ConcurrentMap接口。
请点击输入图片描述
3.2.1.1 TreeMap
尽管TreeMap不是线程安全的,但是基于其数据结构的复杂性和方便对比说明,还是在这里简单提一下。TreeMap实现了SortedMap接口。TreeMap使用的是红黑树(这是高等数据结构中的一种),在红黑树算法中,当添加或删除节点时,需要进行旋转调整树的高度。使用红黑树算法具有较好的操作特性,插入、删除、查找都能在O(log(n))时间内完成。红黑树理论和实现是很复杂的,但可以带来较高的效率,因此在许多场合也得到了广泛使用。红黑树的一个缺陷在于,可变操作很可能影响到整棵树的结构,针对修改的局部效果不好。相关算法请参考。
TreeMap不是线程安全的,如果同时有多个线程访问同一个Map,并且其中至少有一个线程从结构上修改了该映射,则必须使用外部同步。可以使用Collections.synchronizedSortedMap方法来包装该映射。(注意使用包装器包装的SortMap是线程安全的,但不是并发的,效率上很可能远远不及ConcurrentSkipListMap,因此使用包装器的方法并不十分推荐,有人认为那是一种过时的做法。包装器使用了锁机制控制对Map的并发访问,但是这种加锁的粒度可能过大,很可能影响并发度)。
3.2.1.2 ConcurrentSkipListMap
另外一种实现了SortedMap接口的映射表是ConcurrentSkipListMap。ConcurrentSkipListMap提供了一种线程安全的并发访问的排序映射表。SkipList(跳表)结构,在理论上能够在O(log(n))时间内完成查找、插入、删除操作。SkipList是一种红黑树的替代方案,由于SkipList与红黑树相比无论从理论和实现都简单许多,所以得到了很好的推广。SkipList是基于一种统计学原理实现的,有可能出现最坏情况,即查找和更新操作都是O(n)时间复杂度,但从统计学角度分析这种概率极小。Graphic3-6给出了SkipList的数据表示示例。有关skipList更多的说明可以参考: 和 这里不在累述。希望读者自行学习。
使用SkipList类型的数据结构更容易控制多线程对集合访问的处理,因为链表的局部处理性比较好,当多个线程对SkipList进行更新操作(指插入和删除)时,SkipList具有较好的局部性,每个单独的操作,对整体数据结构影响较小。而如果使用红黑树,很可能一个更新操作,将会波及整个树的结构,其局部性较差。因此使用SkipList更适合实现多个线程的并发处理。在非多线程的情况下,应当尽量使用TreeMap,因为似乎红黑树结构要比SkipList结构执行效率略优(无论是时间复杂度还是空间复杂度,作者没有做够测试,只是直觉)。此外对于并发性相对较低的并行程序可以使用Collections.synchronizedSortedMap将TreeMap进行包装,也可以提供较好的效率。对于高并发程序,应当使用ConcurrentSkipListMap,能够提供更高的并发度。
所以在多线程程序中,如果需要对Map的键值进行排序时,请尽量使用ConcurrentSkipListMap,可能得到更好的并发度。
注意,调用ConcurrentSkipListMap的size时,由于多个线程可以同时对映射表进行操作,所以映射表需要遍历整个链表才能返回元素个数,这个操作是个O(log(n))的操作。
Graphic3-6 SkipList示例
请点击输入图片描述
3.2.1.3 HashMap类
对Map类的另外一个实现是HashMap。HashMap使用Hash表数据结构。HashMap假定哈希函数能够将元素适当的分布在各桶之间,提供一种接近O(1)的查询和更新操作。但是如果需要对集合进行迭代,则与HashMap的容量和桶的大小有关,因此HashMap的迭代效率不会很高(尤其是你为HashMap设置了较大的容量时)。
与HashMap性能有影响的两个参数是,初始容量和加载因子。容量是哈希表中桶的数量,初始容量是哈希表在创建时的容量。加载因子是哈希表在容器容量被自动扩充之前,HashMap能够达到多满的一种程度。当hash表中的条目数超出了加载因子与当前容量的乘积时,Hash表需要进行rehash操作,此时Hash表将会扩充为以前两倍的桶数,这个扩充过程需要进行完全的拷贝工作,效率并不高,因此应当尽量避免。合理的设置Hash表的初始容量和加载因子会提高Hash表的性能。HashMap自身不是线程安全的,可以通过Collections的synchronizedMap方法对HashMap进行包装。
3.2.1.4 ConcurrentHashMap类
ConcurrentHashMap类实现了ConcurrentMap接口,并提供了与HashMap相同的规范和功能。实际上Hash表具有很好的局部可操作性,因为对Hash表的更新操作仅会影响到具体的某个桶(假设更新操作没有引发rehash),对全局并没有显著影响。因此ConcurrentHashMap可以提供很好的并发处理能力。可以通过concurrencyLevel的设置,来控制并发工作线程的数目(默认为16),合理的设置这个值,有时很重要,如果这个值设置的过高,那么很有可能浪费空间和时间,使用的值过低,又会导致线程的争用,对数量估计的过高或过低往往会带来明显的性能影响。最好在创建ConcurrentHashMap时提供一个合理的初始容量,毕竟rehash操作具有较高的代价。
3.2.2 ConcurrentSkipListSet类
实际上Set和Map从结构来说是很像的,从底层的算法原理分析,Set和Map应当属于同源的结构。所以Java也提供了TreeSet和ConcurrentSkipListSet两种SortedSet,分别适合于非多线程(或低并发多线程)和多线程程序使用。具体的算法请参考前述的Map相关介绍,这里不在累述。
3.2.3 CopyOnWriteArrayList类
CopyOnWriteArrayList是ArrayList的一个线程安全的变体,其中对于所有的可变操作都是通过对底层数组进行一次新的复制来实现的。
由于可变操作需要对底层的数据进行一次完全拷贝,因此开销一般较大,但是当遍历操作远远多于可变操作时,此方法将会更有效,这是一种被称为“快照”的模式,数组在迭代器生存期内不会发生更改,因此不会产生冲突。创建迭代器后,迭代器不会反映列表的添加、移除或者更改。不支持在迭代器上进行remove、set和add操作。CopyOnWriteArraySet与CopyOnWriteArrayList相似,只不过是Set类的一个变体。
3.2.3 Collections提供的线程安全的封装
Collections中提供了synchronizedCollection、synchronizedList、synchronizedMap、synchronizedSet、synchronizedSortedMap、synchronizedSortedMap等方法可以完成多种集合的线程安全的包装,如果在并发度不高的情况下,可以考虑使用这些包装方法,不过由于Concurrent相关的类的出现,已经不这么提倡使用这些封装了,这些方法有些人称他们为过时的线程安全机制。
3.2.4 简单总结
提供线程安全的集合简单概括分为三类,首先,对于并发性要求很高的需求可以选择以Concurrent开头的相应的集合类,这些类主要包括:ConcurrentHashMap、ConcurrentLinkedQueue、ConcurrentSkipListMap、ConcurrentSkipSet。其次对于可变操作次数远远小于遍历的情况,可以使用CopyOnWriteArrayList和CopyOnWriteArraySet类。最后,对于并发规模比较小的并行需求可以选择Collections类中的相应方法对已有集合进行封装。
此外,本章还对一些集合类的底层实现进行简单探讨,对底层实现的了解有利于对何时使用何种方式作出正确判断。希望大家能够将涉及到原理(主要有循环队列、堆、HashMap、红黑树、SkipList)进行仔细研究,这样才能更深入了解Java为什么这样设计类库,在什么情况使用,应当如何使用。
ConcurrentHashMap如何实现高效地线程安全?
如何保证容器是线程安全的? ConcurrentHashMap如何实现高效地线程安全?
Java提供了不同层面的线程安全支持。在传统集合框架内部,除了Hashtable等同步容器,还提供了所谓的同步包装器(Synchronized Wrapper),我们可以调用Collections工具类提供的包装方法,来获取一个同步的包装容器(如Collections.synchronizedMap),但是它们都是利用非常粗粒度的同步方式,在高并发情况下,性能比较低下。
另外,更加普遍的选择是利用并发包提供的线程安全容器类,它提供了:
各种并发容器,比如ConcurrentHashMap、 CopyOnWriteArrayList。
各种线程安全队列(Queue/Deque),如ArrayBlockingQueue、 SynchronousQueue。
各种有序容器的线程安全版本等。
1.为什么需要ConcurrentHashMap?
Hashtable本身比较低效,因为它的实现基本就是将put、 get、 size等各种方法加上“synchronized”。简单来说,这就导致了所有并发操作都要竞争同一把锁,一个线程在进行同
步操作时,其他线程只能等待,大大降低了并发操作的效率。
个人理解
就是更细粒度的加锁机制来实现更大程度的共享。
2.ConcurrentHashMap分析
ConcurrentHashMap的设计实现其实一直在演化。
1.7
put加锁
通过分段加锁segment,一个hashmap里有若干个segment,每个segment里有若干个桶,桶里存放K-V形式的链表, put数据时通过key哈希得到该元素要添加到的segment,
然后
对segment进行加锁,然后在哈希,计算得到给元素要添加到的桶,然后遍历桶中的链表,替换或新增节点到桶中
size
分段计算两次,两次结果相同则返回,否则对所以段加锁重新计算
1.8
put CAS 加锁
1.8中不依赖与segment加锁,segment数量与桶数量一致;
首先判断容器是否为空,为空则进行初始化利用volatile的sizeCtl作为互斥手段,如果发现竞争性的初始化,就暂停在那里,等待条件恢复,否则利用CAS设置排他标志
(U.compareAndSwapInt(this, SIZECTL, sc, -1)) ;否则重试
对key hash计算得到该key存放的桶位置,判断该桶是否为空,为空则利用CAS设置新节点
否则使用synchronize加锁,遍历桶中数据,替换或新增加点到桶中
最后判断是否需要转为红黑树,转换之前判断是否需要扩容
size
利用LongAdd累加计算
ConcurrentHashMap使用要点
ConcurrentHashMap使用要点
ConcurrentHashMap允许一边更新、一边遍历,也就是说在Iterator对象遍历的时候,ConcurrentHashMap也可以进行remove,put操作,且遍历的数据会随着remove,put操作产出变化
java集合 有序无序,线程是否安全
1.有序集合:集合里的元素可以根据key或index访问;无序集合:集合里的元素只能遍历。
有序集合在属性的增加,删除及修改中拥有较好的性能表现。
Set集合一般是无序的。实现hash算法的集合一般是无序的,例如hashMap,hashTable
List集合一般是有序的。
底层是Tree的一般是有序的,例如TreeSet,TreeMap
底层有lined的一般是有序的,它会用链表维护元素的顺序。
综上:
有序的:List的所有子类
无序的:一般的Set,除了TreeSet,linkedHashSet等底层是树或者链表的。一般的Map,除了底层是树或者链表的。
已知的线程安全集合:vector,hashtable,statck,enumeration
希望可以帮到你,谢谢!
关于java线程安全链表和并发安全的链表的介绍到此就结束了,不知道你从中找到你需要的信息了吗 ?如果你还想了解更多这方面的信息,记得收藏关注本站。
发布于:2022-11-24,除非注明,否则均为
原创文章,转载请注明出处。