Help us understand the problem. What is going on with this article?

CopyOnWriteArrayListとWeakHashMapとConcurrentHashMapとCollections.newSetFromMapについて

More than 1 year has passed since last update.

Hadoopの勉強をしているときに出くわしてその時のメモ。

CopyOnWriteArrayListクラス

HashtableやHashMapの問題を解決した(?)クラス。
配列を新規にコピーしてArrayListでConcurrentModificationExceptionが起こる例外を起こさずに処理してくれるとか。

例えば、
ArrayListのConcurrentModificationException発生させた例は下記のようになる

ArrayListSample.java
import java.util.ArrayList;

public class ArrayListSample{
    private static ArrayList<String> arraylist = new ArrayList<String>();

    public static void main(String[] args) {     
        //要素追加
        for(int i = 0; i < 10000; i++)
        {
            arraylist.add("ArrayList" + i);
        }

        //別スレッドでリストを走査する
        Thread th = new Thread(new Runnable(){
                @Override
                public void run(){
                    for(String h : arraylist)
                    {
                        arraylist.remove(h);  //ConcurrentModificationException発生。
                    }
                }
        });

        th.start();

        //操作中に削除
        arraylist.remove("ArrayList0");
        System.out.println("削除完了");
     }
}

これを実行すると次のようになる

削除完了
Exception in thread "Thread-0" java.util.ConcurrentModificationException
    at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
    at java.util.ArrayList$Itr.next(ArrayList.java:851)
    at ArrayListSample$1.run(ArrayListSample.java:19)
    at java.lang.Thread.run(Thread.java:745)

これは、リストを同期せずイテレータ動作中に他のスレッドで変更をかけたために起きた。
要は同期しろということでsynchronizedやCollections.synchronizedListを使えば直ると思ったが、直らない。

だが、これはArrayListの所をCopyOnWriteArrayListと変えるだけで解決できてしまった。

CopyOnWriteArrayListSample.java
import java.util.concurrent.CopyOnWriteArrayList;

public class CopyOnWriteArrayListSample{
    private static CopyOnWriteArrayList<String> copyonwritearraylist = new CopyOnWriteArrayList<String>();

    public static void main(String[] args) {     
        //要素追加
        for(int i = 0; i < 10000; i++)
        {
            copyonwritearraylist.add("ArrayList" + i);
        }

        //別スレッドでリストを走査する
        Thread th = new Thread(new Runnable(){
                @Override
                public void run(){
                    for(String h : copyonwritearraylist)
                    {
                        copyonwritearraylist.remove(h);
                    }
                }
        });

        th.start();

        //操作中に削除
        arrayList.remove("copyonwritearraylist0");
        System.out.println("削除完了");
     }
}

これを実行すると次のようになる

削除完了

これは最初に記述したように、まず配列を新規にまるまるコピーしてコピーした配列で処理を行っているので、ConcurrentModificationExceptionが起きないということ。synchronized等を使うと、同じ配列中で処理を行って動作するためこのようなことを解決できなかった。なので並行して配列の処理をするときはCopyOnWriteArrayListを使うことがオススメ。だがこれにも欠点があり、それは普通にArrayListで処理をするより処理に時間がかなりかかるということ。だから、このクラスを使う用途としては頻繁に読み取りを行い、稀にしか書き込まれないという処理に使うことが望ましい。

containsメソッド

指定した要素がリストに入っていればtrueを返す。

addメソッド

指定した要素を追加する。

WeakHashMapクラス

キー参照であるHashMapを基に作られており、HashMapと違うのはキーの参照が消えると(例えば、元のキーにnullや違う値を入れたり)WeakHashMapに入れた値はガーベジコレクションを行ったときにその値ごと消してしまうということ。これが弱キー参照もしくは弱参照である。キー参照なので値の方の参照をなくしても何ともならない。

とりあえずHashMapとWeakHashMapの違いを見てみる
まずはHashMap

HashMapSample.java
import java.util.HashMap;

public class HashMapSample {
    static HashMap<String,String> hashmap=new HashMap<String,String>();

    public static void main(String[] args){

        String[] key=new String[5];
        String[] value=new String[5];

        for(int i=0;i<key.length;i++)
        {
            key[i]="Key"+i;
            value[i]="Value"+i;

            hashmap.put(key[i],value[i]);
        }

        System.out.println(hashmap);
        key[1]=null;
        value[2]=null;
        System.out.println("ガーベジコレクション");
        System.gc();
        System.out.println(hashmap);
    }
}

実行結果は

{Key2=Value2, Key1=Value1, Key0=Value0, Key4=Value4, Key3=Value3}
ガーベジコレクション
{Key2=Value2, Key1=Value1, Key0=Value0, Key4=Value4, Key3=Value3}

