[9]スレッドコピーThreadLocal、およびWeakHashMap

マルチスレッドセーフを実現する3つの方法、

1.ロックメカニズムの同期およびロック方法を使用します。リソースをロックするには、以前の記事を参照してください。

2.次のクラスライブラリのjava.util.concurrentを使用します。JDKによって提供されるスレッドセーフなコレクションクラスがあります:AtomicInteger、AtomicStampedReference、ConcurrentHashMap、CopyOnWriteArrayList、ReentrantLock、FutureTask、CountDownLatch。

3.マルチインスタンスまたはマルチコピー(ThreadLocal):ThreadLocalは、スレッドごとにプライベートローカル変数を維持できます。

 

ここでは主にThreadLocalについて話します:

マルチスレッドの同時実行性の問題を解決するためのThreadLocalのアイデアは非常に単純です:スレッドがプライベートリソースを持つように各スレッドのコピー変数を維持することであり、プロセスでリソースを奪い合う必要はありません。各スレッドは、他のスレッドに対応するコピーに影響を与えることなく、独自のコピーを個別に変更できます。

マルチスレッド環境で、自分の変数が他のスレッドによって改ざんされるのを防ぐにはどうすればよいですか?

api:

void set(T value):このスレッドローカル変数の現在のスレッドコピーの値を指定された値に設定します。

T get():このスレッドローカル変数の現在のスレッドコピーの値を返します。

void remove():このスレッドローカル変数の現在のスレッド値を削除します。

 

1. ThreadLocalの基本的な実装は、ThreadLocalMapを使用することです(これはThreadLocalの静的内部クラスであり、ThreadLocalMapのエントリはWeakReferenceを継承します)。各スレッドにはThredLocalMapがあり、要素のキー値は現在のThreadLocalオブジェクトであり、値は対応します。スレッドへ変数のコピーgetメソッドとsetメソッドを呼び出すときは、現在実行中のスレッドのThreadLocalMapオブジェクトが使用されるため、現在実行中のスレッドのプライベート変数を取得して設定できます。

ソースコードの一部:

/* ThreadLocal values pertaining to this thread. This map is maintained

* by the ThreadLocal class. */

ThreadLocal.ThreadLocalMap threadLocals = null;

2. get()メソッドを呼び出すときは、最初に現在のスレッドを取得し、次に現在のスレッドのThreadLocalMapオブジェクトを取得し、次に現在のThreadLocalオブジェクトに従ってエントリを取得します。空でない場合は、ThreadLocal値を取得します。初期化、初期化は、initialValueの値をThreadLocalMapに設定することです。

ソースコードの一部:

public T get() {

Thread t = Thread.currentThread();

ThreadLocalMap map = getMap(t);

if (map != null) {

ThreadLocalMap.Entry e = map.getEntry(this);

if (e != null)

return (T)e.value;

}

return setInitialValue();

}



private T setInitialValue() {  

    T value = initialValue();  //null

    Thread t = Thread.currentThread();  

    ThreadLocalMap map = getMap(t);  

    if (map != null)  

        map.set(this, value);  

    else  

        createMap(t, value);  

    return value;  

}  

3. set()メソッドを呼び出すとき、値をThreadLocalに設定することは非常に一般的です。get()メソッドに似ています。

public void set(T value) {

        Thread t = Thread.currentThread();

        ThreadLocalMap map = getMap(t);

        if (map != null)

            map.set(this, value);

        else

            createMap(t, value);

    }

 

例えば:

public class MyTest {

//一般将ThreadLocal的对象修饰为static对象,方便多个线程共享

public static ThreadLocal threadLocal = new ThreadLocal<>();

public static void main(String[] args) {

    Thread t1 = new Thread(new MyTask());

    Thread t2 = new Thread(new MyTask());

    Thread t3 = new Thread(new MyTask());

    //启动3个线程

    t1.start();

    t2.start();

    t3.start();

}

}

class MyTask implements Runnable{

    @Override

    public void run() {

        MyTest.threadLocal.set("0");

        //线程计算5次

         for(int i=1;i<5;i++){

             MyTest.threadLocal.set( MyTest.threadLocal.get().toString()+i);

         }

         System.out.println("线程"+Thread.currentThread().getName()+"的计算结果:"+MyTest.threadLocal.get());

    }

}

演算結果:

スレッドスレッド1の計算結果:01234

スレッドスレッド0の計算結果:01234

スレッドスレッド2の計算結果:01234

すごい気分じゃないですか!3つのスレッドはすべて同じThreadLocal静的変数オブジェクトを使用して値を格納または取得しますが、3つのスレッドの計算結果は相互に影響を与えず、相互に独立しています。そうです、これはThreadLocalが並行性を解決する方法であり、スレッドごとにスレッドのプライベート変数を維持します。同時に、すべてのスレッドで共有できます。

 

