Làm thế nào để các phương thức tĩnh được đồng bộ hóa hoạt động trong Java và tôi có thể sử dụng nó để tải các thực thể Hibernate không?


179

Nếu tôi có một lớp tiện dụng với các phương thức tĩnh sẽ gọi các hàm Hibernate để thực hiện truy cập dữ liệu cơ bản. Tôi tự hỏi nếu làm cho phương pháp synchronizedlà phương pháp phù hợp để đảm bảo an toàn luồng.

Tôi muốn điều này ngăn chặn truy cập thông tin vào cùng một thể hiện DB. Tuy nhiên, bây giờ tôi chắc chắn nếu đoạn mã sau đang ngăn getObjectByIdkhông được gọi cho tất cả các Lớp khi nó được gọi bởi một lớp cụ thể.

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
}

Câu trả lời:


136

Bằng cách sử dụng đồng bộ hóa trên khóa phương thức tĩnh, bạn sẽ đồng bộ hóa các phương thức và thuộc tính lớp (trái ngược với các phương thức và thuộc tính thể hiện)

Vì vậy, giả định của bạn là chính xác.

Tôi tự hỏi nếu làm cho phương thức được đồng bộ hóa là phương pháp phù hợp để đảm bảo an toàn luồng.

Không hẳn vậy. Bạn nên để RDBMS của bạn làm việc đó thay thế. Họ giỏi loại công cụ này.

Điều duy nhất bạn sẽ nhận được bằng cách đồng bộ hóa quyền truy cập vào cơ sở dữ liệu là làm cho ứng dụng của bạn chậm khủng khiếp. Hơn nữa, trong mã bạn đã đăng bạn đang xây dựng một Factory Factory mỗi lần, theo cách đó, ứng dụng của bạn sẽ dành nhiều thời gian truy cập DB hơn là thực hiện công việc thực tế.

Hãy tưởng tượng kịch bản sau đây:

Khách hàng A và B cố gắng chèn thông tin khác nhau vào bản ghi X của bảng T.

Với cách tiếp cận của bạn, điều duy nhất bạn nhận được là đảm bảo cái này được gọi sau cái kia, khi điều này xảy ra dù sao trong DB, bởi vì RDBMS sẽ ngăn chúng chèn một nửa thông tin từ A và một nửa từ B cùng một lúc . Kết quả sẽ giống nhau nhưng chỉ chậm hơn 5 lần (hoặc hơn).

Có lẽ tốt hơn là hãy xem chương "Giao dịch và đồng thời" trong tài liệu Hibernate. Hầu hết các vấn đề bạn đang cố gắng giải quyết, đã được giải quyết và cách tốt hơn nhiều.


1
Câu trả lời rất hữu ích! CẢM ƠN! Vì vậy, Hibernate chăm sóc cnocurrency bằng cách "khóa lạc quan". Sau đó, không cần phải sử dụng các phương thức "đồng bộ hóa" để giải quyết bất kỳ sự đồng tình truy cập dữ liệu nào ?? Chỉ sử dụng các phương thức "đồng bộ hóa" nếu dữ liệu không được lưu trữ trong cơ sở dữ liệu ?? .. khi nào bạn sử dụng chúng ??
cà chua

1
1) Tôi nghĩ rằng có một số phương tiện để sử dụng khóa bi quan quá. 2) Không, RDBMS có thể làm công việc đó. 3) Nếu dữ liệu được truy cập bởi nhiều luồng cùng một lúc. 4) đồng bộ hóa hữu ích khi hai luồng phải chia sẻ dữ liệu. Nếu họ không cần, thì tốt hơn nhiều!
OscarRyz

7
Bất kỳ nhà hàng thức ăn nhanh sử dụng đa luồng. Một chuỗi sẽ đưa bạn đặt hàng và sử dụng một luồng khác để chuẩn bị, và tiếp tục với khách hàng tiếp theo. Điểm đồng bộ hóa chỉ hoạt động khi họ trao đổi thông tin để biết phải chuẩn bị gì. Theo một mô hình như thế thực sự đơn giản hóa cuộc sống.
OscarRyz

5
"Cả lớp" không bị khóa. Đặc tả ngôn ngữ máy 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.Do đó, nếu một luồng vào một phương thức tĩnh, cùng một đối tượng được trả về bởi Object # getClass bị khóa. Các chủ đề khác vẫn có thể truy cập các phương thức thể hiện.
Martin Andersson

