[Seasar-user:3709] 複数スレッドで同時にcomponentを取得するとロックする(長文)

Asarima [E-MAIL ADDRESS DELETED]
2006年 5月 30日 (火) 13:39:27 JST


Asarimaです。

 私はSeasarを使用したSwingアプリケーションを開発しております。

 今までまったく問題なかったのですが、最近になって、アプリケーションの
ログイン画面からログインした時に、コンテナからコンポーネントを取得する
時にアプリケーションがロックする問題が起こっています。

 大変長くなりますが、もしよろしければ、今から説明することについてより
よい対策があれば教えていただけると助かります。

1.前提とするコード

 まず私は、Mainを以下のように書いています。

---
    // S2コンテナを作成
    S2Container container = (S2Container)S2ContainerFactory.create
        (PATH);
    // ログイン画面をロード
    FrameUI loginFrame = (FrameUI)container.getComponent("loginFrame");
    // ログイン画面を表示
    loginFrame.open();
    // 自動登録を有効にする
    container.init();
---

 ログイン画面のコンポーネントを取得し、それを表示した後にcontainer.init
を実行しています。こうする理由は、ログイン画面を早く表示したいからです。
「早く」というのは、AutoRegisterでかなりの数のコンポーネントを登録するの
で、container.init後にログイン画面を表示していては遅すぎるということです。
 そのために、S2ContainerFactory.createで最低限必要なコンポーネントを登
録し、その後で必要なコンポーネントを自動登録させています。
 FrameUIはJFrameのラップクラスのようなものです。

2.問題を起こすための操作

 ログイン画面が表示されたら、認証情報(いわゆるユーザーIDとパスワード)
を入力してボタンをクリックします。
 これが早すぎると、アプリケーションがロックすることがあります。

3.確認できたこと

 クリックして認証処理を行った後、container.initで登録されるコンポーネン
トの一つを、S2Container.getComponentメソッドで取得しようとしているのです
が、これがコンポーネントを返さずロックします。

 そこで、アプリケーションがロックされている時の各スレッドのスタックト
レースを見て、ある2つのスレッドがwaitしているのを見つけました。

 1つ目のスレッドのスタックトレースは以下の通りです。
 これはcontainer.initによるAutoRegisterの真っ最中かと思います。

---
スレッド [main] (中断中)
    SingletonComponentDeployer.deploy() 行: 50
    ComponentDefImpl.getComponent() 行: 94
    S2ContainerImpl.getComponent(Object) 行: 109
    BindingTypeShouldDef(AbstractBindingTypeDef).bindAuto(ComponentDef,
                PropertyDesc, Object) 行: 75
    BindingTypeShouldDef.doBind(ComponentDef, PropertyDesc, Object) 行: 
        39
    BindingTypeShouldDef(AbstractBindingTypeDef).bind(ComponentDef,
        PropertyDef, PropertyDesc, Object) 行: 64
    AutoPropertyAssembler.assemble(Object) 行: 48
    SingletonComponentDeployer.assemble() 行: 80
    SingletonComponentDeployer.deploy() 行: 51
    SingletonComponentDeployer.init() 行: 96
    ComponentDefImpl.init() 行: 284
    S2ContainerImpl.init() 行: 379
    LoginMain.main(String[]) 行: 43
---

 2つ目のスレッドのスタックトレースは以下の通りです。
 ログイン画面のボタンクリックでコンポーネントを取得するところだと思いま
す。

