同期された静的メソッドはJavaでどのように機能し、それをHibernateエンティティのロードに使用できますか?


179

基本的なデータアクセスを実行するためにHibernate関数を呼び出す静的メソッドを持つutilクラスがある場合。メソッドの作成がsynchronizedスレッドの安全性を確保するための適切なアプローチであるかどうか疑問に思っています。

これにより、同じDBインスタンスへの情報へのアクセスを防ぐことができます。ただし、次のコードがgetObjectById特定のクラスから呼び出されたときに、すべてのクラスに対して呼び出されるのを妨げているかどうかは確信しています。

public class Utils {
     public static synchronized Object getObjectById (Class objclass, Long id) {
           // call hibernate class
         Session session = new Configuration().configure().buildSessionFactory().openSession();
         Object obj = session.load(objclass, id);
         session.close();
         return obj;
     }

     // other static methods
}

回答:


136

静的メソッドロックで同期を使用することで、クラスのメソッドと属性同期します(インスタンスのメソッドと属性ではなく)

だからあなたの仮定は正しいです。

メソッドを同期させることがスレッドセーフを確保するための正しいアプローチかどうか疑問に思っています。

あんまり。代わりに、RDBMSにその機能を実行させる必要があります。彼らはこの種のものを得意としています。

データベースへのアクセスを同期することによって得られる唯一のことは、アプリケーションをひどく遅くすることです。さらに、投稿したコードで毎回セッションファクトリを構築しているため、アプリケーションは実際のジョブを実行するよりもDBへのアクセスに多くの時間を費やします。

次のシナリオを想像してみてください。

クライアントAとBは、テーブルTのレコードXに異なる情報を挿入しようとします。

あなたのアプローチでは、RDBMSがAからの半分の情報とBからの半分の情報を同時に挿入するのを防ぐので、これがDBでとにかく発生するとき、次々に呼び出されることを確認することだけが得られます。 。結果は同じですが、遅くなるのは5倍(またはそれ以上)だけです。

おそらく、Hibernateドキュメントの「Transactions and Concurrency」の章を参照することをお勧めします。ほとんどの場合、解決しようとしている問題はすでに解決されており、はるかに優れた方法です。


1
非常に役立つ回答です。ありがとうございます。したがって、Hibernateは「楽観的ロック」によってcnocurrencyを処理します。次に、データアクセスの一致を解決するために、「同期された」メソッドを使用する必要はありませんか?データがデータベースに保存されていない場合にのみ、「同期」メソッドを使用しますか?..いつ使用しますか?
トマト

1
1)悲観的ロックを使用する方法もいくつかあると思います。2)いいえ、RDBMSはそれを行うことができます。3)データが複数のスレッドによって同時にアクセスされる場合。4)同期は、2つのスレッドがデータを共有する必要がある場合に役立ちます。彼らがする必要がない場合は、はるかに良いです!
OscarRyz

7
どのファーストフード店でもマルチスレッドを使用しています。1つのスレッドで注文し、別のスレッドを使用して準備し、次の顧客に続きます。同期ポイントは、何を準備するかを知るために情報を交換する場合にのみ機能します。そのようなモデルに従うと、本当に人生が簡単になります。
OscarRyz

5
「クラス全体」はロックされていませんJavaの機械言語仕様For a class (static) method, the monitor associated with the Class object for the method's class is used. For an instance method, the monitor associated with this (the object for which the method was invoked) is used.つのスレッドが静的メソッドに入った場合このように、同一のオブジェクトによって返されたオブジェクト#はgetClassがロックされています。他のスレッドは引き続きインスタンスメソッドにアクセスできます。
マーティンアンダーソン、2013

4
笑私自身の言い回しも、最終的には正しくないことがわかりました。「したがって、1つのスレッドが静的メソッドに入ると、Object#getClassによって返される同じオブジェクトがロックされます」と言いました。技術的に正しくない。長い話はすべての好奇心旺盛な人々にとって短くなります。アプリケーションの各クラスにClassは、仮想マシンクラスローダーの1つによってインスタンス化されたオブジェクトが存在します。すべてのオブジェクトと同様に、このオブジェクトにもMonitor関連付けられています。そして、このモニターはロックされているものです。
マーティンアンダーソン、

