アドレッシングオープンのThreadLocalMap分析

ハッシュとは何ですか

ハッシュテーブル(ハッシュテーブル)我々は通常、ハッシュテーブルまたはハッシュテーブルを呼び出し、それが添字ランダムに従ったデータにアクセスするためのサポート機能の配列を使用するため、ハッシュテーブルは、実際には配列の延長である、配列が進化。それは、何の配列なしハッシュテーブルを言わないことができます。

例えば、我々は100個のアイテムを持って、それを行う方法を、4桁の数字は、法律ではなく、今は数字を通じて、製品情報への迅速なアクセスをしたいですか?私たちは、アレイ内のこれらの100の製品情報ことができ、このような製品の数の100%の商品配列の添字の位置1、財2の値に値が、値1を取得する方法は、我々は、配列の添字2位を置きます。ように、Kがある数K選手の配列のインデックス位置に。インデックスデータとハッシュ関数を介して商品の数(その100%)に対応するので、我々は、商品情報の数xを照会する必要があるため、我々は同じ方法を使用し、数は配列のインデックスに変換され、次のことができ目標位置に対応するアレイから得られたデータ。これは典型的な思考ハッシュです。

インデックスに従ってランダムアクセスのアレイを支持するために使用される、時間複雑度はO(1)の特徴であるハッシュテーブル:我々は、上記の実施例により支配に来ることができます。ハッシュ関数(製品番号、100%)を介して重要な要素インデックスにマッピングされ、その後、データストア内の目標位置に対応する配列です。我々はキーに従って要素を照会するときに、我々は同じハッシュ関数、鍵変換配列の添字を使用して、目標位置に対応するアレイからのデータを取ります。

OpenAddressed

それはハッシュ(またはハッシュテーブルと呼ばれる)になると、私たちは、HashMapのかのLinkedHashMapに精通しており、今日の主人公はThreadLocalMapあり、それはThreadLocalの内部クラスです。ソースコードを分析するときにThreadLocalそれをバイパスしません。

ハッシュテーブルは関係なく設計が必然的にハッシュ衝突を存在するであろう方法の配列、ハッシュ関数を使用するため。上記実施例1d 2個の商品は1001及び1101である場合、そのデータは、同じ場所に配置されるアレイになり、競合が存在します

原理を分類整理し、名狄利克雷引き出しの原則は、原則を分類整理。一つの簡単な方法は、発現される:ピジョンホール内のすべてのハトがで遮断されているNとN + 1ケージハト場合、少なくとも二つのケージハトの少なくとも一つ

ThreadLocalMap競合を解決するためにどのような方法で実装ハッシュテーブル、など?それはこの問題を解決するためのオープンなアドレス指定方法を使用しています。

要素の挿入

コアOpenAddressed法律は、ハッシュ衝突が発生した場合、それは、アイドル位置を再検出し、それを挿入することです。あなたが見つけるまで、私たちはハッシュテーブルにデータを挿入すると、ハッシュ関数ハッシュ後のデータは、保管場所が占有されている場合、我々は現在の位置から開始し、アイドル位置があるかどうかを確認するために振り返る向けます。

図から分かるように、ハッシュ・テーブルのサイズは、6つの要素がハッシュテーブルに挿入されたハッシュテーブルのx要素を挿入する前に、10です。ハッシュアルゴリズムは、位置インデックスにハッシュされた後、Xは、それぞれ、7であるが、位置は、データを持っているので、競合が生じます。バック順次すべての自由の場所の最後にトラバース、アイドル位置が存在しないかどうかを確認するために、見つけるために一つ一つが見つからないあなたがアイドル位置2を見つけるまで、だから我々は、我々はこの位置に挿入し、その後、ヘッダから探し始めます。

要素を探します

挿入プロセスに幾分類似ハッシュ・テーブル・プロセス内の要素を検索します。私たちは、ハッシュ関数で重要な要素に対応するハッシュ値を得るために探して、その後、要素のハッシュ値と検索する要素の配列インデックスを比較しています。バックそれ以外の順番を[検索]をクリックします。彼らが等しい場合、我々は要素を探しています。トラバース配列アイドル位置は、発見されていない場合、それはあなたが探している要素は、ハッシュテーブルではありません示しています。

要素を削除します。

アレイとThreadLocalMap、だけでなく、挿入、検索操作をサポートし、また、削除操作をサポートしています。紛争解決の線形プロービング方法を使用して、ハッシュテーブルの場合は、やや特殊な削除。私たちは、単にnullに設定され、削除する要素を入れることはできません。

我々はそれについて話すのルックアップ操作を覚えていますか?我々は自由な位置を見つけるために、線形検出方法を通過したら、探しているとき、私たちはハッシュテーブルにデータがないと仮定することができます。アイドル位置は、我々は、後に除去という点である場合は、それが元の検索アルゴリズムの失敗につながります。それが存在するデータは、存在しないとして識別されます。どのようにこの問題を解決するには?

その後、私たちは、要素を削除することができ、その後nullではないデータが焼き直しますので、それは論理的なクエリには影響しません