4
lol tôi thấy rằng từ ngữ của riêng tôi cuối cùng cũng không đúng. Tôi đã nói "Vì vậy, nếu một luồng vào một phương thức tĩnh, cùng một đối tượng được trả về bởi Object # getClass bị khóa". Không đúng kỹ thuật. Câu chuyện dài được viết tắt cho tất cả những người tò mò: Đối với mỗi lớp trong ứng dụng của bạn, tồn tại một Classđối tượng, được khởi tạo bởi một trong các trình nạp lớp máy ảo. Giống như tất cả các đối tượng, đối tượng này cũng có Monitorliên quan đến nó. Và màn hình này là những gì đang bị khóa.
Martin Andersson

233

Để giải quyết câu hỏi nói chung hơn ...

Hãy nhớ rằng việc sử dụng đồng bộ hóa trên các phương thức thực sự chỉ là tốc ký (giả sử lớp là someClass):

synchronized static void foo() {
    ...
}

giống như

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

synchronized void foo() {
    ...
}

giống như

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

Bạn có thể sử dụng bất kỳ đối tượng như khóa. Nếu bạn muốn khóa các tập con của phương thức tĩnh, bạn có thể

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) {...}
    }
}

(đối với các phương thức không tĩnh, bạn sẽ muốn làm cho các khóa là các trường không tĩnh)


9
4 khối mã hàng đầu là vàng. Chính xác những gì tôi đang tìm kiếm. Cảm ơn bạn.
Ryan Shillington

Có đúng không nếu tôi sử dụng Khóa tĩnh trên phương thức không tĩnh, không có hai đối tượng nào của lớp someClass có thể chạy khối cùng một lúc?
Samuel

2
@Samuel - Hầu như ... Đó là về chủ đề hơn là các đối tượng. Bạn đã đúng ở chỗ các phiên bản riêng biệt của someClass đều sẽ sử dụng cùng một khóa / màn hình: một phiên bản được liên kết với đối tượng Someclass. Class. Vì vậy, nếu hai luồng khác nhau đang xử lý hai phiên bản khác nhau của someClass, cả hai đều không thể chạy cùng một lúc. Tuy nhiên, nếu một luồng duy nhất được gọi là một phương thức trong một phiên bản của someClass và phương thức đó được gọi là một phương thức trong trường hợp khác, thì việc chặn sẽ không xảy ra.
Scott Stanchfield

@ScottStanchfield Bạn đã liệt kê các cách để đồng bộ hóa các phương thức, tất cả chúng có tương đương không?
Bionix1441

1
@ Bionix1441 - Đó là tất cả về phạm vi. Mỗi cơ chế trên cho phép bạn kiểm soát khóa tốt hơn. Đầu tiên, sử dụng chính cá thể khóa toàn bộ một phương thức, sau đó chính cá thể đó để khóa các phần bên trong một phương thức, sau đó bất kỳ đối tượng nào để khóa các phần.
Scott Stanchfield

17

Các phương thức tĩnh sử dụng lớp làm đối tượng để khóa, đó là Utils. Class cho ví dụ của bạn. Vì vậy, có, nó là OK.


14

static synchronizedphương tiện giữ khóa trên của lớp Classđối tượng nơi như synchronizedphương tiện giữ khóa trên đối tượng của lớp đó chính nó. Điều đó có nghĩa là, nếu bạn đang truy cập một phương thức được đồng bộ hóa không tĩnh trong một luồng (thực thi), bạn vẫn có thể truy cập một phương thức được đồng bộ hóa tĩnh bằng cách sử dụng một luồng khác.

Vì vậy, việc truy cập hai loại phương thức giống nhau (hai phương thức tĩnh hoặc hai phương thức không tĩnh) tại bất kỳ thời điểm nào nhiều hơn một luồng là không thể.


10

Tại sao bạn muốn thực thi rằng chỉ một luồng duy nhất có thể truy cập DB bất cứ lúc nào?

Đây là công việc của trình điều khiển cơ sở dữ liệu là thực hiện bất kỳ khóa cần thiết nào, giả sử một Connectionchỉ được sử dụng bởi một luồng tại một thời điểm!

Rất có thể, cơ sở dữ liệu của bạn hoàn toàn có khả năng xử lý nhiều truy cập song song


Tôi cá là nó là / một cách giải quyết cho một số vấn đề giao dịch. Tức là, giải pháp không giải quyết được vấn đề thực sự
matt b

1
Tôi không biết điều đó .... Tôi nghĩ rằng tôi phải tự thực hiện việc này. Cảm ơn đã chỉ ra điều đó! :)
cà chua

