ConcurrentBag的实现原理,Java集合类工作原理及实现
分类:计算机编程

目录

集合类的贯彻原理

集合

 


 

1.集结接口和花色

接口

说明

IEnumerable<T>

如果foreach语句用于集合,就需要IEnumerable接口.这个借口定义了方法GetEnumerator(),他返回一个实现了IEnumerator接口的枚举

ICollection<T>

ICollection<T>接口有泛型集合类实现.使用这个借口可以获得集合中的元素个数(Count属性),把集合复制到数组中(CopyTo()方法),还可以从集合中添加和删除元素(Add(),Remove(),Clear())

List<T>

IList<T>接口用于可通过位置访问其中的元素列表,这个接口定义了一个 索引器,可以在集合的指定位置插入或删除 mount些项(Insert()和Remove()方法).IList<T>接口派生自ICollection<T>接口

ISet<T>

ISet<T>接口是.NET4中新增的.实现这个接口的集允许合并不同的集.获得两个集的交集,检查两个集合是否重叠.ISet<T>接口派生自ICollection<T>接口

IDictionary<TKey,TValue>

IDictionary<TKey,TValue>接口由包含键和值的泛型集合类 实现.使用这个接口可以访问所有的键和值,使用键类型的索引器可以访问某些项,还可以添加或删除某些项

ILookup<TKey,TValue>

ILookup<TKey,TValue>接口类似于IDictionary<TKey,TValue>接口,实现该接口的集合有键和值,且可以通过一个键包含多个值

IComparer<T>

接口ICommparer<T>由比较器实现,通过Comparer()方法给集合中的元素排序

IEqualityComparer<T>

接口IEqualityComparer<T>由一个比较器实现,该比较器可用于字典中的键.使用这个接口,可以对对象进行相等性比较.在.NET中,这个接口也由数组和元组实现

IProducerConsumerColllection<T>

IProducerConsumerCollection<T>接口是.NET4中新增的,它支持新的线程安全的集合类

IReadOnlyList<T>、

IReadOnlyDictionary<T>、

IReadOnlyCollection<T>

初始化后不能修改的集合,只能检索对象,不能添加和删除.

 

2.列表

先看看二个实例:

图片 1图片 2

[Serializable]
  public class Racer : IComparable<Racer>, IFormattable
  {
    public int Id { get; private set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Country { get; set; }
    public int Wins { get; set; }

    public Racer(int id, string firstName, string lastName, string country)
      : this(id, firstName, lastName, country, wins: 0)
    {
    }
    public Racer(int id, string firstName, string lastName, string country, int wins)
    {
      this.Id = id;
      this.FirstName = firstName;
      this.LastName = lastName;
      this.Country = country;
      this.Wins = wins;
    }

    public override string ToString()
    {
      return String.Format("{0} {1}", FirstName, LastName);
    }

    public string ToString(string format, IFormatProvider formatProvider)
    {
      if (format == null) format = "N";
      switch (format.ToUpper())
      {
        case null:
        case "N": // name
          return ToString();
        case "F": // first name
          return FirstName;
        case "L": // last name
          return LastName;
        case "W": // Wins
          return String.Format("{0}, Wins: {1}", ToString(), Wins);
        case "C": // Country
          return String.Format("{0}, Country: {1}", ToString(), Country);
        case "A": // All
          return String.Format("{0}, {1} Wins: {2}", ToString(), Country, Wins);
        default:
          throw new FormatException(String.Format(formatProvider,
                "Format {0} is not supported", format));
      }
    }

    public string ToString(string format)
    {
      return ToString(format, null);
    }

    public int CompareTo(Racer other)
    {
      if (other == null) return -1;
      int compare = string.Compare(this.LastName, other.LastName);
      if (compare == 0)
        return string.Compare(this.FirstName, other.FirstName);
      return compare;
    }
  }

View Code

自学ConcuurentHashMap源码

  • 一、前言
  • 二、ConcurrentBag类
  • 三、 ConcurrentBag线程安全实现原理
    • 1. ConcurrentBag的民用字段
    • 2. 用以数据存储的TrehadLocalList类
    • 3. ConcurrentBag达成新增新币素
    • 4. ConcurrentBag 怎么着兑现迭代器形式
  • 四、总结
  • 小编水平有限,假设不当款待各位商量指正!

LRUCache原理

核心算法
map:存放数据的集合    new LinkedHashMap<K, V>(0, 0.75f, true);
size:当前LruCahce的内存占用大小
maxSize:Lrucache的最大容量
putCount:put的次数
createCount:create的次数
evictionCount:回收的次数
hitCount:命中的次数
missCount:丢失的次数

Lru是近期最少使用算法的简称,意思啊正是询问出以来的时日利用次数起码的极其指标。设置为true的时候,假诺对叁个要素进行了操作(put、get),就能把极度成分放到集结的末尾,设置为false的时候,无论怎么操作,集合成分的顺序都以服从插入的顺序来开展仓库储存的。
算法宗旨:当内容容积达到最大值的时候,只须要移除这一个会集的后面包车型大巴因素直到集合的体量丰盛存款和储蓄数据的时候就足以了。

制造列表

应用暗中同意的构造函数创制二个空驶列车表,成分增添到列表后,列表体量会扩充到可接受4个因素。
倘诺增多了第5个要素,列表大小会重新载入参数为8个要素。每便都会将列表的容积重新安装为原本的2倍.

var intList=new List<int>();

 

只要列表的体积变了,整个集结就要重新分配到新的内部存款和储蓄器块中,大家能够在开头化时设置它的容积:

List<int> intList=new List<int>(10);

假如列表的个数当先10个,能够安装体量Capacity:

intList.Capacity = 20;

借使列表的要素已经增多完了,列表会存在多余的容积空间。能够选用TrimExcess方法去除不要的容积:

intList.TrimExcess();

a.会集开端值设定项

运用初叶化构造器伊始化设定项

int[] arr = { 1, 2, 3 };
var intList = new List<int>(arr) ;

括号中开端化

var intList = new List<int>() { 4, 5 };

 

b.添韩元素

intList.Add(5);

累计数组

intList.AddRange(new int[] { 3, 5 });

c.插入成分

intList.Insert(3, 4);

d.访谈成分

动用索引获取:

var value = intList[3];

巡回遍历:

foreach (var item in intList)
{
     var res = item;
}

forEach方法:

class List<T> : IList<T>
{
    private T[] items;
    public void forEach(Action<T> action)
    {
        if (action == null) throw new ArgumentNullException("action");
        foreach (var item in items)
        {
            action(item);
        }
    }
}

然后大家能够那样调用:

intList.ForEach(m => Console.WriteLine(m));

e.删除成分

按索引删除,非常的慢

intList.RemoveAt(3);

按成分值删除

intList.Remove(4);

f.搜索

在汇聚中寻觅成分。可以搜索索引和要素。

FindIndex通过相配成分值,得到索引:

intList.FindIndex(m => m==4);

FindIndex方法参数Predicate<T>传入相称的表达式,再次来到相称的成分索引值,Predicate<T>委托代表定义一组条件并分明钦赐对象是或不是切合那么些标准的主意

intList.Find(m => m == 4);
intList.FindAll(m => m > 2);

Find重临了协作原则的要素值,FindAll重回了极度原则的富有因素

g.排序

列表使用Sort方法实行成分排序

intList.Sort();

intList.Sort((m, n) => m);

Sort(Comparison<T> comparison)方法参数中的委托Comparison含有2个参数,方法将那2个要素实行相比较,然后回到绝对值,借使回到-1的,成分要求排前边,重返1的要素须要排前边.

Sort(IComparer<T> comparer)方法参数中是二个相比较接口,接口实现Comparer方法

图片 3图片 4

public enum CompareType
  {
    FirstName,
    LastName,
    Country,
    Wins
  }

  public class RacerComparer : IComparer<Racer>
  {
    private CompareType compareType;
    public RacerComparer(CompareType compareType)
    {
      this.compareType = compareType;
    }

    public int Compare(Racer x, Racer y)
    {
      if (x == null && y == null) return 0;
      if (x == null) return -1;
      if (y == null) return 1;

      int result;
      switch (compareType)
      {
        case CompareType.FirstName:
          return string.Compare(x.FirstName, y.FirstName);
        case CompareType.LastName:
          return string.Compare(x.LastName, y.LastName);
        case CompareType.Country:
          result = string.Compare(x.Country, y.Country);
          if (result == 0)
            return string.Compare(x.LastName, y.LastName);
          else
            return result;
        case CompareType.Wins:
          return x.Wins.CompareTo(y.Wins);
        default:
          throw new ArgumentException("Invalid Compare Type");
      }
    }
  }

View Code

 

h.类型转变

 Converter委托

public delegate TOutput Converter<in TInput, out TOutput>(TInput input);

ConvertAll可以将一种类型的集合转换为另一种类型的集合。

intList.ConvertAll(m => m.ToString());

参考:

本文须要关爱的地点。


HashMap

图解HashMap(一)/)

图解HashMap(二)/)

HashMap实际上是八个“链表散列”的数据结构,即数组和链表的结合体

图片 5

image.png

粗略地说,HashMap 在后面部分将 key-value 当成二个完好无缺举办管理,那几个欧洲经济共同体就是四个 Entry 对象。HashMap 底层选用三个Entry[] 数组来保存全体的 key-value 对,当要求仓库储存八个 Entry 对象时,会依据 hash 算法来决定其在数组中的存款和储蓄地方,在依照 equals 方法决定其在该数组地方上的链表中的存款和储蓄地方;当供给抽取二个Entry 时,也会依附 hash 算法找到其在数组中的存款和储蓄地方,再依赖 equals 方法从该职位上的链表中抽取该Entry。

扩容:当 HashMap 中的成分个数超过数组大小 loadFactor时,就能够实行数组扩大体量,loadFactor的暗中同意值为 0.75,那是一个折中的取值。也正是说,私下认可景况下,数组大小为 16,那么当 HashMap 二月素个数超过 160.75=12 的时候,就把数组的深浅扩充为 2*16=32,即扩充学一年级倍,然后再一次总结每一个成分在数组中的地方,而那是多少个丰裕消耗质量的操作,所以假若大家早就预言HashMap 七月素的个数,那么预设成分的个数能够行得通的拉长 HashMap 的习性。

Fail-Fast 机制:HashMap 不是线程安全的,由此一旦在应用迭代器的经过中有此外线程修改了 map,那么将抛出 ConcurrentModificationException,那正是所谓 fail-fast 计策。
举例说:当某贰个线程 A 通过 iterator去遍历某集结的长河中,若该集合的内容被其他线程所更换了;那么线程 A 访谈集适当时候,就能抛出ConcurrentModificationException 十分,发生fail-fast 事件。
完结原理:判别 modCount 跟 expectedModCount 是或不是等于,假若不等于就意味着早就有别的线程修改了 Map。
缓和方案:建议选取“java.util.concurrent 包下的类”去取代“java.util 包下的类”

遍历格局:
第一种:

   Map map = new HashMap();
  Iterator iter = map.entrySet().iterator();
  while (iter.hasNext()) {
  Map.Entry entry = (Map.Entry) iter.next();
  Object key = entry.getKey();
  Object val = entry.getValue();
    }

频率高,将来一定要采纳此种格局
第二种:

Map map = new HashMap();
  Iterator iter = map.keySet().iterator();
  while (iter.hasNext()) {
  Object key = iter.next();
  Object val = map.get(key);
  }

频率低,未来尽量少使用!

只读集结

成立集合后,它们是只读的。

3.队列

代表了三个先进先出的靶子集结。当您须要对各种实行先进先出的拜候时,则动用队列。当您在列表中增多一项,称为入队,当你从列表中移除一项时,称为出队

增多队列成分时加上lock,因为二十八线程可以而且做客,所以对队列举办锁定访问。

图片 6

 

图片 7图片 8

using System;
using System.Collections;

