Javaに関する様々な情報をご紹介します。

Javaに関する様々な情報をご紹介します。
評価

0

クラスメソッド(static)使用時のメモリ割当とスタック

いつもお世話になっております。

ちょっと表題の件でわからないことがあったので皆様のお知恵を貸していただければと思います。
よろしくお願いします。

まず、クラスメソッド(static)があったとして
メモリ上はJVMのヒープに置かれると思うのですが、
そのクラスメソッドを同一プロセスの複数スレッドから同時実行した場合にはそれぞれのスレッドのメモリ上のスタックに積まれると思っています。
※クラスメソッド自体はJVM上で静的に割り当てられますよね?

1プロセス内において複数スレッドからのクラスメソッドの同期を取るにはやはりmutex機構(synchronized)を使うしかないのでしょうか?

何が知りたいかと言うと良くシングルトンのデザインパターンに置いて1インスタンスを使いまわすと思うのですが
仮に自身を返すメソッド(getInstanceとします)を複数から呼んだ場合に1スレッドからは既に生成された自身を返す場合と1スレッドからは自身が削除(別インスタンスとして生成など)されている可能性があるってことですよね?

※インスタンスとしては1インスタンスだけど同一インスタンスでは無くなっている

13

回答

8800

閲覧

13件の回答

評価

0

同期を取らねばならないと考えている対象は何だろうか。
シングルトンインスタンスは、
インスタンス→1つ
フィールド→1つ
メソッドの実体→1つ
メソッド内のローカル変数→複数
だよ。
同一のプロセス内である限り、スレッドがいくつあっても常にインスタンスは1つしかない。

評価

0

$さん、回答ありがとうございます。

例に挙げたのは一般的なシングルトンでしたが
実際にやりたいのはファクトリのマネジャ的なシングルトンで管理してそのファクトリマネジャが持つ管理インスタンス(フィールドで管理されており各クラスの実体を配列管理しています)をしたいのですが、そもそも管理側のファクトリマネジャが1インスタンスは保障されるが同一インスタンスは保障されない、なので困っています。

ファクトマネジャが返す管理インスタンスをスレッド内で同一アクセスしたタイミングでは保障するにはやはりファクトリマネジャ自身が同期されている必要があるのかなと思った次第です。

説明がうまく出来なくて申し訳ありません。

評価

0

文章でうまく説明できないなら、サンプルコードを書くんだよ。

評価

0

サンプルを作ってみたのですがちょっと
思ってたのと動作が違っていたので、もしよろしければご教授ください。

★サンプルソース
StaticMethodTest.java

package statictest;

/**
 * シングルトンインスタンスを片方のスレッドで
 * 削除(null参照)にした場合の動作検証
 */
public class StaticMethodTest {

    public static void main(String[] args) {
        
        // スレッド1
        Thread a = new Thread(new Runnable() {
            
            @Override
            public void run() {
                try {
                    SingletonClass singleton1 = SingletonClass.getInstance();
                    singleton1.print("スレッド1");
                    // 1スレッドでシングルトンインスタンスをnullに
                    SingletonClass.clearInstance();
                } catch(Exception e) {
                    e.printStackTrace();
                }
            }
        });
        
        // スレッド2
        Thread b = new Thread(new Runnable() {
            
            @Override
            public void run() {
                try {
                    SingletonClass singleton2 = SingletonClass.getInstance();
                    // ※ここでNullPointerが出ると思ってた
                    singleton2.print("スレッド2");
                } catch(Exception e) {
                    e.printStackTrace();
                }
            }
        });
        
        // スレッド開始
        a.start();
        b.start();
    }
}

★サンプルソース
SingletonClass.java

package statictest;

public class SingletonClass {

    /** 自身のインスタンス */
    private static SingletonClass singleton = new SingletonClass();
    
    /**
     * コンストラクタ(不可視)
     */
    private SingletonClass() {
        
    }
    
    /**
     * 自身のインスタンスを返却
     */
    public static SingletonClass getInstance() {
        return singleton;
    }
    
    /**
     * 自身のインスタンスを返却
     */
    public static void clearInstance() {
        singleton = null;
    }
    
    /**
     * 文字列出力
     * @param msg
     */
    public void print(String msg) {
        System.out.println(msg);
    }
}

【不明点】
スレッド2がprintを実行する箇所でブレークを設定しておき、スレッド1を動作完了させる。
スレッド1ではシングルトンインスタンスにnullを設定している。(clearInstanceメソッド)
その際に、スレッド2が参照しているシングルトンインスタンスがnullになっていると思っていたのですが結果は正常に終了しました。
※但し、スレッド2がgetInstanceを呼ぶ前にスレッド1でnullにするとNullPointerが発生する

【確認したこと】
スレッド1とスレッド2が参照しているインスタンスは同じ。
※ハッシュ値なども全て同じでした。
スレッド間で同一インスタンスを参照している場合はもしかしてnullにしてもスレッドセーフになるのか?とか思っているのですがよろしければ教えてください。

評価

0

スレッド1がシングルトン取得

スレッド2がシングルトン取得

スレッド1でシングルトンインスタンス削除

でスレッド2が動くのは何故か、ということか?


それはスレッドセーフとは関係ない。
参照というものを誤解してるだけ。

評価

0

意味がわかりました…
お騒がせしました。

singleton1 → singleton → インスタンス実体
singleton2 → singleton → インスタンス実体

で、singletonがnullになろうとsingleton2が既にインスタンス実体を参照しているからってことですね…