233

より一般的な質問に対処するには...

同期されたメソッドを使用することは、実際には省略形であることに注意してください(クラスがSomeClassであると想定)

synchronized static void foo() {
    ...
}

と同じです

static void foo() {
    synchronized(SomeClass.class) {
        ...
    }
}

そして

synchronized void foo() {
    ...
}

と同じです

void foo() {
    synchronized(this) {
        ...
    }
}

ロックとして任意のオブジェクトを使用できます。静的メソッドのサブセットをロックしたい場合は、

class SomeClass {
    private static final Object LOCK_1 = new Object() {};
    private static final Object LOCK_2 = new Object() {};
    static void foo() {
        synchronized(LOCK_1) {...}
    }
    static void fee() {
        synchronized(LOCK_1) {...}
    }
    static void fie() {
        synchronized(LOCK_2) {...}
    }
    static void fo() {
        synchronized(LOCK_2) {...}
    }
}

(非静的メソッドの場合、ロックを非静的フィールドにする必要があります)


9
これらの上位4つのコードブロックはゴールドです。まさに私が探していたもの。ありがとうございました。
Ryan Shillington

非静的メソッドで静的ロックを使用する場合、SomeClassクラスの2つのオブジェクトが同時にブロックを実行できないことは正しいですか?
Samuel、

2
@Samuel-ほとんど...それはオブジェクトのインスタンスよりもスレッドについてです。SomeClassの個別のインスタンスはすべて同じロック/モニター(Someclass.classオブジェクトに関連付けられたもの)を使用するという点で正しいです。したがって、2つの異なるスレッドがSomeClassの2つの異なるインスタンスを処理していた場合、それらは同時に実行できませんでした。ただし、SomeClassの1つのインスタンスで単一のスレッドがメソッドを呼び出し、そのメソッドが別のインスタンスでメソッドを呼び出した場合、ブロッキングは発生しません。
Scott Stanchfield、2015

@ScottStanchfieldメソッドを同期する方法をリストしましたが、それらはすべて同等ですか?
Bionix1441 2017年

1
@ Bionix1441-それはすべてスコープに関するものです。上記の各メカニズムにより、ロックをより細かく制御できます。まず、インスタンス自体を使用してメソッド全体をロックし、次にインスタンス自体を使用してメソッド内のセクションをロックし、次に任意のオブジェクトインスタンスを使用してセクションをロックします。
Scott Stanchfield 2017年

17

静的メソッドは、クラスをロックのオブジェクトとして使用します。これは、例ではUtils.classです。はい、大丈夫です。


14

static synchronized クラスのロックを保持することを意味します Classオブジェクトにsynchronizedロックを保持することを意味し、as はクラスのオブジェクト自体にロックを保持することを意味します。つまり、(実行の)スレッドで非静的同期メソッドにアクセスしている場合でも、別のスレッドを使用して静的同期メソッドにアクセスできます。

したがって、任意の時点で、2つ以上の同じ種類のメソッド(2つの静的メソッドまたは2つの非静的メソッド)にスレッド以外の方法でアクセスすることはできません。


10

一度に1つのスレッドのみがDBにアクセスできるように強制するのはなぜですか?

必要なロックを実装するのは、データベースドライバーの仕事です。Connectionが一度に1つのスレッドでのみ使用されるとです。

ほとんどの場合、データベースは複数の並列アクセスを完全に処理できます


トランザクションの問題の回避策である、または回避策であったに違いありません。つまり、ソリューションは真の問題を解決しません
matt b

1
私はそれを知りませんでした...手動でこれを実装する必要があると思いました。指摘してくれてありがとう!:)
トマト

2

それがデータベースのデータに関係している場合は、データベース分離ロックを利用して実現してみませんか?


データベースに関する知識はありません。今私は知っている!!指摘してくれてありがとう!:)
トマト

2