namespace CollectionsApplication
{
   class Program
   {
      static void Main(string[] args)
      {
         Queue q = new Queue();

         q.Enqueue('A');
         q.Enqueue('M');
         q.Enqueue('G');
         q.Enqueue('W');

         Console.WriteLine("Current queue: ");
         foreach (char c in q)
            Console.Write(c   " ");
         Console.WriteLine();
         q.Enqueue('V');
         q.Enqueue('H');
         Console.WriteLine("Current queue: ");         
         foreach (char c in q)
            Console.Write(c   " ");
         Console.WriteLine();
         Console.WriteLine("Removing some values ");
         char ch = (char)q.Dequeue();
         Console.WriteLine("The removed value: {0}", ch);
         ch = (char)q.Dequeue();
         Console.WriteLine("The removed value: {0}", ch);
         Console.ReadKey();
      }
   }
}

View Code

当上边的代码被编写翻译和施行时,它会发出下列结果:

Current queue: 
A M G W 
Current queue: 
A M G W V H 
Removing values
The removed value: A
The removed value: M

图片 9图片 10

class Program
    {
        static void Main()
        {
            var dm = new DocumentManager();

            ProcessDocuments.Start(dm);

            // Create documents and add them to the DocumentManager
            for (int i = 0; i < 1000; i  )
            {
                Document doc = new Document("Doc "   i.ToString(), "content");
                dm.AddDocument(doc);
                Console.WriteLine("Added document {0}", doc.Title);
                Thread.Sleep(new Random().Next(20));
            }

        }
    }

Program

图片 11图片 12

public class ProcessDocuments
  {
    public static void Start(DocumentManager dm)
    {
      Task.Factory.StartNew(new ProcessDocuments(dm).Run);
    }

    protected ProcessDocuments(DocumentManager dm)
    {
      if (dm == null)
        throw new ArgumentNullException("dm");
      documentManager = dm;
    }

    private DocumentManager documentManager;

    protected void Run()
    {
      while (true)
      {
        if (documentManager.IsDocumentAvailable)
        {
          Document doc = documentManager.GetDocument();
          Console.WriteLine("Processing document {0}", doc.Title);
        }
        Thread.Sleep(new Random().Next(20));
      }
    }
  }

ProcessDocuments

图片 13图片 14

public class DocumentManager
  {
    private readonly Queue<Document> documentQueue = new Queue<Document>();

    public void AddDocument(Document doc)
    {
      lock (this)
      {
        documentQueue.Enqueue(doc);
      }
    }

    public Document GetDocument()
    {
      Document doc = null;
      lock (this)
      {
        doc = documentQueue.Dequeue();
      }
      return doc;
    }

    public bool IsDocumentAvailable
    {
      get
      {
        return documentQueue.Count > 0;
      }
    }
  }

DocumentManager

图片 15图片 16

public class Document
  {
    public string Title { get; private set; }
    public string Content { get; private set; }

    public Document(string title, string content)
    {
      this.Title = title;
      this.Content = content;
    }
  }

Document

4.栈

表示了七个后进先出的指标会集。当您需求对各个实行后进先出的探问时,则运用仓库。当您在列表中加多一项,称为推入要素,当你从列表中移除一项时,称为弹出元素。

图片 17

图片 18图片 19

class Program
    {
        static void Main()
        {
            var alphabet = new Stack<char>();
            alphabet.Push('A');
            alphabet.Push('B');
            alphabet.Push('C');

            Console.Write("First iteration: ");
            foreach (char item in alphabet)
            {
                Console.Write(item);
            }
            Console.WriteLine();

            Console.Write("Second iteration: ");
            while (alphabet.Count > 0)
            {
                Console.Write(alphabet.Pop());
            }
            Console.WriteLine();


        }
    }

Program

First iteration: CBA
Second iteration: CBA

5.链表

LinkedList<T>是二个双向链表,其成分指向它前边和前边的因素,这样经过运动下三个要素就足以正向遍历整个链表。通过活动到前二个成分得以反向遍历那一个链表

链表的长处是,假诺将成分插入列表的中级位置,使用链表会异常快,在插入三个因素时,只需求修改上贰个要素的Next引用和下一个成分的Previous援引,使她们援引所插入的因素。

图片 20图片 21

public class Document
  {
    public string Title { get; private set; }
    public string Content { get; private set; }
    public byte Priority { get; private set; }

    public Document(string title, string content, byte priority)
    {
      this.Title = title;
      this.Content = content;
      this.Priority = priority;
    }
  }

Document

图片 22图片 23

public class PriorityDocumentManager
  {
    private readonly LinkedList<Document> documentList;

    // priorities 0.9
    private readonly List<LinkedListNode<Document>> priorityNodes;

    public PriorityDocumentManager()
    {
      documentList = new LinkedList<Document>();

      priorityNodes = new List<LinkedListNode<Document>>(10);
      for (int i = 0; i < 10; i  )
      {
        priorityNodes.Add(new LinkedListNode<Document>(null));
      }
    }

    public void AddDocument(Document d)
    {
      Contract.Requires<ArgumentNullException>(d != null, "argument d must not be null");
      //  if (d == null) throw new ArgumentNullException("d");

      AddDocumentToPriorityNode(d, d.Priority);
    }

    private void AddDocumentToPriorityNode(Document doc, int priority)
    {
      Contract.Requires<ArgumentException>(priority >= 0 && priority < 10, "priority value must be between 0 and 9");
      //if (priority > 9 || priority < 0)
      //    throw new ArgumentException("Priority must be between 0 and 9");

      if (priorityNodes[priority].Value == null)
      {
        --priority;
        if (priority >= 0)
        {
          // check for the next lower priority
          AddDocumentToPriorityNode(doc, priority);
        }
        else // now no priority node exists with the same priority or lower
        // add the new document to the end
        {
          documentList.AddLast(doc);
          priorityNodes[doc.Priority] = documentList.Last;
        }
        return;
      }
      else // a priority node exists
      {
        LinkedListNode<Document> prioNode = priorityNodes[priority];
        if (priority == doc.Priority)
        // priority node with the same priority exists
        {
          documentList.AddAfter(prioNode, doc);

          // set the priority node to the last document with the same priority
          priorityNodes[doc.Priority] = prioNode.Next;
        }
        else // only priority node with a lower priority exists
        {
          // get the first node of the lower priority
          LinkedListNode<Document> firstPrioNode = prioNode;

          while (firstPrioNode.Previous != null &&
             firstPrioNode.Previous.Value.Priority == prioNode.Value.Priority)
          {
            firstPrioNode = prioNode.Previous;
            prioNode = firstPrioNode;
          }

          documentList.AddBefore(firstPrioNode, doc);

          // set the priority node to the new value
          priorityNodes[doc.Priority] = firstPrioNode.Previous;
        }
      }
    }

    public void DisplayAllNodes()
    {
      foreach (Document doc in documentList)
      {
        Console.WriteLine("priority: {0}, title {1}", doc.Priority, doc.Title);
      }
    }

    // returns the document with the highest priority
    // (that's first in the linked list)
    public Document GetDocument()
    {
      Document doc = documentList.First.Value;
      documentList.RemoveFirst();
      return doc;
    }

  }

PriorityDocumentManager

图片 24图片 25

 class Program
  {
    static void Main()
    {
        PriorityDocumentManager pdm = new PriorityDocumentManager();
        pdm.AddDocument(new Document("one", "Sample", 8));
        pdm.AddDocument(new Document("two", "Sample", 3));
        pdm.AddDocument(new Document("three", "Sample", 4));
        pdm.AddDocument(new Document("four", "Sample", 8));
        pdm.AddDocument(new Document("five", "Sample", 1));
        pdm.AddDocument(new Document("six", "Sample", 9));
        pdm.AddDocument(new Document("seven", "Sample", 1));
        pdm.AddDocument(new Document("eight", "Sample", 1));

        pdm.DisplayAllNodes();

    }
  }

Program

 

6.有体系表

SortedList基于键对集合举办排序.

class Program
  {
    static void Main()
    {
      var books = new SortedList<string, string>();
      books.Add("sty", "");
      books.Add("abc", "");
      books.Add("123", "");
      foreach (var item in books.Keys)
      {
          Console.WriteLine(item);
      }

    }
  }

123
abc
sty

 

7.字典

字典:用于在称呼/值对中蕴藏音信,字典的名号即键不能重复.

图片 26

HashTable和Dictionary

1.HashTable大数据量插入数据时索要费用比Dictionary大的多的年华。

2.for主意遍历HashTable和Dictionary速度最快。

3.在foreach情势遍历时Dictionary遍历速度更加快。

4.HashTable在取值时索要开展类型调换,Dictionary不用做类型转变。

在单线程的时候使用Dictionary更加好一些,二十八线程的时候利用HashTable越来越好。

 

以不改变应万变字典SortedList和SortedDictionary

SortedDictionary 泛型类是寻找运算复杂度为 O(log n) 的二叉寻觅树,在那之中 n 是字典中的成分数。就那一点来说,它与 SortedList 泛型类相似。这多个类具备相似的指标模型,並且都存有 O(log n) 的物色运算复杂度。那多少个类的分别在于内部存款和储蓄器的选取以致插入和移除成分的速度:

  • SortedList 使用的内部存款和储蓄器比 SortedDictionary 少。

  • SortedDictionary 可对未排序的数目实行更加快的插入和移除操作:它的年月复杂度为 O(log n),而SortedList 为 O(n)。

  • 一经应用排序数据一遍性填充列表,则 SortedList 比 SortedDictionary 快。

8.集

包涵不重复成分的集中,叫“集”。.NET包括2个集。HashSet<T>和SortedSet<T>,它们继续ISet;SortedSet是三个长期以来集.

ISet提供了Add方法,如若HashSet中留存这一个元素,再一次利用Add方法不会抛出极度,重临bool值是不是充裕

var companyTeams = new HashSet<string>() { "Ferrari", "McLaren", "Mercedes" };
var traditionalTeams = new HashSet<string>() { "Ferrari", "McLaren" };
var privateTeams = new HashSet<string>() { "Red Bull", "Lotus", "Toro Rosso", "Force India", "Sauber" };

if (privateTeams.Add("Williams"))
    Console.WriteLine("Williams added");
if (!companyTeams.Add("McLaren"))
    Console.WriteLine("McLaren was already in this set");

IsSubsetOf方法判断了traditionalTeams集合是否companyTeams的子集
IsSupersetOf方法判断了companyTeams集合是否traditionalTeams的超集(包含它拥有的所有元素,并且多余它的元素)

var companyTeams = new HashSet<string>() { "Ferrari", "McLaren", "Mercedes" };
var traditionalTeams = new HashSet<string>() { "Ferrari", "McLaren" };
var privateTeams = new HashSet<string>() { "Red Bull", "Lotus", "Toro Rosso", "Force India", "Sauber" };

if (traditionalTeams.IsSubsetOf(companyTeams))
{
  Console.WriteLine("traditionalTeams is subset of companyTeams");
}

if (companyTeams.IsSupersetOf(traditionalTeams))
{
   Console.WriteLine("companyTeams is a superset of traditionalTeams");
}

SortedSet的UnionWith方法可以修改这个集合,并且包含传入的集合

var allTeams = new SortedSet<string>(companyTeams);
allTeams.UnionWith(privateTeams);
allTeams.UnionWith(traditionalTeams);

 

9.可探求的聚焦

若果须要记录集结何时加多和删除成分的信息,能够行使ObservableCollection<T>,那几个自家是为WPF定制的。

ObservableCollection<T>类用于创制自定义集结,在其间接选举用List<T>类,重写虚方法RemoveItem和SetItem()方法触发CollectionChanged事件。

图片 27图片 28

class Program
  {
    static void Main()
    {
      var data = new ObservableCollection<string>();
      data.CollectionChanged  = Data_CollectionChanged;
      data.Add("One");
      data.Add("Two");
      data.Insert(1, "Three");
      data.Remove("One");

    }

    static void Data_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
    {
      Console.WriteLine("action: {0}", e.Action.ToString());

      if (e.OldItems != null)
      {
        Console.WriteLine("starting index for old item(s): {0}", e.OldStartingIndex);
        Console.WriteLine("old item(s):");
        foreach (var item in e.OldItems)
        {
          Console.WriteLine(item);
        }
      }
      if (e.NewItems != null)
      {
        Console.WriteLine("starting index for new item(s): {0}", e.NewStartingIndex);
        Console.WriteLine("new item(s): ");
        foreach (var item in e.NewItems)
        {
          Console.WriteLine(item);
        }
      }


      Console.WriteLine();

    }
  }