---
スレッド [AWT-EventQueue-0] (中断中)
    SingletonComponentDeployer.deploy() 行: 50
    ComponentDefImpl.getComponent() 行: 94
    S2ContainerImpl.getComponent(Object) 行: 109
    BindingTypeShouldDef(AbstractBindingTypeDef).bindAuto(ComponentDef,
        PropertyDesc, Object) 行: 75
    BindingTypeShouldDef.doBind(ComponentDef, PropertyDesc, Object) 行:
        39
    BindingTypeShouldDef(AbstractBindingTypeDef).bind(ComponentDef,
        PropertyDef, PropertyDesc, Object) 行: 64
    AutoPropertyAssembler.assemble(Object) 行: 48
    SingletonComponentDeployer.assemble() 行: 80
    SingletonComponentDeployer.deploy() 行: 51
    ComponentDefImpl.getComponent() 行: 94
    S2ContainerImpl.getComponent(Object) 行: 109
    BindingTypeShouldDef(AbstractBindingTypeDef).bindAuto(ComponentDef,
        PropertyDesc, Object) 行: 75
    BindingTypeShouldDef.doBind(ComponentDef, PropertyDesc, Object) 行: 
        39
    BindingTypeShouldDef(AbstractBindingTypeDef).bind(ComponentDef,
        PropertyDef, PropertyDesc, Object) 行: 64
    AutoPropertyAssembler.assemble(Object) 行: 48
    SingletonComponentDeployer.assemble() 行: 80
    SingletonComponentDeployer.deploy() 行: 51
    ComponentDefImpl.getComponent() 行: 94
    S2ContainerImpl.getComponent(Object) 行: 109
    TopMenuManagerImpl.getComponent(Object) 行: 64
    TopMenuManagerImpl.getStartFrameUI() 行: 50
    LoginFrameImpl.btnLoginAction(ActionEvent) 行: 165
    NativeMethodAccessorImpl.invoke0(Method, Object, Object[]) 行:
        使用不可 [ネイティブ・メソッド]
    NativeMethodAccessorImpl.invoke(Object, Object[]) 行: 使用不可
    DelegatingMethodAccessorImpl.invoke(Object, Object[]) 行: 使用不可
    Method.invoke(Object, Object...) 行: 使用不可
    MethodUtil.invoke(Method, Object, Object[]) 行: 42
    ActionMethodInvoker.invokeMethod(AWTEvent) 行: 110
    ActionMethodInvoker.actionPerformed(ActionEvent) 行: 120
    JButton(AbstractButton).fireActionPerformed(ActionEvent) 行:
        使用不可
    AbstractButton$Handler.actionPerformed(ActionEvent) 行: 使用不可
    DefaultButtonModel.fireActionPerformed(ActionEvent) 行: 使用不可
    DefaultButtonModel.setPressed(boolean) 行: 使用不可
    JButton(AbstractButton).doClick(int) 行: 使用不可
    JButton(AbstractButton).doClick() 行: 使用不可
    ButtonEnterActionAdaptor.keyPressed(KeyEvent) 行: 22
    JButton(Component).processKeyEvent(KeyEvent) 行: 使用不可
    JButton(JComponent).processKeyEvent(KeyEvent) 行: 使用不可
    JButton(Component).processEvent(AWTEvent) 行: 使用不可
    JButton(Container).processEvent(AWTEvent) 行: 使用不可
    JButton(Component).dispatchEventImpl(AWTEvent) 行: 使用不可
    JButton(Container).dispatchEventImpl(AWTEvent) 行: 使用不可
    JButton(Component).dispatchEvent(AWTEvent) 行: 使用不可
    DefaultKeyboardFocusManager(KeyboardFocusManager).redispatchEvent(
        Component, AWTEvent) 行: 使用不可
    DefaultKeyboardFocusManager.dispatchKeyEvent(KeyEvent) 行: 使用不可
    DefaultKeyboardFocusManager.preDispatchKeyEvent(KeyEvent) 行:
        使用不可
    DefaultKeyboardFocusManager.typeAheadAssertions(Component, AWTEvent)
        行: 使用不可
    DefaultKeyboardFocusManager.dispatchEvent(AWTEvent) 行: 使用不可
    LoginFrame$$EnhancedByS2AOP$$40ece0(Component).dispatchEventImpl(
        AWTEvent) 行: 使用不可
    LoginFrame$$EnhancedByS2AOP$$40ece0(Container).dispatchEventImpl(
        AWTEvent) 行: 使用不可
    LoginFrame$$EnhancedByS2AOP$$40ece0(Window).dispatchEventImpl(
        AWTEvent) 行: 使用不可
    LoginFrame$$EnhancedByS2AOP$$40ece0(Component).dispatchEvent(
        AWTEvent) 行: 使用不可
    EventQueue.dispatchEvent(AWTEvent) 行: 使用不可
    EventDispatchThread.pumpOneEventForHierarchy(int, Component) 行:
        使用不可
    EventDispatchThread.pumpEventsForHierarchy(int, Conditional, 
        Component) 行: 使用不可
    EventDispatchThread.pumpEvents(int, Conditional) 行: 使用不可
    EventDispatchThread.pumpEvents(Conditional) 行: 使用不可
    EventDispatchThread.run() 行: 使用不可
---

 SingletonComponentDeployer.deployメソッドがwaitしているのでそれを抜き
出し、そこで参照しているcomponentのStringを書いたのが以下です。

---
スレッド [main] (中断中)
    SingletonComponentDeployer.deploy() 行: 50
component= JinjiMenuFrameImpl$$EnhancedByS2AOP$$1a40fff  (ID=4164)
    SingletonComponentDeployer.deploy() 行: 51
component= JinjiDataOutputFrameImpl$$EnhancedByS2AOP$$1ddc55f  (ID=4165)

スレッド [AWT-EventQueue-0] (中断中)
    SingletonComponentDeployer.deploy() 行: 50
component= JinjiDataOutputFrameImpl$$EnhancedByS2AOP$$1ddc55f  (ID=4165)
    SingletonComponentDeployer.deploy() 行: 51
component= JinjiSearchFrameImpl$$EnhancedByS2AOP$$1b1d931  (ID=4166)
    SingletonComponentDeployer.deploy() 行: 51
component= JinjiMenuFrameImpl$$EnhancedByS2AOP$$1a40fff  (ID=4164)
---

4.推測したこと

 これを見て、直感的に、スレッドのデッドロックが起こっているのでは、と
思いました。deployメソッドにはsynchronized修飾子がついています。
 私は、以下のようなことが起こっていると推測しました。

 コンポーネントAがBに依存し、CがDに依存しているとする。
 2つのスレッドでそれぞれ同時にコンポーネントA、Cを取得すると、
AのdeployでBのdeployが発生、CのdeployでDのdeployが発生するため、
CのdeployがAのdeployを待ち、BのdeployがCのdeployを待つので、
デッドロックとなる。

5.推測をもとにして考えた対策

 とにかく複数のスレッドで同時にdeployされないようにします。

(a)container.initを後で実行するのをあきらめる

(b)ログイン画面は表示するが、container.initが終わるまでは
   ログイン後のコンポーネントの取得をwaitする

 以上です。



Seasar-user メーリングリストの案内