2012-08-10 3 views
1

이 주제를 다루는 데있어 어떤 질문도 발견되지 않았으므로 다음과 같은 시나리오에서 솔루션을 공유 할 것이라고 생각했습니다. 그 대답은 명백 할 수도 있지만, 나는 긴 길을 찾아 냈다. :) 질문과 답변뿐 아니라 다른 솔루션에 대한 의견을 보내 주시면 감사하겠습니다.지연로드 게터 동기화시

시나리오 :

것은 당신이 다중 스레드 프로그램을 가지고 프로그램의 다른 부분에서 필요하지 않은 동안 프로그램의 일부 기능에 대한 데이터베이스 연결 (또는 다른 공유 객체를)한다고 가정 모든. 그래도 DB에 단 하나의 연결이 있어야합니다.

동시에 db 연결 손실을 감지하고 즉시 연결을 시도하려고합니다.

연결 대상을 반환하기 전에 연결 유효성을 검사하는 지연로드 패턴 "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를 연결을 얻기 위해 시도 할 수 있습니다. 일부 클래스가 참조를 유지할 때 여러 연결이 공존 할 수도 있습니다.

답변

1

여러 스레드가 거의 동시에 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); 
} 
+0

이것을 'Semaphore' 클래스와 결합 할 수 있습니까? 그렇게하는 것이 합리적입니까? – riha

1

동시에 여러 연결을 만드는 것을 피하기 위해 동기화를 사용할 수 있습니다. 두 개 이상의 스레드가 거의 동시에 호출하면 두 스레드 중 하나는 다른 스레드가 완료 될 때까지 대기합니다 (대기). 이렇게하면 두 번째 스레드가 다른 연결을 설정하는 대신 첫 번째 스레드에 의해 방금 생성 된 연결을 가져옵니다.

는 내가 처음이 같은 개체에 동기화 시도 :

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; 
} 

문제를 여기 IS, 게으른 로딩 작동하지 않습니다. 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은 (드라이버에 따라) 연결 유효성을 확인하기위한 쿼리를 발행 할 수 있기 때문입니다. 각 메소드가 실행되는 동안 레퍼런스를 유지한다면 아마 괜찮을 것이다.

+2

이 모든 것이 학습 경험으로는 좋지만 프로덕션에서 실제로 원하는 것은 심각한 연결 풀 ** 구현으로 여기에서 지적한 문제는 물론 다른 여러 가지 문제도 처리합니다. 당신이 찾고있는 문제. 'boneCP'는 제가 현재 좋아하는 것입니다. –

+0

여전히 여러 스레드가 동일한 연결을 확보 할 수있는 문제가 있습니다. –

1

그럼 여기 내 2C의 당신이 의미에서 나쁜 년대 _db 개체를 사용하는 경우 당신은 당신이 원하는 것을 얻을 수없는 곳. 여기에있는 아이디어는 여러 스레드가 _db ​​인스턴스를 "동시에"(JDK 프로세스에 관한 한) 생성하려고하면 해당 스레드 중 하나가 하나의 인스턴스를 만들고 다른 스레드는 즉시이를 인식해야합니다 그 인스턴스가 존재하고 다른 인스턴스를 만들려고하지 않는다는 것입니다. 이제, 우리가 스레드간에 동기화하려고하는 바로 그 인스턴스에서 코드 블록을 동기화한다면, 비록 그 인스턴스가 결코 null이되지는 않더라도, 두 스레드가 각각을 생성 할 수있는 경합 조건 상황에있게 될 것입니다. 인스턴스 _db 및 코드 블록이 해당 인스턴스에서 동기화되었으므로 실제로 두 개의 개별 잠금이 있으므로 스레드에서 잠금에 의해 차단되지 않습니다. 분명히 전체 메서드를 동기화하는 것이 좋습니다. 이것은 _db 인스턴스를 생성하는 메소드를 호출

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; 
     } 
    } 

모든 스레드를 작성하는 것과 같습니다 것 같은 잠금 (메인 클래스의 인스턴스)를 통해 "싸움"그래서 당신은 스레드가 잠금을 획득 한 번 확신 할 수 있습니다 다른 스레드는 스레드가 완료 될 때까지 차단 한 다음 메소드를 실행할 차례가되면 if check가 _db ​​객체의 두 번째 인스턴스를 만들지 못하게합니다.

다른 문제는 날씨가 정말 여러 스레드에서 동일한 _db 인스턴스를 갖고 싶습니다. 이 질문은 날씨가 정말 낮아집니다. _db는 스레드로부터 안전합니다. 다른 말로하면, 그것은 stateless입니까? 상태가 좋고 여러 스레드가 공유하고 다중 스레드 호출에 대해 해당 상태를 지키지 않으면 이상한 동작과 오류가 발생합니다. 예 : JDBC 연결 객체는 여러 스레드가 동시에 동일한 JDBC 연결에 액세스하는 경우 설명 할 수 없게 변경 될 수있는 트랜잭션과 같은 항목에 대한 상태를 포함하기 때문에 스레드로부터 안전하지 않습니다. 이 때문에 다중 스레드 환경에서 JDBC 연결을 사용할 때 어느 정도의 (객체 인스턴스) 격리를 사용하는 것이 좋습니다. 각 스레드마다 새 JDBC Connection 인스턴스를 만들거나 하나만 만들면 각 스레드 내부에서 ThreadLocal 필드로 유지되어 각 스레드가 실제로 자신이 변경/액세스 할 자신의 인스턴스를 가져옵니다.

또 다른 예로 HasmMap 및 ConcurrentHashMap이 있습니다. 여기에서 여러 스레드에서 동일한 HashMap을 사용하면 오류가 발생합니다 (예 : 하나의 스레드가 맵 항목을 반복하고 다른 스레드가 수정하려고 시도하는 경우 동시 수정 예외가 발생 함) 여러 스레드에서 여러 개의 쓰기를 보내면 Map이 많은 양의 다시 해시 작업을 수행하므로 성능 병 목을 사용할 필요가 없습니다. 반면 ConcurrentHashMap은 하나의 인스턴스가 여러 스레드간에 공유되는 데 적합합니다. 동시 수정 예외가 발생하지 않으며 여러 스레드가 동시에 수정하는 경우 Map의 성능이 훨씬 향상됩니다.

+0

즉, 동기화 된 블록은 참조'_db'에서 동기화되지 않지만 블록이 실행을 시도 할 때 참조되는 객체는 동기화됩니까? – riha

+0

@riha 무슨 뜻인지 잘 모르겠다. 동기화 된 BLOCKS는 인수로 전달 된 인스턴스를 잠금으로 사용합니다. 동기화 된 메소드는 해당 메소드가있는 클래스의 인스턴스를 잠급니다 ("synchronized (this)"블록과 동일합니다). 동기화 된 정적 메소드는 잠금으로 encompasing 클래스를 사용합니다. –

+0

예. 감사합니다. 전에는 동기화 된 블록이 참조에 전달되고 객체 인스턴스에는 할당되지 않았다고 생각했습니다. 실제로 생각하면 바보 같아요. 오, 감사합니다.'Connection'은 스레드로부터 안전하지 않습니다. 나는 그 사실을 정말로 알지 못했다. – riha