View Code

Data_CollectionChanged方法接收了NotifyCollectionChanged伊夫ntArgs,包蕴了聚众的改换消息,Action属性给出了是否丰裕或删除一项的新闻,对于删除的项,会设置OldItems属性,列出删除的项

对于增进的项,会设置NewItems属性,列出丰硕的项。

action: Add
starting index for new item(s): 0
new item(s):
One

action: Add
starting index for new item(s): 1
new item(s):
Two

action: Add
starting index for new item(s): 1
new item(s):
Three

action: Remove
starting index for old item(s): 0
old item(s):
One

 

10.位数组

  1. 利用分支锁完结五个线程并发写入、删除恐怕涂改(私下认可16);
  2. 使用HashEntry的不改变性和 volatile 变量的可以见到性来保管get读大约无需加锁(判定获得的entry的value是或不是为null,为null时才使用加锁的秘籍重新去获取,原因是put的时候的一声令下重排序)
  3. get的二种情景剖析
  4. 各种版本的ConcurrentHashMap大概都有改动,本文评释的JDK6的源码。JDK8比较前边的版本退换最大,最终有简短的辨证

SparseArray

SparseArray 的采用及贯彻原理
优势:

  • 制止了中央数据类型的装箱操作
  • 没有须求额外的结构体,单个成分的囤积花费更低
  • 数据量小的状态下,随机访问的功效越来越高

有可取就势必不正常

  • 布置操作需求复制数组,增加和删除功效下落
  • 数据量宏大时,复制数组花费巨大,gc()开销也壮烈
  • 数据量宏大时,查询效能也会鲜明减少

BitArray类的不二等秘书籍和本性

下表列出了一部分BitArray类的常用属性:

属性 描述
Count 获取包含在BitArray元素的数量
IsReadOnly 获取一个值,指示BitArray是否是只读
Item 获取或设置在所述BitArray的特定位置的比特的值
Length 获取或设置在BitArray元素的数量

下表列出了有个别BitArray类的常用方法:

 

S.N 方法名称及用途
1 public BitArray And( BitArray value ); 
执行对指定BitArray的相应元素在当前BitArray元素的按位与运算
2 public bool Get( int index ); 
获取在所述BitArray的特定位置的比特的值
3 public BitArray Not();
反转当前BitArray所有的位值,使设置为true的元素被更改为false,并设置为false元素更改为true
4 public BitArray Or( BitArray value ); 
在执行对指定BitArray的相应元素在当前BitArray的元素的按位或操作
5 public void Set( int index, bool value ); 
设置在所述BitArray为指定值的特定位置的比特值
6 public void SetAll( bool value ); 
设置在BitArray所有位设置为指定值
7 public BitArray Xor( BitArray value ); 
执行关于对在指定BitArray的相应元素中的当前BitArray的元素按位异或运算

当需求仓储位,但不精通事先比特数就使用它。您能够经过应用一个平头索引,它从零带头访问BitArray群集中的项。

图片 29图片 30

using System;
using System.Collections;

namespace CollectionsApplication
{
    class Program
    {
        static void Main(string[] args)
        {
            //creating two  bit arrays of size 8
            BitArray ba1 = new BitArray(8);
            BitArray ba2 = new BitArray(8);
            byte[] a = { 60 };
            byte[] b = { 13 };

            //storing the values 60, and 13 into the bit arrays
            ba1 = new BitArray(a);
            ba2 = new BitArray(b);

            //content of ba1
            Console.WriteLine("Bit array ba1: 60");
            for (int i = 0; i < ba1.Count; i  )
            {
                Console.Write("{0, -6} ", ba1[i]);
            }
            Console.WriteLine();

            //content of ba2
            Console.WriteLine("Bit array ba2: 13");
            for (int i = 0; i < ba2.Count; i  )
            {
                Console.Write("{0, -6} ", ba2[i]);
            }
            Console.WriteLine();


            BitArray ba3 = new BitArray(8);
            ba3 = ba1.And(ba2);

            //content of ba3
            Console.WriteLine("Bit array ba3 after AND operation: 12");
            for (int i = 0; i < ba3.Count; i  )
            {
                Console.Write("{0, -6} ", ba3[i]);
            }
            Console.WriteLine();

            ba3 = ba1.Or(ba2);
            //content of ba3
            Console.WriteLine("Bit array ba3 after OR operation: 61");
            for (int i = 0; i < ba3.Count; i  )
            {
                Console.Write("{0, -6} ", ba3[i]);
            }
            Console.WriteLine();

            Console.ReadKey();
        }
    }
}

View Code

让我们编写翻译和平运动行方面包车型地铁次序,那将爆发以下结果:

Bit array ba1: 60 
False False True True True True False False 
Bit array ba2: 13
True False True True False False False False 
Bit array ba3 after AND operation: 12
False False True True False False False False 
Bit array ba3 after OR operation: 61
True False True True False False False False 

一、基础知识

  并发编制程序施行中,ConcurrentHashMap是一个平日被选择的数据结构,比较于Hashtable以致Collections.synchronizedMap(),ConcurrentHashMap能够提供越来越高的并发度。同步容器将全部对容器状态的拜谒都串行化,以贯彻它们的线程安全性。这种艺术的代价是严重下跌并发性,当八个线程竞争容器的锁时,吞吐量将严重下滑。ConcurrentHashMap关键在于使用了分段锁本领。
  在ConcurrentHashMap的贯彻中动用了二个暗含拾九个锁的数组,每一个锁珍爱有着散列桶的1/16,当中第N个散列桶由第(N mod 16)个锁来保卫安全。每贰个锁敬服的区域称为段(Segment),每一种段其实正是一个小的Hashtable,它们有投机的锁。假诺散列函数具备合理的布满性,况且首要字能够均匀布满,那么那大约能把对于锁的呼吁裁减到原本的1/16,便是那项技能驱动ConcurrentHashMap能够支持多达十五个冒出的写入器。
  ConcurrentHashMap与任何并发容器一起抓好了协同容器类:它们提供的迭代器不会抛出 ConcurrentModificationException,由此无需在迭代经过中对容器加锁。ConcurrentHashMap 重临的迭代器械备弱一致性(Weakly Consistent),而毫不“及时战败”。弱一致性的迭代器可以容忍并发的改变,当制造迭代器时会遍历已部分成分,并能够(然而不保障)在迭代器被组织后将修改操作反映给容器。
  就算有那几个革新,但还是有一部分索要权衡的要素。对于一些亟需在漫天Map上举行总结的格局,例如size和isEmpty,那一个办法的语义被略微裁减了以展示容器的产出天性。由于size 再次回到的结果在估测计算时恐怕已经过期了,它其实只是贰个估量值,因而同意size重回叁个近似值并非三个准确值。因为:事实上size和isEmpty那样的主目的在于产出情状下的用途异常的小,因为它们的再次来到值总在随地随时变化。由此,那几个操作的供给被弱化了,以换取对其他更关键操作的属性优化,满含get、put、containsKey和remove等。
  锁分段的一个劣点在于:与运用单个锁来促成独占访问比较,要获得八个锁来达成独占访谈将越发困难并且开拓更加高。经常,在施行一个操作时最五只需获得二个锁,但在少数境况下需求加锁整个容器,举例当ConcurrentHashMap供给扩张映射范围,以至重新计算键值的散列值要分布到越来越大的桶会集中时,就必要取得分段锁集合中的具备锁。

一、前言

我近来在做多少个品种,项目中为了提高吞吐量,使用了新闻队列,中间实现了生产花费方式,在生产开销者格局中要求有三个集结,来存款和储蓄生产者所生产的物料,小编利用了最常见的List<T>会见类型。

鉴于生产者线程有好八个,花费者线程也可以有广大个,所以不可制止的就生出了线程同步的难点。初步我是利用lock最首要字,举办线程同步,不过质量并不是特地理想,然后有网络朋友说能够运用SynchronizedList<T>来代表使用List<T>高达到规定的分数线程安全的目标。于是作者就替换到了SynchronizedList<T>,可是发掘质量照旧不佳,于是查看了SynchronizedList<T>的源代码,开掘它正是归纳的在List<T>提供的API的底蕴上加了lock,所以品质基本与小编完成格局相差无几。

终极作者找到了解决的方案,使用ConcurrentBag<T>类来达成,品质有非常的大的改动,于是笔者查阅了ConcurrentBag<T>的源代码,达成丰硕Mini,特此在这里记录一下。

HashSet

对此 HashSet 中保存的指标,请留神科学重写其 equals 和 hashCode 方法,以保证放入的对象的独一性。那五个艺术是十分重大的,希望我们在其后的付出进度中供给专心一下。

图片 31

image.png

BitVector32

提供了一个简易结构,该组织以三拾三个人内存存款和储蓄布尔和小数值

对于里边使用的布尔值和小整数,BitVector32 比 BitArray 更有效。 BitArray 能够按供给非常地强盛,但它有内部存款和储蓄器和天性方面包车型客车系统开辟,那是类实例所供给的。 相比之下,BitVector32 只行使 三玖位。

BitVector32 结构得以设置成富含小卡尺头的若干节或带有布尔值的比较多位标识,但不能同偶然间蕴含两个。BitVector32.Section 是 BitVector32 中的窗口,且由微小数量的连天位结合,三翻五次位可以分包 CreateSection 中钦命的最大值。 举个例子,带有最大值 1 的节只由三个位构成,而含有最大值 5 的节由多个位结合。 能够创建带有最大值 1 的 BitVector32.Section 作为布尔值,进而使您能够在同一 BitVector32 中储存整数和布尔值。

BitVector32 不仅能安装为节,也足以安装为位标识,分别有成员能够应用于那二种情形。 举个例子,BitVector32.Item 属性是当作节设置的 BitVector32 的索引器,而 BitVector32.Item 属性是当作位标记设置的BitVector32 的索引器。 CreateMask 创造一雨后春笋屏蔽,这一个屏蔽可用来访谈作为位标记设置的 BitVector32 中的单个位。

在作为节设置的 BitVector32 上应用屏蔽也许会招致敬外的结果。

图片 32图片 33

using System;
using System.Collections.Specialized;


public class SamplesBitVector32  {

   public static void Main()  {

      // Creates and initializes a BitVector32 with all bit flags set to FALSE.
      BitVector32 myBV = new BitVector32( 0 );

      // Creates masks to isolate each of the first five bit flags.
      int myBit1 = BitVector32.CreateMask();
      int myBit2 = BitVector32.CreateMask( myBit1 );
      int myBit3 = BitVector32.CreateMask( myBit2 );
      int myBit4 = BitVector32.CreateMask( myBit3 );
      int myBit5 = BitVector32.CreateMask( myBit4 );

      // Sets the alternating bits to TRUE.
      Console.WriteLine( "Setting alternating bits to TRUE:" );
      Console.WriteLine( "   Initial:         {0}", myBV.ToString() );
      myBV[myBit1] = true;
      Console.WriteLine( "   myBit1 = TRUE:   {0}", myBV.ToString() );
      myBV[myBit3] = true;
      Console.WriteLine( "   myBit3 = TRUE:   {0}", myBV.ToString() );
      myBV[myBit5] = true;
      Console.WriteLine( "   myBit5 = TRUE:   {0}", myBV.ToString() );

   }

}

/*
This code produces the following output.

Setting alternating bits to TRUE:
   Initial:         BitVector32{00000000000000000000000000000000}
   myBit1 = TRUE:   BitVector32{00000000000000000000000000000001}
   myBit3 = TRUE:   BitVector32{00000000000000000000000000000101}
   myBit5 = TRUE:   BitVector32{00000000000000000000000000010101}


*/
BitVector用作节集合

using System;
using System.Collections.Specialized;


public class SamplesBitVector32  {

