読者です 読者をやめる 読者になる 読者になる

Javaのラムダとスレッドセーフ

Java8のlambda構文がどのようにクロージャーではないかを読んで思ったのは、ラムダの外側の変数に対する処理がスレッドセーフなのかどうかという点です。というわけで早速試してみました。

スレッドセーフでない ArrayList クラスに対して並列で値を追加するというコードです。

    public void test() {
        Integer[] array = new Integer[100000];
        Arrays.fill(array,0);

        List<Integer> nums = Arrays.asList(array);

        Stream<Integer> stream = nums.stream();

        List<Integer> list = new ArrayList<Integer>();

        stream.parallel().forEach(s -> list.add(s));

        System.out.println(list.size());
    }

試した限りでは確実に例外が出て完走出来ませんでした。

Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 244
	at java.util.ArrayList.add(ArrayList.java:416)
	at Test.lambda$0(Test.java:32)
	at Test$$Lambda$3.accept(Unknown Source)
	at java.util.stream.op.ForEachOp$1.accept(ForEachOp.java:51)
	at java.util.Arrays$ArrayIterator.forEach(Arrays.java:4316)


では、ArrayListを synchronizedList でラップしてやりましょう。これで list に対する値の追加はスレッドセーフです。

    public void test() {
        Integer[] array = new Integer[100000];
        Arrays.fill(array,0);

        List<Integer> nums = Arrays.asList(array);

        Stream<Integer> stream = nums.stream();

        List<Integer> list = Collections.synchronizedList(new ArrayList<Integer>());

        stream.parallel().forEach(s -> list.add(s));

        System.out.println(list.size());
    }

完走出来ました。

100000

というわけで、安易にラムダの外側の変数の中身を書き換えるようなコードを書くと危険かも?ラムダを引き渡した後の奥深くで並列で呼び出されちゃったりとか・・・・