となり、キーの参照を消してもそのまま。
次はWeakHashMap

WeakHashMapSample.java
import java.util.WeakHashMap;

public class WeakHashMapSample {
    static WeakHashMap<String,String> weakhashmap=new WeakHashMap<String,String>();

    public static void main(String[] args){

        String[] key=new String[5];
        String[] value=new String[5];

        for(int i=0 ;i<key.length;i++)
        {
            key[i]="Key"+i;
            value[i]="Value"+i;

            weakhashmap.put(key[i],value[i]);
        }

        System.out.println(weakhashmap);
        key[1]=null;
        value[2]=null;
        System.out.println("ガーベジコレクション");
        System.gc();
        System.out.println(weakhashmap);
    }
}

実行結果は

{Key3=Value3, Key4=Value4, Key1=Value1, Key2=Value2, Key0=Value0}
ガーベジコレクション
{Key3=Value3, Key4=Value4, Key2=Value2, Key0=Value0}

のように参照を消したキーはガーベジコレクションしたときに値ごと消えている。

keySetメソッド

WeakHashMapのすべてのキーを返す。

Collections.newSetFromMapとConcurrentHashMapクラス

Collections.newSetFromMap

まずは、Collections.newSetFromMap。Mapから新しくSetを返してくれるCollectionsのstaticメソッド。このメソッドに入れるMapは空でないといけない制約がある。Setに入れられる要素が1つだけなので要素を2つ持つMapはSetと対応させるために2番目の要素はBoolean型になっている。

例えば、WeakHashMapをやったのでこれをSetに

Collections_newSetFromMapSample.java
import java.util.Collections;
import java.util.Set;
import java.util.WeakHashMap;

public class Collections_newSetFromMapSample {
    static WeakHashMap<String,Boolean> weakhashmap=new WeakHashMap<String,Boolean>();
    static Set<String> set=Collections.newSetFromMap(weakhashmap);

    public static void main(String[] args) {
        String[] key=new String[5];

        for(int i=0 ;i<key.length;i++)
        {
            key[i]="Key"+i;
            set.add(key[i]);
        }

        System.out.println("Set is: "+set);
        System.out.println("Map is: "+weakhashmap);
        key[1]=null;
        System.out.println("ガーベジコレクション");
        System.gc();
        System.out.println("Set is: "+set);
        System.out.println("Map is: "+weakhashmap);
    }
}

実行結果

Set is: [Key3, Key4, Key1, Key2, Key0]
Map is: {Key3=true, Key4=true, Key1=true, Key2=true, Key0=true}
ガーベジコレクション
Set is: [Key3, Key4, Key2, Key0]
Map is: {Key3=true, Key4=true, Key2=true, Key0=true}

ちゃんとキーがSetに入れられていることが確認できる(Mapの第2要素になぜtrueが入れられるのか不明なので知っている方がいれば教えてください)。もちろんガーベジコレクションで参照されないキーを排除したのでその部分は消えている。

ConcurrentHashMapクラス

HashTableの機能性を持つHashMap。他のスレッドが働いているときは自動的に同期して、さらにその同期時間をかなり短縮してくれるようになっている。読み込み操作はブロックされず、更新競合は最小限に抑えられている。

ただし、他のHashMapと違い、nullをキーもしくは値に入れてしまうとNullPointerExceptionが発生する。

実際にnullを入れてみる

ConcurrentHashMapSample.java
import java.util.concurrent.ConcurrentHashMap;

public class ConcurrentHashMapSample {
    static ConcurrentHashMap<String,String> concurrenthashmap=new ConcurrentHashMap<String,String>();

    public static void main(String[] args) {
        String[] key=new String[5];
        String[] value=new String[5];

        for(int i=0 ;i<key.length;i++)
        {
            key[i]="Key"+i;
            value[i]="Value"+i;

            concurrenthashmap.put(key[i],value[i]);
        }

        System.out.println(concurrenthashmap);

        key[1]=null;
        concurrenthashmap.put(key[1],value[1]);
        System.out.println(concurrenthashmap);
    }
}

を実行すると

{Key2=Value2, Key1=Value1, Key0=Value0, Key4=Value4, Key3=Value3}
Exception in thread "main" java.lang.NullPointerException
    at java.util.concurrent.ConcurrentHashMap.putVal(ConcurrentHashMap.java:1011)
    at java.util.concurrent.ConcurrentHashMap.put(ConcurrentHashMap.java:1006)
    at ConcurrentHashMapSample.main(ConcurrentHashMapSample.java:22)

のように実際にNullPointerExceptionが発生する。

clearメソッド

ConcurrentHashMap内のMapを全て削除する。

おわり

なにか意見や訂正点があれば気軽にください。

Apacher-inf
技術や新しい知識はいいもんですね。
https://scrapbox.io/yamprogramming/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away