   public static void Main()  {

      // Creates and initializes a BitVector32.
      BitVector32 myBV = new BitVector32( 0 );

      // Creates four sections in the BitVector32 with maximum values 6, 3, 1, and 15.
      // mySect3, which uses exactly one bit, can also be used as a bit flag.
      BitVector32.Section mySect1 = BitVector32.CreateSection( 6 );
      BitVector32.Section mySect2 = BitVector32.CreateSection( 3, mySect1 );
      BitVector32.Section mySect3 = BitVector32.CreateSection( 1, mySect2 );
      BitVector32.Section mySect4 = BitVector32.CreateSection( 15, mySect3 );

      // Displays the values of the sections.
      Console.WriteLine( "Initial values:" );
      Console.WriteLine( "tmySect1: {0}", myBV[mySect1] );
      Console.WriteLine( "tmySect2: {0}", myBV[mySect2] );
      Console.WriteLine( "tmySect3: {0}", myBV[mySect3] );
      Console.WriteLine( "tmySect4: {0}", myBV[mySect4] );

      // Sets each section to a new value and displays the value of the BitVector32 at each step.
      Console.WriteLine( "Changing the values of each section:" );
      Console.WriteLine( "tInitial:    t{0}", myBV.ToString() );
      myBV[mySect1] = 5;
      Console.WriteLine( "tmySect1 = 5:t{0}", myBV.ToString() );
      myBV[mySect2] = 3;
      Console.WriteLine( "tmySect2 = 3:t{0}", myBV.ToString() );
      myBV[mySect3] = 1;
      Console.WriteLine( "tmySect3 = 1:t{0}", myBV.ToString() );
      myBV[mySect4] = 9;
      Console.WriteLine( "tmySect4 = 9:t{0}", myBV.ToString() );

      // Displays the values of the sections.
      Console.WriteLine( "New values:" );
      Console.WriteLine( "tmySect1: {0}", myBV[mySect1] );
      Console.WriteLine( "tmySect2: {0}", myBV[mySect2] );
      Console.WriteLine( "tmySect3: {0}", myBV[mySect3] );
      Console.WriteLine( "tmySect4: {0}", myBV[mySect4] );

   }

}

View Code

图片 34图片 35

/*
This code produces the following output.

Initial values:
        mySect1: 0
        mySect2: 0
        mySect3: 0
        mySect4: 0
Changing the values of each section:
        Initial:        BitVector32{00000000000000000000000000000000}
        mySect1 = 5:    BitVector32{00000000000000000000000000000101}
        mySect2 = 3:    BitVector32{00000000000000000000000000011101}
        mySect3 = 1:    BitVector32{00000000000000000000000000111101}
        mySect4 = 9:    BitVector32{00000000000000000000001001111101}
New values:
        mySect1: 5
        mySect2: 3
        mySect3: 1
        mySect4: 9

*/

View Code

 

11.不改变的集结

Net提供的不可变集结

ImmutableStack<int> a1 = ImmutableStack<int>.Empty;
ImmutableStack<int> a2 = a1.Push(10);
ImmutableStack<int> a3 = a2.Push(20);
ImmutableStack<int> a4 = a3.Push(30);
ImmutableStack<int> iv3 = a4.Pop(); 

使用Net不可变列表集结有少数要注意的是,当大家Push值时要重复赋值给原变量才正确,因为push后会生成二个新目的,原a1只是旧值:

ImmutableStack<int> a1 = ImmutableStack<int>.Empty;
a1.Push(10); //不正确,a1仍是空值值,push会生成新的栈。
a1 = a1.Push(10); //需要将新栈重新赋值给a1

NET提供的常用数据结构

1.ImmutableStack
2.ImmutableQueue
3.ImmutableList
4.ImmutableHashSet
5.ImmutableSortedSet
6.ImmutableDictionary<K, V>
7.ImmutableSortedDictionary<K, V>

不可变优点

1.集聚分享安全,从不被改造
2.拜候集适那时候候,无需锁集结(线程安全)
3.修改会集不担忧旧集结被更动
4.书写更加精简,函数式风格。 var list = ImmutableList.Empty.Add(10).Add(20).Add(30);
5.保障数据完整性,安全性

不可变对象劣势

不可变自己的亮点便是短处,当每一次对象/集结操作都会回去个新值。而旧值照旧会保留一段时间,那会使内部存款和储蓄器有十分的大开支,也会给GC形成回收担负,质量也比可变集结差的多。

图片 36

 

12.并发集合

线程安全的聚焦可堤防多个线程以互动冲突的章程访问集结

.NET 的System.Collections.Concurrent提供了多少个平平安安的类和意义:

说明
BlockingCollection<T>
ConcurrentBag<T>
ConcurrentDictionary<TKey, TValue>
ConcurrentQueue<T>
ConcurrentStack<T>
OrderablePartitioner<TSource>
Partitioner
Partitioner<TSource>

1)创设管道

将那些并发集结类用于管道,一个任务向多少个会集类写入一些剧情,同期另三个任务从该群集中读取内容

演示中四个职责造成三个管道.
第多个管道,
第1阶段的职务读取文件名,加多到行列,这些职责运营同一时间,
第2等第的天职现已起来从队列中读取文件名并加载它们的程序,结果被写入另三个行列。
第3品级同一时间开动,读取并拍卖第三个种类的剧情,结果被写入二个字典。

第3品级完结,并且内容已被最后管理,字典得到完整结果时,下一阶段才开端。
第4等第从字典中读取内容,转变数据,然后写入队列中
第5等第在项中增添颜色音讯,然后把它们增加到另一个体系中,最终三个等第突显消息。
第4到第6品级也可以并发运营.

图片 37图片 38

class Program
  {
    static void Main(string[] args)
    {
      StartPipeline();
      Console.ReadLine();
    }

    private static async void StartPipeline()
    {
      var fileNames = new BlockingCollection<string>();
      var lines = new BlockingCollection<string>();
      var words = new ConcurrentDictionary<string, int>();
      var items = new BlockingCollection<Info>();
      var coloredItems = new BlockingCollection<Info>();

      Task t1 = PipelineStages.ReadFilenamesAsync(@"../../..", fileNames);
      ConsoleHelper.WriteLine("started stage 1");
      Task t2 = PipelineStages.LoadContentAsync(fileNames, lines);
      ConsoleHelper.WriteLine("started stage 2");
      Task t3 = PipelineStages.ProcessContentAsync(lines, words);
      await Task.WhenAll(t1, t2, t3);
      ConsoleHelper.WriteLine("stages 1, 2, 3 completed");

      Task t4 = PipelineStages.TransferContentAsync(words, items);
      Task t5 = PipelineStages.AddColorAsync(items, coloredItems);
      Task t6 = PipelineStages.ShowContentAsync(coloredItems);
      ConsoleHelper.WriteLine("stages 4, 5, 6 started");

      await Task.WhenAll(t4, t5, t6);

      ConsoleHelper.WriteLine("all stages finished");
    }
  }

Program

图片 39图片 40

public class ConsoleHelper
  {
    private static object syncOutput = new object();

    public static void WriteLine(string message)
    {
      lock (syncOutput)
      {
        Console.WriteLine(message);
      }
    }

    public static void WriteLine(string message, string color)
    {
      lock (syncOutput)
      {
        Console.ForegroundColor = (ConsoleColor)Enum.Parse(typeof(ConsoleColor), color);
        Console.WriteLine(message);
        Console.ResetColor();
      }
    }
  }

ConsoleHelper

图片 41图片 42

public static class PipelineStages
  {
    public static Task ReadFilenamesAsync(string path, BlockingCollection<string> output)
    {
      return Task.Run(() =>
        {
          foreach (string filename in Directory.EnumerateFiles(path, "*.cs", SearchOption.AllDirectories))
          {
            output.Add(filename);
            ConsoleHelper.WriteLine(string.Format("stage 1: added {0}", filename));
          }
          output.CompleteAdding();
        });
    }

    public static async Task LoadContentAsync(BlockingCollection<string> input, BlockingCollection<string> output)
    {
      foreach (var filename in input.GetConsumingEnumerable())
      {
        using (FileStream stream = File.OpenRead(filename))
        {
          var reader = new StreamReader(stream);
          string line = null;
          while ((line = await reader.ReadLineAsync()) != null)
          {
            output.Add(line);
            ConsoleHelper.WriteLine(string.Format("stage 2: added {0}", line));
          }
        }
      }
      output.CompleteAdding();
    }

    public static Task ProcessContentAsync(BlockingCollection<string> input, ConcurrentDictionary<string, int> output)
    {
      return Task.Run(() =>
        {
          foreach (var line in input.GetConsumingEnumerable())
          {
            string[] words = line.Split(' ', ';', 't', '{', '}', '(', ')', ':', ',', '"');
            foreach (var word in words.Where(w => !string.IsNullOrEmpty(w)))
            {
              output.AddOrIncrementValue(word);
              ConsoleHelper.WriteLine(string.Format("stage 3: added {0}", word));
            }
          }
        });
    }

    public static Task TransferContentAsync(ConcurrentDictionary<string, int> input, BlockingCollection<Info> output)
    {
      return Task.Run(() =>
        {
          foreach (var word in input.Keys)
          {
            int value;
            if (input.TryGetValue(word, out value))
            {
              var info = new Info { Word = word, Count = value };
              output.Add(info);
              ConsoleHelper.WriteLine(string.Format("stage 4: added {0}", info));
            }
          }
          output.CompleteAdding();
        });
    }

    public static Task AddColorAsync(BlockingCollection<Info> input, BlockingCollection<Info> output)
    {
      return Task.Run(() =>
        {
          foreach (var item in input.GetConsumingEnumerable())
          {
            if (item.Count > 40)
            {
              item.Color = "Red";
            }
            else if (item.Count > 20)
            {
              item.Color = "Yellow";
            }
            else
            {
              item.Color = "Green";
            }
            output.Add(item);
            ConsoleHelper.WriteLine(string.Format("stage 5: added color {1} to {0}", item, item.Color));
          }
          output.CompleteAdding();
        });
    }

    public static Task ShowContentAsync(BlockingCollection<Info> input)
    {
      return Task.Run(() =>
        {
          foreach (var item in input.GetConsumingEnumerable())
          {
            ConsoleHelper.WriteLine(string.Format("stage 6: {0}", item), item.Color);
          }
        });
    }
  }

PipelineStages

 

2)使用BlockingCollection

第1品级的ReadFilenamesAsync方法,达成了迭代目录文件名。在成就文件名增添后调用output.CompleteAdding();用以通告全数读取器不再等待集结中另外附加的项.若无调用的话,循环中读取器会拉长等待越多的项.

图片 43图片 44

public static Task ReadFilenamesAsync(string path, BlockingCollection<string> output)
    {
      return Task.Run(() =>
        {
          foreach (string filename in Directory.EnumerateFiles(path, "*.cs", SearchOption.AllDirectories))
          {
            output.Add(filename);
            ConsoleHelper.WriteLine(string.Format("stage 1: added {0}", filename));
          }
          output.CompleteAdding();
        });
    }

ReadFilenamesAsync

下一阶段读取文件并将器内容增添到另一个聚众中,由LoadContentAsync方法成功,该方法运用了输入集结传递的文书名,展开文件,把文件中的全数行增加到输出的联谊中。在循环中用输入阻塞会集调用GetConsumingEnumerable()方法,以迭代各样,不利用也是足以的,不过值会迭代当前处境的集纳。不会迭代过后增多的项。

即使在填充集结的同偶然候,使用读取器读取集合,则须求接纳GetConsumingEnumerable()方法获得阻塞集合的枚举器,并非直接迭代集结

图片 45图片 46

public static async Task LoadContentAsync(BlockingCollection<string> input, BlockingCollection<string> output)
    {
      foreach (var filename in input.GetConsumingEnumerable())
      {
        using (FileStream stream = File.OpenRead(filename))
        {
          var reader = new StreamReader(stream);
          string line = null;
          while ((line = await reader.ReadLineAsync()) != null)
          {
            output.Add(line);
            ConsoleHelper.WriteLine(string.Format("stage 2: added {0}", line));
          }
        }
      }
      output.CompleteAdding();
    }

LoadContentAsync

 

3)使用ConcurrentDictionary

图片 47

图片 48图片 49

public static Task ProcessContentAsync(BlockingCollection<string> input, ConcurrentDictionary<string, int> output)
    {
      return Task.Run(() =>
        {
          foreach (var line in input.GetConsumingEnumerable())
          {
            string[] words = line.Split(' ', ';', 't', '{', '}', '(', ')', ':', ',', '"');
            foreach (var word in words.Where(w => !string.IsNullOrEmpty(w)))
            {
              output.AddOrIncrementValue(word);
              ConsoleHelper.WriteLine(string.Format("stage 3: added {0}", word));
            }
          }
        });
    }