$さん、ありがとうございました!

評価

0

何度もすみません。

と、いうことはJVMのメモリ空間に一度new(実体)されたインスタンスはいくら参照ポインタ(変数)をnullにしようともGCが走らない限りはメモリ空間からは削除出来ないということで認識あってますでしょうか?

評価

0

メモリ上という以前に、そもそも複数の場所から参照されたインスタンスは、それらの変数全てがnullになるか、全てのスレッドの実行が各々の変数のスコープから出るかしない限りGCの対象にならない。

上記の例でいけば、スレッド1で元の変数にnullを入れようと、スレッド2からの参照が残ってる間はGCの対象にはならない。

参照がなくなった後の話であれば合ってるけど。
しかしGCが走る前であろうと、一旦変数をnullにしたら以前入っていたポインタを復元することは(基本的には)できないのだから、メモリ上に残っていてもプログラマには関係のない話。

評価

0

本当に何度もすみません。
ちょっと思ってたのと動きが違っていました。
※色々やっている内にごっちゃになっていました。
上記でそうなるのは元々理解しています。

で、本来聞きたかったのは以下の場合です。

package statictest;

/**
 * シングルトンインスタンスを片方のスレッドで
 * 削除(null参照)にした場合の動作検証
 */
public class StaticMethodTest {

    public static void main(String[] args) {
        
        // スレッド?
        Thread a = new Thread(new Runnable() {
            
            @Override
            public void run() {
                try {
                    SingletonClass singleton1 = SingletonClass.getInstance();
                    singleton1.addValue("スレッド1");
                    singleton1.printValue();
                    // 1スレッドでシングルトンインスタンスをnullに
                    SingletonClass.clearInstance();
                } catch(Exception e) {
                    e.printStackTrace();
                }
            }
        });
        
        // スレッド?
        Thread b = new Thread(new Runnable() {
            
            @Override
            public void run() {
                try {
                    SingletonClass singleton2 = SingletonClass.getInstance();
                    singleton2.addValue("スレッド2");
                    singleton2.printValue();
                } catch(Exception e) {
                    e.printStackTrace();
                }
            }
        });
        
        // スレッド開始
        a.start();
        b.start();
    }
}

package statictest;

import java.util.ArrayList;
import java.util.List;

public class SingletonClass {

    /** 自身のインスタンス */
    private static SingletonClass singleton = null;
    
    /** 文字列格納用 */
    private List<String> value = new ArrayList<String>();
    
    /**
     * コンストラクタ(不可視)
     */
    private SingletonClass() {
        
    }
    
    /**
     * 自身の参照を返却
     */
    public static SingletonClass getInstance() {
        if (singleton == null) {
            singleton = new SingletonClass();
        }
        return singleton;
    }
    
    /**
     * 自身の参照を削除
     */
    public static void clearInstance() {
        singleton = null;
    }
    
    /**
     * 文字列出力
     * @param msg
     */
    public void print(String msg) {
        System.out.println(msg);
    }
    
    /**
     * 格納
     * @param value
     */
    public void addValue(String value) {
        this.value.add(value);
    }
    
    /**
     * 格納文字列出力
     * @param value
     */
    public void printValue() {
        for (String value : this.value) {
            System.out.println(value);
        }
    }
}

【問題箇所】
スレッド1が最初にシングルトンインスタンスを生成したタイミングでスレッド2がgetInstanceメソッドの呼び出しを行い、シングルトンインスタンス参照変数がnullかどうかの判定前にスレッド1にてnull(clearInstance)を呼ばれた場合です。
当たり前ですが当然スレッド2のgetInstanceではシングルトンインスタンス参照変数はnullなのでスレッド1が参照していた実体とは異なってしまいます。

で、ここからが本題のmutexを使うしか無いのかって話です。

スレッド2にてgetInstanceが呼ばれた時点でのシングルトンインスタンス参照へのatomicを保障してやりたいのです。

つまり、やりたいことはサンプルソースでいうとsingleton参照変数へのスレッド間からの参照を保障してやるにはsynchronizedを使うしか無いのかって質問でした。

評価

0

補足です。

synchronizedを使うのはsingleton参照変数に対してです。

Java1.6からはConcurrentの仕組みがかなり入っていると思いますがそれと同じ仕組みを保証してやりたいのです。

評価

0

すみません。自己解決しました。

やはり参照変数自体を保護するにはvolatileかsynchronizedを使うしか無いのですね…

同様の事象は参考HPなども多々ありました。

$さん、長文に付き合って頂きましてありがとうございました!

評価

0

何が問題なのか、やっと分かった。
nullを代入するなんて処理がなくても、厳密にはgetInstance()において裸でnull判定してはいけない。
スレッド1でnull判定

スレッド2でnull判定

スレッド1でnew

スレッド2でnew
という処理になり得る。

基本はsynchronizedでロジックを保護だが、コンストラクタで例外が出ないならフィールドの初期値をnewで指定する。
nullクリアするなら初期値にはできないが。

ところで、途中でシングルトンインスタンスをクリアなんてどういう話なんだろう。
シングルトンの本質は利用する側にインスタンス生成を制御させないことにある。
外部で判断するのであれば、そもそもシングルトンにすべきではない。

評価

0

シングルトンオブジェクトの基本は

----------------------------------
    /** 自身のインスタンス */
    private static final SingletonClass singleton = 
new SingletonClass();
----------------------------------

質問から6ヶ月以上経過しているので、回答を書き込むことはできません。