2

Nếu đó là một cái gì đó để làm với dữ liệu trong cơ sở dữ liệu của bạn, tại sao không sử dụng khóa cách ly cơ sở dữ liệu để đạt được?


Tôi không có bất kỳ nền tảng cơ sở dữ liệu. Giờ thì tôi đã biết !! Cảm ơn đã chỉ ra điều đó! :)
cà chua

2

Để trả lời câu hỏi của bạn, đúng vậy: synchronizedphương pháp của bạn không thể được thực thi bởi nhiều hơn một luồng cùng một lúc.


2

Như thế nào synchronized hoạt động của từ khóa Java

Khi bạn thêm synchronized từ khóa vào một phương thức tĩnh, phương thức chỉ có thể được gọi bởi một luồng duy nhất tại một thời điểm.

Trong trường hợp của bạn, mọi cuộc gọi phương thức sẽ:

  • tạo một cái mới SessionFactory
  • tạo một cái mới Session
  • lấy thực thể
  • trả lại thực thể cho người gọi

Tuy nhiên, đây là những yêu cầu của bạn:

  • Tôi muốn điều này để ngăn truy cập thông tin vào cùng một thể hiện DB.
  • ngăn chặn getObjectByIdđược gọi cho tất cả các lớp khi nó được gọi bởi một lớp cụ thể

Vì vậy, ngay cả khi getObjectByIdphương thức này là an toàn luồng, việc thực hiện là sai.

SessionFactory thực hành tốt nhất

SessionFactorylà an toàn cho luồng và nó là một đối tượng rất tốn kém để tạo vì nó cần phân tích các lớp thực thể và xây dựng biểu diễn siêu mô hình thực thể bên trong.

Vì vậy, bạn không nên tạo cuộc gọi SessionFactorytrên mọi getObjectByIdphương thức.

Thay vào đó, bạn nên tạo một ví dụ singleton cho nó.

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

Các Sessionnên luôn luôn được đóng lại

Bạn đã không đóng Sessiontrong một finallykhối và điều này có thể rò rỉ tài nguyên cơ sở dữ liệu nếu một ngoại lệ được ném khi tải thực thể.

Theo Session.loadphương thức JavaDoc có thể đưa ra một HibernateExceptionthực thể nếu không thể tìm thấy thực thể trong cơ sở dữ liệu.

Bạn không nên sử dụng phương pháp này để xác định xem có tồn tại một thể hiện hay không (sử dụng get()thay thế). Chỉ sử dụng điều này để truy xuất một thể hiện mà bạn cho là tồn tại, trong đó sự không tồn tại sẽ là một lỗi thực tế.

Đó là lý do tại sao bạn cần sử dụng một finallykhối để đóng Session, như thế này:

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

Ngăn chặn truy cập đa luồng

Trong trường hợp của bạn, bạn muốn đảm bảo chỉ có một luồng được truy cập vào thực thể cụ thể đó.

Nhưng synchronizedtừ khóa chỉ ngăn hai chủ đề gọigetObjectById đồng thời. Nếu hai luồng gọi phương thức này lần lượt theo cách khác, bạn vẫn sẽ có hai luồng sử dụng thực thể này.

Vì vậy, nếu bạn muốn khóa một đối tượng cơ sở dữ liệu nhất định để không có luồng nào khác có thể sửa đổi nó, thì bạn cần sử dụng khóa cơ sở dữ liệu.

Các synchronizedtừ khóa chỉ hoạt động trong một JVM duy nhất. Nếu bạn có nhiều nút web, điều này sẽ không ngăn truy cập đa luồng trên nhiều JVM.

Những gì bạn cần làm là sử dụng LockModeType.PESSIMISTIC_READhoặcLockModeType.PESSIMISTIC_WRITE trong khi áp dụng các thay đổi cho DB, như thế này:

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

Vì vậy, đây là những gì tôi đã làm:

  • Tôi đã tạo một cái mới EntityTransaction giao dịch và bắt đầu một giao dịch cơ sở dữ liệu mới
  • Tôi đã tải Post thực thể trong khi giữ một khóa trên bản ghi cơ sở dữ liệu liên quan
  • Tôi đã thay đổi Post thực thể và cam kết giao dịch
  • Trong trường hợp Exceptionbị ném, tôi quay lại giao dịch

Để biết thêm chi tiết về ACID và giao dịch cơ sở dữ liệu, hãy xem bài viết này .

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.