ProcessContentAsync

图片 50

图片 51图片 52

public static class ConcurrentDictionaryExtension
  {
    public static void AddOrIncrementValue(this ConcurrentDictionary<string, int> dict, string key)
    {
      bool success = false;
      while (!success)
      {
        int value;
        if (dict.TryGetValue(key, out value))
        {
          if (dict.TryUpdate(key, value   1, value))
          {
            success = true;
          }
        }
        else
        {
          if (dict.TryAdd(key, 1))
          {
            success = true;
          }
        }
      }
    }
  }

ConcurrentDictionaryExtension

图片 53

在成功第一个级次后,第4到6阶段也足以并行运营,TransferContentAsync从字典中获取数据,实行类型调换,输出到BlockingCollection<string>中

图片 54图片 55

 public static Task ProcessContentAsync(BlockingCollection<string> input, ConcurrentDictionary<string, int> output)
    {
      return Task.Run(() =>
        {
          foreach (var line in input.GetConsumingEnumerable())
          {
            string[] words = line.Split(' ', ';', 't', '{', '}', '(', ')', ':', ',', '"');
            foreach (var word in words.Where(w => !string.IsNullOrEmpty(w)))
            {
              output.AddOrIncrementValue(word);
              ConsoleHelper.WriteLine(string.Format("stage 3: added {0}", word));
            }
          }
        });
    }

    public static Task TransferContentAsync(ConcurrentDictionary<string, int> input, BlockingCollection<Info> output)
    {
      return Task.Run(() =>
        {
          foreach (var word in input.Keys)
          {
            int value;
            if (input.TryGetValue(word, out value))
            {
              var info = new Info { Word = word, Count = value };
              output.Add(info);
              ConsoleHelper.WriteLine(string.Format("stage 4: added {0}", info));
            }
          }
          output.CompleteAdding();
        });
    }

    public static Task AddColorAsync(BlockingCollection<Info> input, BlockingCollection<Info> output)
    {
      return Task.Run(() =>
        {
          foreach (var item in input.GetConsumingEnumerable())
          {
            if (item.Count > 40)
            {
              item.Color = "Red";
            }
            else if (item.Count > 20)
            {
              item.Color = "Yellow";
            }
            else
            {
              item.Color = "Green";
            }
            output.Add(item);
            ConsoleHelper.WriteLine(string.Format("stage 5: added color {1} to {0}", item, item.Color));
          }
          output.CompleteAdding();
        });
    }

    public static Task ShowContentAsync(BlockingCollection<Info> input)
    {
      return Task.Run(() =>
        {
          foreach (var item in input.GetConsumingEnumerable())
          {
            ConsoleHelper.WriteLine(string.Format("stage 6: {0}", item), item.Color);
          }
        });
    }

View Code

 

 

13.性能

集合的措施平常有总体性提醒,给出大写O记录操作时间。

图片 56

O(1)表示无论是集合中有微微多少项,那几个操作必要的时日都不改变。
O(n)代表对此集结实施二个操作须求的平地风波在最坏景况时是N.
O(log n)表示操作需求的时辰随会集瓜月素的加码而增添

图片 57

1. 达成原理

  ConcurrentHashMap使用分段锁技艺,将数据分为一段一段的存款和储蓄,然后给每一段数据配一把锁,当贰个线程占用锁访谈在那之中一个段数据的时候,别的段的数量也能被其他线程访问,能够落到实处真正的面世访问。如下图是ConcurrentHashMap的内部结构图:
图片 58
它把区间遵照并发品级(concurrentLevel),分成了几个segment。暗中同意情形下里面按并发等级为16来创建。对于每种segment的体量,默许情形也是16。当然并发等第(concurrentLevel)和种种段(segment)的起先容积皆以足以经过构造函数设定的。

二、ConcurrentBag类

ConcurrentBag<T>实现了IProducerConsumerCollection<T>接口,该接口首要用于生产者花费者形式下,可以看到该类基本就是为生产花费者情势定制的。然后还落到实处了健康的IReadOnlyCollection<T>类,达成了此类就必要贯彻IEnumerable<T>、IEnumerable、 ICollection类。

ConcurrentBag<T>对外提供的法子未有List<T>那么多,可是同样有Enumerable落成的强盛方法。类自己提供的章程如下所示。

名称 说明
Add 将对象添加到 ConcurrentBag 中。
CopyTo 从指定数组索引开始,将 ConcurrentBag 元素复制到现有的一维 Array 中。
Equals(Object) 确定指定的 Object 是否等于当前的 Object。 (继承自 Object。)
Finalize 允许对象在“垃圾回收”回收之前尝试释放资源并执行其他清理操作。 (继承自 Object。)
GetEnumerator 返回循环访问 ConcurrentBag 的枚举器。
GetHashCode 用作特定类型的哈希函数。 (继承自 Object。)
GetType 获取当前实例的 Type。 (继承自 Object。)
MemberwiseClone 创建当前 Object 的浅表副本。 (继承自 Object。)
ToArray 将 ConcurrentBag 元素复制到新数组。
ToString 返回表示当前对象的字符串。 (继承自 Object。)
TryPeek 尝试从 ConcurrentBag 返回一个对象但不移除该对象。
TryTake 尝试从 ConcurrentBag 中移除并返回对象。

Hashtable

HashMap和HashTab的异同

1.HashTable 依照 Dictionary 类,而 HashMap 是遵照AbstractMap。Dictionary 是另外可将键映射到相应值的类的悬空父类,而 AbstractMap 是依靠 Map 接口的兑现,它以最大限度地回退完结此接口所需的专门的学问。

2.HashMap 的 key 和 value 都同意为 null,而 Hashtable 的 key 和 value 都不允许为 null。HashMap 际遇 key 为 null 的时候,调用 putForNullKey 方法开展处理,而对 value 未有拍卖;Hashtable际遇 null,直接回到 NullPointerException。

3.Hashtable 艺术是联合签字,而HashMap则不是。大家得以看一下源码,Hashtable 中的差不离全部的 public 的格局都以 synchronized 的,而有一点措施也是在中间通过 synchronized 代码块来落到实处。所以有人平日都提出一旦是关系到二十八线程同步时使用 HashTable,未有涉及就动用 HashMap,不过在 Collections 类中留存贰个静态方法:synchronizedMap(),该办法创立了二个线程安全的 Map 对象,并把它充作二个包裹的对象来回到。

非泛型类集结

泛型集合类是在.NET2.0的时候出来的,也等于说在1.0的时候是未曾如此低价的东西的。今后基本上大家早就不使用这几个集结类了,除非在做一些和老代码保持兼容的行事的时候。来寻访1.0时日的.NET技术员们都有哪些集结类能够用。

ArraryList后来被List<T>替代。

HashTable 后来被Dictionary<TKey,TValue>替代。 
Queue 后来被Queue<T>替代。 
SortedList 后来被SortedList<T>替代。 
Stack 后来被Stack<T>替代。

2. 源码解读

  ConcurrentHashMap中主要性实体类便是多少个:ConcurrentHashMap(整个Hash表),Segment(当作锁的角色,每一个Segment 对象守护整个散列表的某三个段的几何个桶),HashEntry(键值节点),对应上边的图能够看来之间的涉及。

三、 ConcurrentBag线程安全达成原理

LinkedHashMap

LinkedHashMap是Hash表和链表的落实,何况依据着双向链表保险了迭代相继是插入的依次
骨子里 LinkedHashMap 大概和 HashMap 同样:从技能上来讲,不相同的是它定义了三个 Entry<K,V> header,那些header 不是位于 Table 里,它是极度独立出来的。LinkedHashMap 通过持续 hashMap 中的 Entry<K,V>,并加多两天性子 Entry<K,V> before,after,和 header 结合起来组成一个双向链表,来促成按插入顺序或访问顺序排序。
HashMap,LinkedHashMap,TreeMap的区别

线程安全的会集类

ConcurrentQueue 线程安全版本的Queue 
ConcurrentStack线程安全版本的Stack 
ConcurrentBag线程安全的对象会集 
ConcurrentDictionary线程安全的Dictionary 
BlockingCollection

 

HashEntry 类

  在 HashEntry 类中,key,hash 和 next 域都被声称为 final 型,value 域被声称为 volatile 型。

 static final class HashEntry<K,V> {  
     final K key;  
     final int hash;  
     volatile V value;  
     final HashEntry<K,V> next;  
 } 

  在 ConcurrentHashMap 中,在散列时一旦发生“冲突”,将动用“链地址法”来管理“冲突”:把“冲突”的 HashEntry 对象链接成二个单向链表。由于 HashEntry 的 next 域为 final 型,所以新节点只好在链表的表头处插入。 下图是在多个空桶中逐个插入 A,B,C 多少个 HashEntry 对象后的结构图:
图片 59
瞩目:由于只可以在表头插入,所以链表中节点的各种和插入的逐一相反。其实,哪怕next不是final的,也应该从表头插入,因为从表尾插入的话,首先需求遍历到表尾,然后才具插入节点,复杂度O(n)。

1. ConcurrentBag的个人字段

ConcurrentBag线程安全完成重视是通过它的多寡存款和储蓄的结商谈细颗粒度的锁。

   public class ConcurrentBag<T> : IProducerConsumerCollection<T>, IReadOnlyCollection<T>
    {
        // ThreadLocalList对象包含每个线程的数据
        ThreadLocal<ThreadLocalList> m_locals;

        // 这个头指针和尾指针指向中的第一个和最后一个本地列表,这些本地列表分散在不同线程中
        // 允许在线程局部对象上枚举
        volatile ThreadLocalList m_headList, m_tailList;

        // 这个标志是告知操作线程必须同步操作
        // 在GlobalListsLock 锁中 设置
        bool m_needSync;

}

主要推荐大家来看它阐明的个人字段,当中须要在乎的是集合的数额是寄存在在ThreadLocal线程当地存款和储蓄中的。也正是说访问它的各类线程会维护二个协调的集合数据列表,两个凑合中的数据只怕会存放在差别线程的本地存款和储蓄空间中,所以如果线程访谈自身当地存款和储蓄的对象,那么是不曾难题的,那正是贯彻线程安全的率先层,动用线程本地存款和储蓄数据

下一场能够见到ThreadLocalList m_headList, m_tailList;本条是贮存在着地面列表对象的头指针和尾指针,通过那多个指针,大家就足以经过遍历的点子来拜候具备地方列表。它接纳volatile修饰,不容许线程举办业地缓存,种种线程的读写都以平昔操作在分享内部存款和储蓄器上,那就保证了变量始终具备一致性。任何线程在其余时刻开展读写操作均是新型值。对于volatile修饰符,感谢自己是工程师提出描述不当。

最终又定义了三个证明,这些标识告知操作线程必需实行同步操作,那是兑现了三个细颗粒度的锁,因为独有在多少个标准化知足的情形下才必要打开线程同步。

LinkedHashSet

  • LinkedHashSet 是 Set 的三个现实贯彻,其保险着三个周转于具备条条框框的双重链接列表。此链接列表定义了迭代逐个,该迭代顺序可为插入顺序或是访问顺序。
  • LinkedHashSet 承继与 HashSet,何况个中间是因此 LinkedHashMap 来贯彻的。有一点点类似于大家在此之前说的LinkedHashMap 其内部是基于 Hashmap 达成平等,不过依然有一丝丝不相同的(具体的界别我们可以本身去考虑一下)。
  • 万一大家供给迭代的依次为插入顺序或然访问顺序,那么 LinkedHashSet 是亟需你首先考虑的。

Segment 类

  Segment 类承袭于 ReentrantLock 类,从而每种segment都得以视作三个锁。各类 Segment 对象用来守护其(成员对象 table 中)包罗的若干个桶。
  table 是二个由 HashEntry 对象组成的数组。table 数组的每三个数组成员便是散列表的贰个桶。

static final class Segment<K,V> extends ReentrantLock implements Serializable { 
       /** 
        * 在本 segment 范围内,包含的 HashEntry 元素的个数
        * 该变量被声明为 volatile 型
        */ 
       transient volatile int count; 