もう一つの方法は次のとおりです。あなたは、要素を削除することができ、削除済みとしてマークされ、特別に。リニアは見つけることプロービングすると、出会いのスペースが削除されたとして、停止しないでマークされますが、ダウンプローブし続け

二番煎じ

ここでの手順を説明する焼き直し:時間を素子8を削除するとき、最初の添字は、8の値がnullであり、その後遅くとも空の配列要素を再ハッシュありません。図8は、それが9を移動させないように、位置(9 = 10%9)を想定し、そのような要素9、後ろにあります。次の要素が19であり、それはインデックス位置9であるべきで、それが占有されているので、タブに、3が空いているインデックス位置に次の空き位置を探して、[3]。次いで、タブ1の次の要素のタブに素子7タブの[1]に移動されていない、位置[7]、既に占有されているように、次の空き位置[8]。次の要素19の遺跡は、このタブので、[9] [0]タブに、次の空き位置を占めてきました。タブ[4]の4位の最後の要素に続く、それは移動していません。要素4の次の位置には、プロセス全体の焼き直しの端空です。

荷重係数

あなたは実際には、大きな問題線形検出方法があり、発見したことがあります。データのハッシュテーブルが上昇し、ハッシュの衝突の可能性が増加するに挿入されたとき、アイドル位置は少なくなり、時間リニアプローブが長く取得されるであろう。極端なケースでは、我々は、全体のハッシュテーブルを探索する必要があるかもしれないので、最悪の場合の時間複雑度はO(N)です。削除して検索するときも同様に、全体のハッシュ表を調べ、またはあなたが削除されたデータを見つけるために探している線形があるかもしれません。

場合ハッシュテーブル、ハッシュの衝突確率が大幅に改善されるアイドル位置のはるかにかかわらず、検出方法の、合理的であるように設計されたハッシュ関数を使用します。ハッシュテーブルの最高の動作効率を維持するために、通常の状況下では、我々は、空きスロットの一定割合にそのハッシュテーブルを確保しようとします。私たちは、空孔の数を表す係数(負荷率)をロードします。

負荷率が計算されるハッシュテーブルは、テーブル/ハッシュテーブル内の要素の個数=負荷係数の長さを充填されている以下休止位置、より多くの競合を示す、負荷率が大きく、ハッシュ・テーブルは、パフォーマンスを低下させるであろう。

ソースコード解析

ThreadLocalMap定義されました

コアデータ構造のThreadLocal ThreadLocalMapは、次のようにそのデータ構造は次のとおりです。

static class ThreadLocalMap {
  
  // 这里的entry继承WeakReference了
  static class Entry extends WeakReference<ThreadLocal<?>> {
      Object value;
      Entry(ThreadLocal<?> k, Object v) {
          super(k);
          value = v;
      }
  }

  // 初始化容量,必须是2的n次方
  private static final int INITIAL_CAPACITY = 16;

  // entry数组,用于存储数据
  private Entry[] table;

  // map的容量
  private int size = 0;

  // 数据量达到多少进行扩容,默认是 table.length * 2 / 3
  private int threshold;
复制代码

ThreadLocalMapエントリキーの定義から分かるようにThreadLocalであり、その値は値です。キー(ThreadLocalの一例)は弱参照対応するエントリと呼ばれているように、一方、エントリは、弱い参照を継承しました。それは三分の二の配列の長さの負荷率を定義します。

set()メソッド

private void set(ThreadLocal<?> key, Object value) {

  Entry[] tab = table;
  int len = tab.length;
  int i = key.threadLocalHashCode & (len-1);

  // 采用线性探测,寻找合适的插入位置
  for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
      ThreadLocal<?> k = e.get();
      // key存在则直接覆盖
      if (k == key) {
          e.value = value;
          return;
      }
      // key不存在,说明之前的ThreadLocal对象被回收了
      if (k == null) {
          replaceStaleEntry(key, value, i);
          return;
      }
  }

  // 不存在也没有旧元素,就创建一个
  tab[i] = new Entry(key, value);
  int sz = ++size;
  // 清除旧的槽(entry不为空,但是ThreadLocal为空),并且当数组中元素大于阈值就rehash
  if (!cleanSomeSlots(i, sz) && sz >= threshold)
    expungeStaleEntries();
    // 扩容
    if (size >= threshold - threshold / 4)
      resize();
}

复制代码

上記の手順の主な情報源次のように

  1. 線形検出方式、適切な挿入位置を検索します。最初がキーがあるかどうかを判断し、直接カバレッジが存在します。キーは、この時点で収集した古いごみの要素を証明するために存在していない場合は、新しい要素を交換する必要があります
  2. 対応する要素が存在しない場合、我々は、新しい要素を作成する必要があります
  3. メモリリークを防ぐために、要素の明確なエントリが空ではありませんが、ThreadLocalの(キーのエントリは、回収されました)
  4. 閾値/ 4回前のアレイを展開し、中の配列の要素と(焼き直し)を移動させる位置を再計算します - サイズ> =閾値:条件は、場合。そのようなアレイ16の初期サイズの開始、などの場合サイズ> =(16 * 2/3 = 10) - (10/4)= 8、場合意志膨張、32アレイのサイズの拡大。