あなたの質問に答えるために、はいそれはします:あなたのsynchronizedメソッドは一度に複数のスレッドによって実行されることはできません。


2

どのようにsynchronizedJavaのキーワード作品

synchronized静的メソッドにキーワードを追加すると、そのメソッドは一度に1つのスレッドからのみ呼び出すことができます。

あなたの場合、すべてのメソッド呼び出しは次のようになります:

  • 新しいを作成します SessionFactory
  • 新しいを作成します Session
  • エンティティをフェッチする
  • エンティティを呼び出し元に返す

ただし、これらはあなたの要件でした:

  • これにより、同じDBインスタンスへの情報へのアクセスを防ぐことができます。
  • 防止は、getObjectByIdそれが特定のクラスによって呼び出されたときに、すべてのクラスのために呼び出されています

したがって、getObjectByIdメソッドがスレッドセーフであっても、実装は間違っています。

SessionFactory ベストプラクティス

SessionFactoryスレッドセーフであり、それはそれはエンティティクラスを解析し、内部実体のメタモデル表現を構築する必要があるとして作成するには、非常に高価な物です。

したがって、SessionFactoryすべてを作成する必要はありませんgetObjectByIdメソッド呼び出しでを。

代わりに、そのためのシングルトンインスタンスを作成する必要があります。

private static final SessionFactory sessionFactory = new Configuration()
    .configure()
    .buildSessionFactory();

Session必ず閉じる必要があります

あなたはで閉じませんでしSessionfinallyブロックたため、エンティティの読み込み中に例外がスローされた場合、データベースリソースがリークする可能性があります。

Session.loadメソッドによると、HibernateExceptionエンティティがデータベースで見つからない場合、JavaDocはをスローする可能性があります。

このメソッドを使用してインスタンスが存在するかどうかを判断しないでください(get()代わりに使用してください)。これは、存在しないことが実際のエラーになる、存在すると想定するインスタンスを取得する場合にのみ使用してください。

そのfinallyためSession、次のようにブロックを使用してを閉じる必要があります。

public static synchronized Object getObjectById (Class objclass, Long id) {    
     Session session = null;
     try {
         session = sessionFactory.openSession();
         return session.load(objclass, id);
     } finally {
         if(session != null) {
             session.close(); 
         }
     }
 }

マルチスレッドアクセスの防止

あなたの場合、1つのスレッドだけがその特定のエンティティにアクセスできるようにする必要がありました。

ただし、このsynchronizedキーワードは、2つのスレッドがgetObjectById同時にです。2つのスレッドがこのメソッドを順番に呼び出す場合でも、このエンティティを使用する2つのスレッドがあります。

したがって、他のスレッドがオブジェクトを変更できないように特定のデータベースオブジェクトをロックする場合は、データベースロックを使用する必要があります。

synchronizedキーワードは、単一のJVMで動作します。複数のWebノードがある場合、これは複数のJVMにわたるマルチスレッドアクセスを妨げません。

あなたがする必要があるのは、次のように、DBに変更を適用するLockModeType.PESSIMISTIC_READLockModeType.PESSIMISTIC_WRITE使用することです:

Session session = null;
EntityTransaction tx = null;

try {
    session = sessionFactory.openSession();

    tx = session.getTransaction();
    tx.begin();

    Post post = session.find(
        Post.class, 
        id, 
        LockModeType.LockModeType.PESSIMISTIC_READ
    );

    post.setTitle("High-Performance Java Perisstence");

    tx.commit();
} catch(Exception e) {
    LOGGER.error("Post entity could not be changed", e);
    if(tx != null) {
        tx.rollback(); 
    }
} finally {
    if(session != null) {
        session.close(); 
    }
}

だから、これは私がやったことです:

  • EntityTransaction新しいデータベーストランザクションを作成して開始しました
  • Post関連するデータベースレコードをロックしている間にエンティティをロードしました
  • Postエンティティを変更してトランザクションをコミットしました
  • Exceptionスローされた場合、トランザクションをロールバックしました

ACIDとデータベーストランザクションの詳細については、こちらの記事もご覧ください。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.