2

このトピックをカバーする質問は見つからなかったので、次のシナリオの解決策を共有したいと思いました。答えは明らかかもしれませんが、私は長い道のりをたどって見つけました。:)質問と回答の両方、および他の解決策へのフィードバックをいただければ幸いです。

シナリオ:

マルチスレッドプログラムがあり、プログラムの他の部分ではまったく必要ないのに、プログラムの一部の機能にデータベース接続(または他の共有オブジェクト)が必要であるとします。ただし、dbへの接続は1つだけである必要があります。

同時に、データベース接続の損失を検出し、その場で再接続を試みます。

これをカバーするために、接続オブジェクトを返す前に接続の有効性もチェックする遅延読み込みパターン「getter」を実装します。

コードは次のようになります。

public class Main {
  private DB _db;

  public static void main(String[] args) {
    new Main().start();
  }

  private void start() {
    // Program code goes here
    // You create several threads, some of which may call getDB() whenever they need DB access
  }

  public DB getDB() {
    if (_db == null) {
      _db = getDBConnection();
    } else if (!_db.isConnectionValid()) {
      /*
       * DB connection is not valid anymore. Let's close it and
       * try to get a new connection.
       */
      _db.close();
      _db = getDBConnection();
    }

    return _db;
  }

  private DB getDBConnection() {
    DB db;

    // Obtain a new connection...
    ...

    return db;
  }
}

問題

複数のスレッドがほぼ同時にdb接続を取得しようとする場合があります。一部のクラスがそれらへの参照を保持している場合、複数の接続が共存する可能性さえあります。

4

3 に答える 3

2

同期を使用して、同時に複数の接続を作成しないようにすることができます。2つ(またはそれ以上)のスレッドがほぼ同時にそれを呼び出す場合、それらの1つは、もう1つが終了するまでブロック(待機)します。これにより、別の接続を確立する代わりに、2番目のスレッドが最初のスレッドによって作成されたばかりの接続を取得することが保証されます。

私は最初に次のようにオブジェクトを同期しようとしました:

public DB getDB() {
  synchronized (_db) {
    if (_db == null) {
      _db = getDBConnection();
    } else if (!_db.isConnectionValid()) {
      /*
       * DB connection is not valid anymore. Let's close it and
       * try to get a new connection.
       */
      _db.close();
      _db = getDBConnection();
    }
  }

  return _db;
}

ここでの問題は、遅延読み込みでは機能しないことです。で同期することはできませんnull(を取得しますNullPointerException)が、の最初の呼び出しではまだオブジェクトがありませんgetDB()

解決策は、メソッド全体で同期することです。

public synchronized DB getDB() {
  if (_db == null) {
    _db = getDBConnection();
  } else if (!_db.isConnectionValid()) {
    /*
     * DB connection is not valid anymore. Let's close it and
     * try to get a new connection.
     */
    _db.close();
    _db = getDBConnection();
  }


  return _db;
}

_dbさらに、他のメソッドがプライベートフィールドにアクセスしたり、直接呼び出したりしないようにする必要がありますgetDBConnection()。それはもう同期されません。

デッド接続オブジェクトでのガベージコレクションを防ぐため、クラスは接続への参照を保持しないでください。ただし、getterを頻繁に呼び出すことはお勧めしません。これは、各getがクエリを発行して接続の有効性を確認する場合があるためです(ドライバーによって異なります)。各メソッドが実行中に参照を保持していれば、おそらく問題ありません(何年も実行されない限り)。

于 2012-08-10T10:01:42.673 に答える
2

複数のスレッドがほぼ同時にdb接続を取得しようとする場合があります。一部のクラスがそれらへの参照を保持している場合、複数の接続が共存する可能性さえあります。

その場合、複数の異なるインスタンスを取得できるため、プールが必要です。使用可能なDatabaseConnectionプールは多数あり、一部のJDBCドライバーには独自のプールがあります。JDBCドライバーに付属しているものを使用するか、C3P0などを使用してデータベース接続プールとして機能することをお勧めします。