       /** 
        * table 被更新的次数
        */ 
       transient int modCount; 

       /** 
        * 当 table 中包含的 HashEntry 元素的个数超过本变量值时,触发 table 的再散列
        */ 
       transient int threshold; 

       /** 
        * table 是由 HashEntry 对象组成的数组
        */ 
       transient volatile HashEntry<K,V>[] table; 

       final float loadFactor; 

       Segment(int initialCapacity, float lf) { 
           loadFactor = lf; 
           setTable(HashEntry.<K,V>newArray(initialCapacity)); 
       } 

       void setTable(HashEntry<K,V>[] newTable) { 
           threshold = (int)(newTable.length * loadFactor); 
           table = newTable; 
       } 

       /** 
        * 根据 key 的散列值,找到 table 中对应的那个桶(table 数组的某个数组成员)
        */ 
       HashEntry<K,V> getFirst(int hash) { 
           HashEntry<K,V>[] tab = table; 
           return tab[hash & (tab.length - 1)]; 
       } 
}

2. 用来数据存储的TrehadLocalList类

接下去大家来看一下ThreadLocalList类的构造,该类正是实际上存款和储蓄了数额的岗位。实际上它是利用双向链表这种布局进行数据存款和储蓄。

[Serializable]
// 构造了双向链表的节点
internal class Node
{
    public Node(T value)
    {
        m_value = value;
    }
    public readonly T m_value;
    public Node m_next;
    public Node m_prev;
}

/// <summary>
/// 集合操作类型
/// </summary>
internal enum ListOperation
{
    None,
    Add,
    Take
};

/// <summary>
/// 线程锁定的类
/// </summary>
internal class ThreadLocalList
{
    // 双向链表的头结点 如果为null那么表示链表为空
    internal volatile Node m_head;

    // 双向链表的尾节点
    private volatile Node m_tail;

    // 定义当前对List进行操作的种类 
    // 与前面的 ListOperation 相对应
    internal volatile int m_currentOp;

    // 这个列表元素的计数
    private int m_count;

    // The stealing count
    // 这个不是特别理解 好像是在本地列表中 删除某个Node 以后的计数
    internal int m_stealCount;

    // 下一个列表 可能会在其它线程中
    internal volatile ThreadLocalList m_nextList;

    // 设定锁定是否已进行
    internal bool m_lockTaken;

    // The owner thread for this list
    internal Thread m_ownerThread;

    // 列表的版本,只有当列表从空变为非空统计是底层
    internal volatile int m_version;

    /// <summary>
    /// ThreadLocalList 构造器
    /// </summary>
    /// <param name="ownerThread">拥有这个集合的线程</param>
    internal ThreadLocalList(Thread ownerThread)
    {
        m_ownerThread = ownerThread;
    }
    /// <summary>
    /// 添加一个新的item到链表首部
    /// </summary>
    /// <param name="item">The item to add.</param>
    /// <param name="updateCount">是否更新计数.</param>
    internal void Add(T item, bool updateCount)
    {
        checked
        {
            m_count  ;
        }
        Node node = new Node(item);
        if (m_head == null)
        {
            Debug.Assert(m_tail == null);
            m_head = node;
            m_tail = node;
            m_version  ; // 因为进行初始化了,所以将空状态改为非空状态
        }
        else
        {
            // 使用头插法 将新的元素插入链表
            node.m_next = m_head;
            m_head.m_prev = node;
            m_head = node;
        }
        if (updateCount) // 更新计数以避免此添加同步时溢出
        {
            m_count = m_count - m_stealCount;
            m_stealCount = 0;
        }
    }

    /// <summary>
    /// 从列表的头部删除一个item
    /// </summary>
    /// <param name="result">The removed item</param>
    internal void Remove(out T result)
    {
        // 双向链表删除头结点数据的流程
        Debug.Assert(m_head != null);
        Node head = m_head;
        m_head = m_head.m_next;
        if (m_head != null)
        {
            m_head.m_prev = null;
        }
        else
        {
            m_tail = null;
        }
        m_count--;
        result = head.m_value;

    }

    /// <summary>
    /// 返回列表头部的元素
    /// </summary>
    /// <param name="result">the peeked item</param>
    /// <returns>True if succeeded, false otherwise</returns>
    internal bool Peek(out T result)
    {
        Node head = m_head;
        if (head != null)
        {
            result = head.m_value;
            return true;
        }
        result = default(T);
        return false;
    }

    /// <summary>
    /// 从列表的尾部获取一个item
    /// </summary>
    /// <param name="result">the removed item</param>
    /// <param name="remove">remove or peek flag</param>
    internal void Steal(out T result, bool remove)
    {
        Node tail = m_tail;
        Debug.Assert(tail != null);
        if (remove) // Take operation
        {
            m_tail = m_tail.m_prev;
            if (m_tail != null)
            {
                m_tail.m_next = null;
            }
            else
            {
                m_head = null;
            }
            // Increment the steal count
            m_stealCount  ;
        }
        result = tail.m_value;
    }


    /// <summary>
    /// 获取总计列表计数, 它不是线程安全的, 如果同时调用它, 则可能提供不正确的计数
    /// </summary>
    internal int Count
    {
        get
        {
            return m_count - m_stealCount;
        }
    }
}

从地方的代码中大家得以尤其证实此前的观念,正是ConcurentBag<T>在一个线程中存放数据时,使用的是双向链表ThreadLocalList贯彻了一组对链表增加和删除改查的章程。

ArrayList

底层选拔Object[]数组来兑现
ArrayList 能够明白为动态数组,用 MSDN 中的说法,正是 Array 的复杂版本。与 Java 中的数组相比较,它的容积能动态增进。ArrayList 是 List 接口的可变数组的贯彻。完毕了独具可选列表操作,并同意包罗 null 在内的具备因素。除了完结 List 接口外,此类还提供一些形式来操作内部用来存款和储蓄列表的数组的轻重缓急。(此类大概上平等 Vector 类,除了此类是分裂台的。)

ConcurrentHashMap 类

public class ConcurrentHashMap<K, V> extends AbstractMap<K, V> implements ConcurrentMap<K, V>, Serializable { 

   /** 
    * 散列表的默认初始容量为 16,即初始默认为 16 段
    */ 
   static final int DEFAULT_INITIAL_CAPACITY= 16; 
   static final float DEFAULT_LOAD_FACTOR= 0.75f; 

   /** 
    * 散列表的默认并发级别为 16。该值表示当前更新线程的估计数,在构造函数中没有指定这个参数时,使用本参数
    */ 
   static final int DEFAULT_CONCURRENCY_LEVEL= 16; 

   /** 
    * segments 的掩码值
    * key 的散列码的高位用来选择具体的 segment 
    */ 
   final int segmentMask; 

   /** 
    * 偏移量
    */ 
   final int segmentShift; 

   /** 
    * 由 Segment 对象组成的数组
    */ 
   final Segment<K,V>[] segments; 

   /** 
    * 创建一个带有指定初始容量、负载因子和并发级别的新的空散列表
    */ 
   public ConcurrentHashMap(int initialCapacity,  float loadFactor, int concurrencyLevel) { 
       if(!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0) 
           throw new IllegalArgumentException(); 

       if(concurrencyLevel > MAX_SEGMENTS) 
           concurrencyLevel = MAX_SEGMENTS; 

       // 寻找最佳匹配参数(不小于给定参数的最接近的 2 次幂) 
       int sshift = 0; 
       int ssize = 1; 
       while(ssize < concurrencyLevel) { 
             sshift; 
           ssize <<= 1; 
       } 
       segmentShift = 32 - sshift;       // 偏移量值
       segmentMask = ssize - 1;           // 掩码值 
       this.segments = Segment.newArray(ssize);   // 创建数组

       if (initialCapacity > MAXIMUM_CAPACITY) 
           initialCapacity = MAXIMUM_CAPACITY; 
       int c = initialCapacity / ssize; 
       if(c * ssize < initialCapacity) 
             c; 
       int cap = 1; 
       while(cap < c) 
           cap <<= 1; 

       // 依次遍历每个数组元素
       for(int i = 0; i < this.segments.length;   i) 
           // 初始化每个数组元素引用的 Segment 对象
          this.segments[i] = new Segment<K,V>(cap, loadFactor); 
   } 

   /** 
    * 创建一个带有默认初始容量 (16)、默认加载因子 (0.75) 和 默认并发级别 (16) 的空散列表。
    */ 
   public ConcurrentHashMap() { 
       this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL); 
}

3. ConcurrentBag落到实处新增美成分

接下去大家看一看ConcurentBag<T>是怎么新增港成分的。

/// <summary>
/// 尝试获取无主列表,无主列表是指线程已经被暂停或者终止,但是集合中的部分数据还存储在那里
/// 这是避免内存泄漏的方法
/// </summary>
/// <returns></returns>
private ThreadLocalList GetUnownedList()
{
    //此时必须持有全局锁
    Contract.Assert(Monitor.IsEntered(GlobalListsLock));

    // 从头线程列表开始枚举 找到那些已经被关闭的线程
    // 将它所在的列表对象 返回
    ThreadLocalList currentList = m_headList;
    while (currentList != null)
    {
        if (currentList.m_ownerThread.ThreadState == System.Threading.ThreadState.Stopped)
        {
            currentList.m_ownerThread = Thread.CurrentThread; // the caller should acquire a lock to make this line thread safe
            return currentList;
        }
        currentList = currentList.m_nextList;
    }
    return null;
}
/// <summary>
/// 本地帮助方法,通过线程对象检索线程线程本地列表
/// </summary>
/// <param name="forceCreate">如果列表不存在,那么创建新列表</param>
/// <returns>The local list object</returns>
private ThreadLocalList GetThreadList(bool forceCreate)
{
    ThreadLocalList list = m_locals.Value;

    if (list != null)
    {
        return list;
    }
    else if (forceCreate)
    {
        // 获取用于更新操作的 m_tailList 锁
        lock (GlobalListsLock)
        {
            // 如果头列表等于空,那么说明集合中还没有元素
            // 直接创建一个新的
            if (m_headList == null)
            {
                list = new ThreadLocalList(Thread.CurrentThread);
                m_headList = list;
                m_tailList = list;
            }
            else
            {
               // ConcurrentBag内的数据是以双向链表的形式分散存储在各个线程的本地区域中
                // 通过下面这个方法 可以找到那些存储有数据 但是已经被停止的线程
                // 然后将已停止线程的数据 移交到当前线程管理
                list = GetUnownedList();
                // 如果没有 那么就新建一个列表 然后更新尾指针的位置
                if (list == null)
                {
                    list = new ThreadLocalList(Thread.CurrentThread);
                    m_tailList.m_nextList = list;
                    m_tailList = list;
                }
            }
            m_locals.Value = list;
        }
    }
    else
    {
        return null;
    }
    Debug.Assert(list != null);
    return list;
}
/// <summary>
/// Adds an object to the <see cref="ConcurrentBag{T}"/>.
/// </summary>
/// <param name="item">The object to be added to the
/// <see cref="ConcurrentBag{T}"/>. The value can be a null reference
/// (Nothing in Visual Basic) for reference types.</param>
public void Add(T item)
{
    // 获取该线程的本地列表, 如果此线程不存在, 则创建一个新列表 (第一次调用 add)
    ThreadLocalList list = GetThreadList(true);
    // 实际的数据添加操作 在AddInternal中执行
    AddInternal(list, item);
}

/// <summary>
/// </summary>
/// <param name="list"></param>
/// <param name="item"></param>
private void AddInternal(ThreadLocalList list, T item)
{
    bool lockTaken = false;
    try
    {
        #pragma warning disable 0420
        Interlocked.Exchange(ref list.m_currentOp, (int)ListOperation.Add);
        #pragma warning restore 0420
        // 同步案例:
        // 如果列表计数小于两个, 因为是双向链表的关系 为了避免与任何窃取线程发生冲突 必须获取锁
        // 如果设置了 m_needSync, 这意味着有一个线程需要冻结包 也必须获取锁
        if (list.Count < 2 || m_needSync)
        {
            // 将其重置为None 以避免与窃取线程的死锁
            list.m_currentOp = (int)ListOperation.None;
            // 锁定当前对象
            Monitor.Enter(list, ref lockTaken);
        }
        // 调用 ThreadLocalList.Add方法 将数据添加到双向链表中
        // 如果已经锁定 那么说明线程安全  可以更新Count 计数
        list.Add(item, lockTaken);
    }
    finally
    {
        list.m_currentOp = (int)ListOperation.None;
        if (lockTaken)
        {
            Monitor.Exit(list);
        }
    }
}