ThreadLocalMapソースコード:挿入プロセス中、

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

            // We don't use a fast path as with  get() because it is at

            // least as common to use set() to  create new entries as

            // it is to replace existing ones,  in which case, a fast

            // path would fail more often than  not.

            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();

                if (k == key) {

                    e.value = value;

                    return;

                }

                if (k == null) {

                    replaceStaleEntry(key,  value, i);

                    return;

                }

            }

            tab[i] = new Entry(key, value);

            int sz = ++size;

            if (!cleanSomeSlots(i, sz) && sz >=  threshold)

                rehash();

        }

ThreadLocalオブジェクトのハッシュ値に従って、テーブル内の位置iを見つけます。プロセスは次のとおりです。1。現在の位置が空の場合は、Entryオブジェクトを初期化して位置iに配置します。2。残念ながら位置iはすでに存在します。このエントリオブジェクトのキーが設定するキーである場合は、エントリの値をリセットします。3。残念ながら、位置iのエントリオブジェクトは設定するキーとは関係ありません。 、したがって、次の1つの空いているポジションのみを見つけることができます。

長さはhashMap(16)の長さと同じですが、配列のエントリ要素はリンクリストではありません。

 

エントリソースコード(ThreadLocalMapの静的内部クラス):

static class Entry extends  WeakReference<ThreadLocal<?>> {

            /** The value associated with this  ThreadLocal. */

            Object value;

            Entry(ThreadLocal<?> k, Object v) {

                super(k);

                value = v;

            }

        }

メモリリークの問題:プログラムがメモリを適用した後、適用されたメモリスペースを解放または使用できず、複数のメモリリークが発生するとメモリオーバーフローが発生します。

メモリリークが発生する可能性があります。エントリ内のキーが弱く参照されているため、GCが発生すると、キーがリサイクルされます。この時点で、ThreadLocalMapにnullキーを持つエントリがありますが、nullを持つエントリがあります。キーにアクセスできません。値の値。スレッドが終了していない場合、エントリは再利用されず、メモリリークが発生します。

メモリリークの問題を解決します。

ThreadLocalのget()およびset()を呼び出すと、ThreadLocalMapでキーがnullのEntryオブジェクトクリアされる場合があります(getメソッドが対応するEntryを見つけられない場合、対応するEntryが見つからない場合はThreadLocalにリセットされてnull値が設定され、setメソッドによって元のエントリの上書き)。これにより、対応する値にGCルートが到達できなくなり、次のGCでリサイクルできます。もちろん、removeメソッドが呼び出されると、対応するEntryオブジェクトは確実に削除されます。

 

WeakHashMapがメモリリークの問題をどのように解決するか:

WeakHashMap$Entry:

private static class Entry<K,V> extends  WeakReference<Object> implements Map.Entry<K,V>  {

        V value;

        final int hash;

        Entry<K,V> next;

        /**

         * Creates new entry.

         */

        Entry(Object key, V value,

              ReferenceQueue<Object> queue,

              int hash, Entry<K,V> next) {

            super(key, queue);

            this.value = value;

            this.hash  = hash;

            this.next  = next;

        }

}//Entry继承WeakReference,所以引用队列中存放的是Entry对象,但是 super(key, queue);所以只有key是弱引用

    

private void expungeStaleEntries() {

        for (Object x; (x = queue.poll()) !=  null; ) {

            synchronized (queue) {

                @SuppressWarnings("unchecked")

                    Entry<K,V> e = (Entry<K,V>)  x;

                int i = indexFor(e.hash,  table.length);

                Entry<K,V> prev = table[i];

                Entry<K,V> p = prev;

                while (p != null) {

                    Entry<K,V> next = p.next;

                    if (p == e) {

                        if (prev == e)

                            table[i] = next;

                        else

                            prev.next = next;

                        // Must not null out  e.next;

                        // stale entries may be  in use by a HashIterator

                        e.value = null; // Help  GC

                        size--;

                        break;

                    }

                    prev = p;

                    p = next;

                }

            }

        }

    }

メモリリークの問題の主な解決策は、expungeStaleEntriesメソッドです。このメソッドは、ReferenceQueue(GCが格納された後のEntryオブジェクト)でエントリを検索し、WeakHashMapのエントリ配列でインデックスを見つけて、値を設定します。対応するチェーンからnullへの関連するエントリの、これにより関連データのクリーニングが完了し、put、get、remove、sizeなどのメソッドがexpungeStaleEntriesメソッドをトリガーできますが、これらのメソッドが呼び出されない場合、メモリリークが発生する可能性があります。

おすすめ

転載: blog.csdn.net/Jack_PJ/article/details/88016471