西海岸より

つらつらざつざつと

this参照の逸脱

並行処理プログラミングのP48、安全なオブジェクトの公開について。

  • スレッドセーフではない。
public class UnsafePublisher{
    private final String id;
    public UnsafePublisher(String id){
        Runnable task = new Runnable(){
            public void run(){
                System.out.println("run() UnsafePublisher id: " + UnsafePublisher.this.id);	
            }
            public String toString(){
                return "toString() UnsafePublisher id:" + UnsafePublisher.this.id;
            }
        };
        new TaskStarter().startTask(task);
        this.id = id;
    }

    public static void main(String[] args){
        new UnsafePublisher("1");	
    }
}

class TaskStarter{
    public void startTask(Runnable task){
        System.out.println("Start task");
        System.out.println(task);
        new Thread(task).start();
    }
}
  • 実行結果
Start task
toString() UnsafePublisher id:null
run() UnsafePublisher id: 1

このコードが安全でない理由は、UnsafePublisherのコンストラクタ中に構築したRunnableインスタンスがUnsafePublisherのthis参照を持ち、外部クラスに渡してしまっているため。こうして、startTaskメソッドが実行される時点でidフィールドが初期化されていないにも関わらず利用され、実行結果にもあるように初期化前のid(null)が表示されてしまっている。

また、コンストラクタでスレッドをスタートする際も、タイミングによっては不完全なthisを参照することがあり、run()メソッドが表示するidもnullになるケースも確認できた。

合わせて、以下の2点を気をつけなければならない。

修正方法としては、ファクトリメソッドを用意し、コンストラクタが確実に完了した後に外部クラスに渡し、スレッドについてもコンストラクタ完了時にスタートをかけるようにする。

public class SafePublisher{
    private final String id;
    private final Runnable task;
    public SafePublisher(String id){
        task = new Runnable(){
            public void run(){
                System.out.println("run() SafePublisher id: " + SafePublisher.this.id);	
            }
            public String toString(){
                return "toString() SafePublisher id:" + SafePublisher.this.id;
            }
        };
        this.id = id;
    }

    public static SafePublisher newInstance(String id, TaskStarter starter){
        SafePublisher sp = new SafePublisher(id);
        starter.startTask(sp.task);
        return sp;
    }

    public static void main(String[] args){
        SafePublisher publisher = SafePublisher.newInstance("1", new TaskStarter());	
    }
}
  • 実行結果
Start task
toString() SafePublisher id: 1
run() SafePublisher id: 1

本の他にd:id:moqoo:20090307:1236438615を参考にさせてもらいました。