从下面代码中,大家得以很掌握的知情Add()措施是怎样运行的,在那之中的严重性正是GetThreadList()办法,通过该措施能够得到当前线程的多少存款和储蓄列表对象,假诺不设有数量存款和储蓄列表,它会活动创建或然经过GetUnownedList()主意来找寻那么些被终止不过还蕴藏有数量列表的线程,然后将数据列表重返给当下线程中,幸免了内部存款和储蓄器泄漏。

在数码拉长的进度中,落成了细颗粒度的lock协助举办锁,所以质量会相当高。删除和别的操作与新扩充类似,本文不再赘述。

LinkedList

LinkedList 是依照链表结构完毕,所以在类中带有了 first 和 last 四个指针(Node)。Node 中含有了上贰个节点和下贰个节点的引用,那样就重组了双向的链表。每一个 Node 只好知道自身的前二个节点和后二个节点,但对此链表来讲,那早就丰富了。

put方法

  首先,依据 key 总计出相应的 hash 值:

public V put(K key, V value) { 
       if (value == null)          //ConcurrentHashMap 中不允许用 null 作为映射值
           throw new NullPointerException(); 
       int hash = hash(key.hashCode());        // 计算键对应的散列码
       // 根据散列码找到对应的 Segment 
       return segmentFor(hash).put(key, hash, value, false); 
}

  然后,依照 hash 值找到相应的Segment 对象:

/** 
    * 使用 key 的散列码来得到 segments 数组中对应的 Segment 
    */ 
final Segment<K,V> segmentFor(int hash) { 
   // 将散列值右移 segmentShift 个位,并在高位填充 0 ,然后把得到的值与 segmentMask 相“与”,从而得到 hash 值对应的 segments 数组的下标值,最后根据下标值返回散列码对应的 Segment 对象
       return segments[(hash >>> segmentShift) & segmentMask]; 
}

  最后,在这里个 Segment 中进行实际的 put 操作:

    V put(K key, int hash, V value, boolean onlyIfAbsent) { 
           lock();  // 加锁,这里是锁定某个 Segment 对象而非整个 ConcurrentHashMap 
           try { 
               int c = count; 

               if (c   > threshold)     // 如果超过再散列的阈值
                   rehash();              // 执行再散列,table 数组的长度将扩充一倍

               HashEntry<K,V>[] tab = table; 
               int index = hash & (tab.length - 1); 
               // 找到散列值对应的具体的那个桶
               HashEntry<K,V> first = tab[index]; 

               HashEntry<K,V> e = first; 
               while (e != null && (e.hash != hash || !key.equals(e.key))) 
                   e = e.next; 

               V oldValue; 
               if (e != null) {            // 如果键值对已经存在
                   oldValue = e.value; 
                   if (!onlyIfAbsent) 
                       e.value = value;    // 替换 value 值
               } 
               else {                        // 键值对不存在 
                   oldValue = null; 
                     modCount;         // 要添加新节点到链表中,所以 modCont 要加 1  
                   // 创建新节点,并添加到链表的头部 
                   tab[index] = new HashEntry<K,V>(key, hash, first, value); 
                   count = c;               // 写 count 变量
               } 
               return oldValue; 
           } finally { 
               unlock();                     // 解锁
           } 
       }

  注意:这里的加锁操作是针对某些具体的 Segment,锁定的是该 Segment 并不是百分之百 ConcurrentHashMap。因为插入键 / 值对操作只是在这里个 Segment 满含的有个别桶中成就,不供给锁定任何ConcurrentHashMap。此时,其余写线程对此外15 个Segment 的加锁并不会因为脚下线程对这几个 Segment 的加锁而堵塞。相同的时间,全部读线程大约不会因本线程的加锁而围堵(除非读线程刚好读到那一个Segment 中有些 HashEntry 的 value 域的值为 null,此时内需加锁后再也读取该值)。
  绝比较于 HashTable 和由一块包装器包装的 HashMap每一遍只可以有多个线程推行读或写操作,ConcurrentHashMap 在出现访谈质量上有了质的加强。在理想图景下,ConcurrentHashMap 能够支撑 16 个线程推行并发写操作(如若现身等第设置为 16),及随便数量线程的读操作。

4. ConcurrentBag 怎么着落到实处迭代器格局

看完上边的代码后,作者很诧异ConcurrentBag<T>是怎么样落到实处IEnumerator来完毕迭代访谈的,因为ConcurrentBag<T>是经过分流在不相同线程中的ThreadLocalList来囤积数据的,那么在贯彻迭代器方式时,进度会比较复杂。

末尾再查看了源码之后,开采ConcurrentBag<T>为了兑现迭代器形式,将分在分歧线程中的数据全都存到贰个List<T>晤面中,然后回到了该副本的迭代器。所以每便访谈迭代器,它都会新建三个List<T>的别本,那样纵然浪费了必然的积攒空间,但是逻辑上极其简便易行了。

/// <summary>
/// 本地帮助器方法释放所有本地列表锁
/// </summary>
private void ReleaseAllLocks()
{
    // 该方法用于在执行线程同步以后 释放掉所有本地锁
    // 通过遍历每个线程中存储的 ThreadLocalList对象 释放所占用的锁
    ThreadLocalList currentList = m_headList;
    while (currentList != null)
    {

        if (currentList.m_lockTaken)
        {
            currentList.m_lockTaken = false;
            Monitor.Exit(currentList);
        }
        currentList = currentList.m_nextList;
    }
}

/// <summary>
/// 从冻结状态解冻包的本地帮助器方法
/// </summary>
/// <param name="lockTaken">The lock taken result from the Freeze method</param>
private void UnfreezeBag(bool lockTaken)
{
    // 首先释放掉 每个线程中 本地变量的锁
    // 然后释放全局锁
    ReleaseAllLocks();
    m_needSync = false;
    if (lockTaken)
    {
        Monitor.Exit(GlobalListsLock);
    }
}

/// <summary>
/// 本地帮助器函数等待所有未同步的操作
/// </summary>
private void WaitAllOperations()
{
    Contract.Assert(Monitor.IsEntered(GlobalListsLock));

    ThreadLocalList currentList = m_headList;
    // 自旋等待 等待其它操作完成
    while (currentList != null)
    {
        if (currentList.m_currentOp != (int)ListOperation.None)
        {
            SpinWait spinner = new SpinWait();
            // 有其它线程进行操作时,会将cuurentOp 设置成 正在操作的枚举
            while (currentList.m_currentOp != (int)ListOperation.None)
            {
                spinner.SpinOnce();
            }
        }
        currentList = currentList.m_nextList;
    }
}

/// <summary>
/// 本地帮助器方法获取所有本地列表锁
/// </summary>
private void AcquireAllLocks()
{
    Contract.Assert(Monitor.IsEntered(GlobalListsLock));

    bool lockTaken = false;
    ThreadLocalList currentList = m_headList;

    // 遍历每个线程的ThreadLocalList 然后获取对应ThreadLocalList的锁
    while (currentList != null)
    {
        // 尝试/最后 bllock 以避免在获取锁和设置所采取的标志之间的线程港口
        try
        {
            Monitor.Enter(currentList, ref lockTaken);
        }
        finally
        {
            if (lockTaken)
            {
                currentList.m_lockTaken = true;
                lockTaken = false;
            }
        }
        currentList = currentList.m_nextList;
    }
}

/// <summary>
/// Local helper method to freeze all bag operations, it
/// 1- Acquire the global lock to prevent any other thread to freeze the bag, and also new new thread can be added
/// to the dictionary
/// 2- Then Acquire all local lists locks to prevent steal and synchronized operations
/// 3- Wait for all un-synchronized operations to be done
/// </summary>
/// <param name="lockTaken">Retrieve the lock taken result for the global lock, to be passed to Unfreeze method</param>
private void FreezeBag(ref bool lockTaken)
{
    Contract.Assert(!Monitor.IsEntered(GlobalListsLock));

    // 全局锁定可安全地防止多线程调用计数和损坏 m_needSync
    Monitor.Enter(GlobalListsLock, ref lockTaken);

    // 这将强制同步任何将来的添加/执行操作
    m_needSync = true;

    // 获取所有列表的锁
    AcquireAllLocks();

    // 等待所有操作完成
    WaitAllOperations();
}

/// <summary>
/// 本地帮助器函数返回列表中的包项, 这主要由 CopyTo 和 ToArray 使用。
/// 这不是线程安全, 应该被称为冻结/解冻袋块
/// 本方法是私有的 只有使用 Freeze/UnFreeze之后才是安全的 
/// </summary>
/// <returns>List the contains the bag items</returns>
private List<T> ToList()
{
    Contract.Assert(Monitor.IsEntered(GlobalListsLock));
    // 创建一个新的List
    List<T> list = new List<T>();
    ThreadLocalList currentList = m_headList;
    // 遍历每个线程中的ThreadLocalList 将里面的Node的数据 添加到list中
    while (currentList != null)
    {
        Node currentNode = currentList.m_head;
        while (currentNode != null)
        {
            list.Add(currentNode.m_value);
            currentNode = currentNode.m_next;
        }
        currentList = currentList.m_nextList;
    }

    return list;
}

/// <summary>
/// Returns an enumerator that iterates through the <see
/// cref="ConcurrentBag{T}"/>.
/// </summary>
/// <returns>An enumerator for the contents of the <see
/// cref="ConcurrentBag{T}"/>.</returns>
/// <remarks>
/// The enumeration represents a moment-in-time snapshot of the contents
/// of the bag.  It does not reflect any updates to the collection after 
/// <see cref="GetEnumerator"/> was called.  The enumerator is safe to use
/// concurrently with reads from and writes to the bag.
/// </remarks>
public IEnumerator<T> GetEnumerator()
{
    // Short path if the bag is empty
    if (m_headList == null)
        return new List<T>().GetEnumerator(); // empty list

    bool lockTaken = false;
    try
    {
        // 首先冻结整个 ConcurrentBag集合
        FreezeBag(ref lockTaken);
        // 然后ToList 再拿到 List的 IEnumerator
        return ToList().GetEnumerator();
    }
    finally
    {
        UnfreezeBag(lockTaken);
    }
}

由地点的代码可以知道晓,为了博取迭代器对象,总共实行了三步关键的操作。

  1. 使用FreezeBag()措施,冻结一切ConcurrentBag<T>会师。因为急需改动集结的List<T>别本,生成别本时期不能够有其余线程改动损坏数据。
  2. ConcurrrentBag<T>生成List<T>副本。因为ConcurrentBag<T>累积数据的措施比较卓绝,间接促成迭代器形式困难,思量到线程安全和逻辑,最棒的不二等秘书籍是生成一个别本。
  3. 完了上述操作之后,就能够动用UnfreezeBag()主意解冻整个集结。