より具体的には、別のスレッドが同じ接続を取得できないように(接続を取得するだけでなく)接続を取得する必要があります。簡単な例は、キューを使用することです。

private final Queue<DB> freeDBs = new ConcurrentLinkedQueue<>();

public DB acquireDB() {
    DB db = freeDBs.poll();
    if (db != null && db.isConnectionValid()) 
        return db;
    if (db != null)
        db.close();
    return getDBConnection();
}

public void release(DB db) {
    if (freeDBs.size() >= MAX_FREE_SIZE)
        db.close();
    else
        freeDBs.add(db);
}
于 2012-08-10T10:09:37.837 に答える
2

これが私の2cです:

まず、同期を行うために使用するObjectインスタンスに関して、必要なものが得られないという意味で悪い_dbオブジェクトを使用する場合。ここでの考え方は、複数のスレッドが「同時に」_dbインスタンスを作成しようとした場合(JDKプロセスに関する限り)、それらのスレッドの1つが1つのインスタンスを作成すると、他のスレッドがすぐに認識できるようにすることです。そのインスタンスが存在し、別のインスタンスを作成しようとしないこと。さて、スレッド間で同期しようとしているそのインスタンスでコードのブロックを同期すると、そのインスタンスがnullになることは決してない場合でも、2つのスレッドがそれぞれ何とか作成する競合状態の状況になります。 _dbのインスタンスであり、コードブロックはそのインスタンスで同期されているため、どのスレッドもロックによってブロックされません。確かに2つの別々のロックがあるので。明らかに、メソッド全体を同期することをお勧めします。これは書くのと同じです

public DB getDB() {
        synchronized (this) {
            if (_db == null) {
                _db = getDBConnection();
            } else if (!_db.isConnectionValid()) {
                /*
                 * DB connection is not valid anymore. Let's close it and
                 * try to get a new connection.
                 */
                _db.close();
                _db = getDBConnection();
            }
            return _db;
        }
    }

_dbインスタンスを作成するメソッドを呼び出すすべてのスレッドは、同じロック(Mainクラスのインスタンス)で「ファイト」するため、スレッドがそのロックを取得すると、そのスレッドが終了するまで他のスレッドがブロックされることを確認できます。メソッドを実行する番になると、ifチェックにより、_dbオブジェクトの2番目のインスタンスを作成できなくなります。

さて、別の問題は、複数のスレッドにまたがって同じ_dbインスタンスを本当に持ちたい天気です。この質問は本当に天気に還元されます_dbはスレッドセーフですか、言い換えれば、ステートレスですか?ステートフルで複数のスレッドによって共有されている場合、およびその状態がマルチスレッド呼び出しから保護されていない場合は、奇妙な動作やエラーが発生します。例:JDBC接続オブジェクトはスレッドセーフではありません。トランザクションなどの状態が含まれているためです。トランザクションは、複数のスレッドが同時に同じJDBC接続にアクセスする場合に何とも言えないほど変更される可能性があります。このため、マルチスレッド環境でJDBC接続を使用する場合は、ある程度の(オブジェクトインスタンス)分離を使用することをお勧めします。昔ながらのスレッドごとに新しいJDBC接続インスタンスを作成するか、1つだけ作成します。

別の例は、HasmMapとConcurrentHashMapです。ここで、同じHashMapを複数のスレッドで使用すると、必ずエラーが発生します(たとえば、あるスレッドがマップエントリをイタリングし、別のスレッドがマップエントリを変更しようとすると、同時変更例外が発生します)。エラーでない場合は、少なくとも巨大なエラーが発生します。複数のスレッドから複数の書き込みが送信された結果として、マップが多くの再ハッシュを実行するため、パフォーマンスのボトルネック。一方、ConcurrentHashMapは、1つのインスタンスを複数のスレッド間で共有するのに非常に適しています。同時変更の例外は発生せず、複数のスレッドが同時に書き込みを行うと、マップのパフォーマンスが大幅に向上します。

于 2012-08-10T10:28:43.863 に答える