replaceStaleEntry()メソッドまたはcleanSomeSlots()メソッドかどうかは、最も重要なメソッド呼び出しはexpungeStaleEntryは()、あなたはその存在で見つけることができ、セットされますが、ThreadLocalMap呼び出しを削除することができます。

private int expungeStaleEntry(int staleSlot) {
  Entry[] tab = table;
  int len = tab.length;

  // 删除对应位置的entry
  tab[staleSlot].value = null;
  tab[staleSlot] = null;
  size--;

  Entry e;
  int i;

  // rehash过程,直到entry为null
  for (i = nextIndex(staleSlot, len);(e = tab[i]) != null; i = nextIndex(i, len)) {
    ThreadLocal<?> k = e.get();
    // k为空,证明已经被垃圾回收了
    if (k == null) {
        e.value = null;
        tab[i] = null;
        size--;
    } else {
        int h = k.threadLocalHashCode & (len - 1);
        // 判断当前元素是否处于"真正"应该待的位置
        if (h != i) {
            tab[i] = null;
            // 线性探测
            while (tab[h] != null)
                h = nextIndex(h, len);
            tab[h] = e;
        }
    }
  }
  return i;
}
复制代码

ThreadLocalMapは、以下の条件、追加、取得、焼き直しの条件に従って行われようとしている削除した場合、物品の組み合わせの焼き直しの最初のコードの上記の説明は、理解しやすいです

  1. ThreadLocalのオブジェクトが回収され、この場合、エントリキーの値がnullでない、ヌルです。そして、焼き直しをトリガーします
  2. しきい値はThreadLocalMapの三分の二の容量である場合には

()メソッドを取得します

private Entry getEntry(ThreadLocal<?> key) {
  int i = key.threadLocalHashCode & (table.length - 1);
  Entry e = table[i];
  // 现在数据中进行查找
  if (e != null && e.get() == key)
      return e;
  else
      return getEntryAfterMiss(key, i, e);
}

// 采用线性探测找到对应元素
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
  Entry[] tab = table;
  int len = tab.length;

  while (e != null) {
      ThreadLocal<?> k = e.get();
      // 找到元素
      if (k == key)
          return e;
      // ThreadLocal为空,需要删除过期元素,同时进行rehash
      if (k == null)
          expungeStaleEntry(i);
      else
          i = nextIndex(i, len);
      e = tab[i];
  }
  return null;
}

复制代码

GETを通してリニア検出方法、設定されたすべてのプロセスが、原則はコードを見て理解することは非常に簡単です。

()メソッドを削除

private void remove(ThreadLocal<?> key) {
   Entry[] tab = table;
   int len = tab.length;
   int i = key.threadLocalHashCode & (len-1);
   for (Entry e = tab[i];
        e != null;
        e = tab[i = nextIndex(i, len)]) {
       if (e.get() == key) {
           e.clear();
           expungeStaleEntry(i);
           return;
       }
   }
}

复制代码

古いエントリを削除するために戻って時間を削除し、焼き直し。

ThreadLocalの使用

public class Counter {

  private static ThreadLocal<Integer> seqCount = new ThreadLocal<Integer>(){
    public Integer initialValue() {
        return 0;
    }
  };

  public int nextInt(){
    seqCount.set(seqCount.get() + 1);

    return seqCount.get();
  }
  public static void main(String[] args){
    Counter seqCount = new Counter();

    CountThread thread1 = new CountThread(seqCount);
    CountThread thread2 = new CountThread(seqCount);
    CountThread thread3 = new CountThread(seqCount);
    CountThread thread4 = new CountThread(seqCount);

    thread1.start();
    thread2.start();
    thread3.start();
    thread4.start();
  }

  private static class CountThread extends Thread{
    private Counter counter;

    CountThread(Counter counter){
        this.counter = counter;
    }

    @Override
    public void run() {
        for(int i = 0 ; i < 3 ; i++){
            System.out.println(Thread.currentThread().getName() + " seqCount :" + counter.nextInt());
        }
    }
  }
}


复制代码

次のように業績は以下のとおりです。

Thread-3 seqCount :1
Thread-0 seqCount :1
Thread-3 seqCount :2
Thread-0 seqCount :2
Thread-0 seqCount :3
Thread-2 seqCount :1
Thread-2 seqCount :2
Thread-1 seqCount :1
Thread-3 seqCount :3
Thread-1 seqCount :2
Thread-1 seqCount :3
Thread-2 seqCount :3
复制代码

ThreadLocalのは、スレッドごとに実際に影響を受けません同時アクセスを可能にする、変数のコピーを提供しています。ここからも変数に各スレッドの変更が他のスレッドに影響を与えることなく、スレッド・オブジェクト・データへの他のスレッドから見える、そしてThreadLocalのある可能にするために同期され、これと同期の間で異なるシナリオを参照して、それが最も適していますシーンは、同じスレッドの開発のさまざまなレベルでの共有データでなければなりません。

おすすめ

転載: juejin.im/post/5d43e415e51d4561db5e39ed