那么FreezeBag()艺术是何许来冻结一切集结的啊?也是分为三步走。

  1. 第一得到全局锁,通过Monitor.Enter(GlobalListsLock, ref lockTaken);与此相类似一条语句,那样任何线程就无法冻结集合。
  2. 接下来拿走具无线程中ThreadLocalList的锁,通过`AcquireAllLocks()方法来遍历获取。那样任何线程就不能够对它进行操作损坏数据。
  3. 伺机已经进去了操作流程线程截至,通过WaitAllOperations()艺术来兑现,该方法会遍历每贰个ThreadLocalList对象的m_currentOp属性,确定保障全数处在None操作。

完了上述流程后,那么正是真正的冰冻了一切ConcurrentBag<T>集结,要解冻的话也类似。在那不再赘言。

ConcurrentHashMap

ConcurrentHashMap 的积极分子变量中,包蕴了三个 Segment 的数组(final Segment<K,V>[] segments;),而 Segment 是 ConcurrentHashMap 的里边类,然后在 Segment 那几个类中,包涵了二个 HashEntry 的数组(transient volatile HashEntry<K,V>[] table;)。而 HashEntry 也是 ConcurrentHashMap 的个中类。HashEntry 中,包括了 key 和 value 以至 next 指针(类似于 HashMap 中 Entry),所以 HashEntry 可以组合一个链表。

就此通俗的讲,ConcurrentHashMap 数据结构为二个 Segment 数组,Segment 的数据结构为 HashEntry 的数组,而 HashEntry 存的是大家的键值对,能够构成链表。

ConcurrentHashMap 的高并发性主要来自于五个方面:

  • 用分离锁完结四个线程间的越来越深档期的顺序的分享访谈。
  • 用 HashEntery 对象的不改变性来下滑实践读操作的线程在遍历链表时期对加锁的要求。
  • 经过对同二个 Volatile 变量的写 / 读访谈,和煦不一致线程间读 / 写操作的内部存款和储蓄器可知性。

应用分别锁,减小了诉求 同三个锁的频率。

通过 HashEntery 对象的不改变性及对同二个 Volatile 变量的读 / 写来协和内存可知性,使得 读操作大非常多时候无需加锁就会不辱义务获取到须求的值。由于散列映射表在实际上利用中山高校部操作都以打响的 读操作,所以 2 和 3 既可以够减去央求同多少个锁的频率,也足以有效削减持有锁的日子。通过减弱央浼同一个锁的频率和尽量收缩持有锁的时光 ,使得 ConcurrentHashMap 的并发性相对于 HashTable 和用联合包装器包装的 HashMap有了质的增高。

get方法

public V get(Object key) {
    int hash = hash(key); // throws NullPointerException if key null
    return segmentFor(hash).get(key, hash);
}

V get(Object key, int hash) { 
           if(count != 0) {       // 首先读 count 变量
               HashEntry<K,V> e = getFirst(hash); 
               while(e != null) { 
                   if(e.hash == hash && key.equals(e.key)) { 
                       V v = e.value; 
                       if(v != null)            
                           return v; 
                       // 如果读到 value 域为 null,说明发生了重排序,加锁后重新读取
                       return readValueUnderLock(e); 
                   } 
                   e = e.next; 
               } 
           } 
           return null; 
       }

  ConcurrentHashMap完全同意多少个读操作并发实行,读操作并无需加锁。关键是用 HashEntry 对象的不改变性来下滑读操作对加锁的急需。只是一口咬定得到的entry的value是还是不是为null,为null时才使用加锁的章程重新去获得。
  在代码清单“HashEntry 类的定义”中大家得以见到,HashEntry 中的 key,hash,next 都宣称为 final 型。那意味,不可能把节点增添到链接的高级中学级和尾部,也不可能在链接的中等和尾巴删除节点。这几个特点能够确定保障:在拜望有些节点时,那几个节点之后的链接不会被更动。这些特点可以大大裁减管理链表时的错综复杂。
上边深入分析在get的时候的线程安全性

四、总结

上边给出一张图,描述了ConcurrentBag<T>是怎么样存款和储蓄数据的。通过各样线程中的ThreadLocal来贯彻线程本地存款和储蓄,每一个线程中都有诸有此类的结构,互不忧愁。然后每种线程中的m_headList接连指向ConcurrentBag<T>的首先个列表,m_tailList本着最终一个列表。列表与列表之间通过m_locals 下的 m_nextList连发,构成二个单链表。

数量存款和储蓄在各种线程的m_locals中,通过Node类构成一个双向链表。
PS: 要注意m_tailListm_headList并不是积存在ThreadLocal中,而是拥有的线程分享一份。

图片 60

如上正是有关ConcurrentBag<T>类的落到实处,小编的部分笔录和深入分析。

1、若是get的长河中另三个线程恰好新添entry

图片 61
  HashEntry 类的 value 域被声称为 volatile 型,Java 的内部存款和储蓄器模型能够确认保障:某些写线程对 value 域的写入登时能够被三翻五次的某部读线程“看”到。在 ConcurrentHashMap 中,不一致敬用 null 作为键和值,当读线程读到某些 HashEntry 的 value 域的值为 null 时,便领会发生了指令重排序现象(注意:volatile变量重排序法规,同时也是优先产生原则的一片段:对三个volatile变量的写操作先行产生于前边对那些变量的读操作,这里的“前面”同样是指时间上的前后相继顺序。所以,在tab[index] = new HashEntry<K,V>(key, hash, first, value);中,大概会出现当前线程获得的newEntry对象是二个未曾完全构造好的目的援用。),要求加锁后再一次读入那个value 值。

小编水平有限,假若不当接待各位商量指正!

附上ConcurrentBag<T>源码地址:戳一戳

2、假使get的长河中另三个线程修改了一个entry的value

  由于对 volatile 变量的可以知道性,写线程对链表的非结构性修改能够被接续不加锁的读线程“见到”。

3、若是get的经过中另三个线程删除了三个entry

  假诺大家的链表成分是:e1-> e2 -> e3 -> e4 大家要刨除 e3以此entry
  因为HashEntry中next的不可变,所以大家鞭长莫及直接把e2的next指向e4,而是将在删除的节点在此之前的节点复制一份,产生新的链表。它的贯彻大概如下图所示:
图片 62
  注意:末了才将数组中对应桶位置的链表替换为新链表(也等于在终极一步替换以前,tab[i]针对的始终是去除此前的链表,详细看上边包车型客车remove方法)。
  借使我们get的也凑巧是e3,大概咱们沿着链表刚找到e1,那时另多少个线程就施行了去除e3的操作,而我辈线程还大概会持续本着旧的链表找到e3重回,那时候大概看见被剔除的数码,不过在高并发蒙受下,这种影响是相当小的。

remove方法

    V remove(Object key, int hash, Object value) { 
           lock();         // 加锁
           try{ 
               int c = count - 1; 
               HashEntry<K,V>[] tab = table; 
               int index = hash & (tab.length - 1); 
               HashEntry<K,V> first = tab[index]; 
               HashEntry<K,V> e = first; 
               while(e != null&& (e.hash != hash || !key.equals(e.key))) 
                   e = e.next; 

               V oldValue = null; 
               if(e != null) { 
                   V v = e.value; 
                   if(value == null|| value.equals(v)) { // 找到要删除的节点
                       oldValue = v; 
                         modCount; 
                       // 所有处于待删除节点之后的节点原样保留在链表中
                       // 所有处于待删除节点之前的节点被克隆(其实是把所有值取出来放到一个新的HashEntry对象中)到新链表中
                       HashEntry<K,V> newFirst = e.next;// 待删节点的后继结点
                       for(HashEntry<K,V> p = first; p != e; p = p.next) 
                           newFirst = new HashEntry<K,V>(p.key, p.hash,  newFirst, p.value); 

                       // 新的头结点是原链表中,删除节点之前的那个节点
                       tab[index] = newFirst; 
                       count = c;      // 写 count 变量
                   } 
               } 
               return oldValue; 
           } finally{ 
               unlock();               // 解锁
           } 
       }

  和 get 操作同样,首先依照散列码找到实际的链表;然后遍历这几个链表找到要刨除的节点;最终把待删除节点之后的有所节点原样保留在新链表中,把待删除节点此前的每一种节点克隆(其实是把全部值抽出来放到四个新的HashEntry对象中)到新链表中;最后才将数组中对应桶地方的链表替换为新链表(也正是在轮换以前,get的一味是剔除此前的链表)。
  上面通过图例来申明 remove 操作。假使写线程推行 remove 操作,要删减链表的 C 节点,另三个读线程同一时间正在遍历这些链表。
图片 63
施行删除之后的新链表
图片 64
B中的next定义为final,不能修改将它指向D,由此C在此以前的享有节点都要重新创建。並且它们在新链表中的链接顺序被反转了。

size方法

  在 ConcurrentHashMap中,每二个 Segment 对象都有二个 count 对象来代表本 Segment 中包蕴的 HashEntry 对象的个数。假如我们要计算整个ConcurrentHashMap里成分的尺寸,就不能够不总括全数Segment里成分的深浅后求和。Segment里的全局变量count是一个volatile变量,那么在二十四线程场景下,大家是否直接把拥有Segment的count相加就能够获取任何ConcurrentHashMap大小了呢?不是的,固然相加时能够收获每一个Segment的count的新星值,可是得到事后或然增加的长河中count产生了改换,那么总计结果就不准了。所以最安全的做法,是在计算size的时候把装有Segment锁定。
  因为在累计count操作进度中,此前增加过的count发生变化的可能率比一点都不大,所以ConcurrentHashMap的做法是先品尝2次经过不锁住Segment的法子来总括各类Segment大小,若是计算的长河中,容器的count产生了扭转,则再使用加锁的点子来总结全体Segment的分寸。
  那么ConcurrentHashMap是什么样决断在计算的时候容器是不是爆发了变化吗?使用modCount变量,在put , remove和clean方法里操作元素前都会将变量modCount进行加1,那么在总括size前后相比较modCount是还是不是产生变化,进而获悉容器的轻重是或不是发生变化。

二、比较

  每一个版本的ConcurrentHashMap差非常的少都有改变,本文注明的JDK6的源码。JDK8比较后面的本子更动最大,上面简要说一下:

  1. 它舍弃了Segment(段锁)的定义,而是启用了一种全新的主意贯彻,利用CAS算法。
  2. 它沿用了与它同不常候期的HashMap版本的合计,底层依旧由“数组” 链表 红黑树的格局思虑,可是为了形成出现,又充实了过多帮助的类,举个例子TreeBin,Traverser等对象内部类。

详细看:

与Hashtable比较

  由于Hashtable无论是读照旧写照旧遍历,都要求得到对象锁,串行操作,因而在多线程情状下质量很差。
  可是ConcurrentHashMap不可能一心代表Hashtable:HashTable的迭代器是强一致性的,而ConcurrentHashMap是弱一致的。其实 ConcurrentHashMap的get,clear,iterator 都以弱一致性的。 Doug Lea 也将这么些推断留给客户自个儿主宰是或不是接纳ConcurrentHashMap。
  弱一致性:不保险数据完全处于一致性状态。比如:

get方法:

也许在get的时候获得多个还没完全构造好的HashEntry对象,导致得到的entry的value为null,此时亟待加锁重新读取。

clear方法

public void clear() {
    for (int i = 0; i < segments.length;   i)
        segments[i].clear();
}

  因为未有大局的锁,在打消完三个segments之后,正在清理下叁个segments的时候,已经清理segments恐怕又被投入了数码,由此clear再次来到的时候,ConcurrentHashMap中是大概存在多少的。因而,clear方法是弱一致的。

迭代器

   java.util 包中的集合类都回去 fail-fast 迭代器,那意味着它们一旦线程在联谊内容中张开迭代时,会集不会更动它的内容。假如fail-fast 迭代器检查实验到在迭代历程中开展了改换操作,那么它会抛出 ConcurrentModificationException。
   ConcurrentHashMap中的迭代器首要不外乎entrySet、keySet、values方法。它们完全同样,这里选用entrySet解释。当大家调用entrySet重回值的iterator方法时,重临的是EntryIterator,在EntryIterator上调用next方法时,最终骨子里调用到了HashIterator.advance()方法,看下这么些艺术:

final void advance() {
    if (nextEntry != null && (nextEntry = nextEntry.next) != null)
        return;

    while (nextTableIndex >= 0) {
        if ( (nextEntry = currentTable[nextTableIndex--]) != null)
            return;
    }

    while (nextSegmentIndex >= 0) {
        Segment<K,V> seg = segments[nextSegmentIndex--];
        if (seg.count != 0) {
            currentTable = seg.table;
            for (int j = currentTable.length - 1; j >= 0; --j) {
                if ( (nextEntry = currentTable[j]) != null) {
                    nextTableIndex = j - 1;
                    return;
                }
            }
        }
    }
}

  在此种迭代格局中,比方大家删除了链表的有个别entry,不过在做到之前,迭代器获得了旧的链表指针,那么就能遍历旧的链表,况兼不会报非常。

本文由pc28.am发布于计算机编程,转载请注明出处:ConcurrentBag的实现原理,Java集合类工作原理及实现

上一篇:多线程基础篇3,你必须掌握的多线程编程 下一篇:没有了
猜你喜欢
热门排行
精彩图文