Tại sao nên sử dụng ReentrantLock nếu người ta có thể sử dụng đồng bộ hóa (cái này)?


317

Tôi đang cố gắng hiểu điều gì làm cho khóa đồng thời trở nên quan trọng nếu người ta có thể sử dụng synchronized (this). Trong mã giả dưới đây, tôi có thể làm một trong hai cách sau:

  1. đồng bộ hóa toàn bộ phương thức hoặc đồng bộ hóa khu vực dễ bị tổn thương ( synchronized(this){...})
  2. HOẶC khóa vùng mã dễ bị tấn công bằng ReentrantLock.

Mã số:

    private final ReentrantLock lock = new ReentrantLock(); 
    private static List<Integer> ints;

    public Integer getResult(String name) { 
        .
        .
        .
        lock.lock();
        try {
            if (ints.size()==3) {
                ints=null;
                return -9;
            }   

            for (int x=0; x<ints.size(); x++) {
                System.out.println("["+name+"] "+x+"/"+ints.size()+". values >>>>"+ints.get(x));
            }

        } finally {
            lock.unlock();
        } 
        return random;
}

1
BTW tất cả các khóa java nội tại được reentrant tự nhiên.
Aniket Thakur

@pongapundit vì vậy synchronized(this){synchronized(this){//some code}}sẽ không gây ra khóa chết. Đối với khóa nội tại nếu họ có được màn hình trên một tài nguyên và nếu họ muốn nó một lần nữa, họ có thể lấy nó mà không cần khóa chết.
Aniket Thakur

Câu trả lời:


474

Một ReentrantLockkhông có cấu trúc , không giống như synchronizedcấu trúc - tức là bạn không cần phải sử dụng một cấu trúc khối cho khóa và thậm chí có thể tổ chức một khóa trên phương pháp. Một ví dụ:

private ReentrantLock lock;

public void foo() {
  ...
  lock.lock();
  ...
}

public void bar() {
  ...
  lock.unlock();
  ...
}

Dòng chảy như vậy là không thể biểu diễn thông qua một màn hình duy nhất trong một synchronizedcấu trúc.


Bên cạnh đó, ReentrantLockhỗ trợ bỏ phiếu khóakhóa gián đoạn chờ hỗ trợ hết thời gian . ReentrantLockcũng có hỗ trợ cho chính sách công bằng cấu hình , cho phép lập lịch trình linh hoạt hơn.

Hàm tạo cho lớp này chấp nhận một tham số công bằng tùy chọn . Khi được đặt true, dưới sự tranh chấp, các khóa sẽ ưu tiên cấp quyền truy cập cho chuỗi chờ đợi lâu nhất. Nếu không, khóa này không đảm bảo bất kỳ thứ tự truy cập cụ thể. Các chương trình sử dụng khóa công bằng được truy cập bởi nhiều luồng có thể hiển thị thông lượng tổng thể thấp hơn (nghĩa là chậm hơn, thường chậm hơn nhiều) so với các chương trình sử dụng cài đặt mặc định, nhưng có thời gian nhỏ hơn để có được khóa và đảm bảo thiếu đói. Tuy nhiên, lưu ý rằng tính công bằng của khóa không đảm bảo tính công bằng của lập lịch luồng. Do đó, một trong nhiều luồng sử dụng khóa công bằng có thể có được nó nhiều lần liên tiếp trong khi các luồng hoạt động khác không tiến triển và hiện không giữ khóa. Cũng lưu ý rằng không sơn lóttryLockphương pháp không tôn vinh các thiết lập công bằng. Nó sẽ thành công nếu khóa có sẵn ngay cả khi các luồng khác đang chờ.


ReentrantLock cũng có thể có khả năng mở rộng hơn , hoạt động tốt hơn nhiều trong sự tranh chấp cao hơn. Bạn có thể đọc thêm về điều này ở đây .

Yêu cầu này đã được tranh luận, tuy nhiên; xem bình luận sau:

Trong thử nghiệm khóa reentrant, một khóa mới được tạo ra mỗi lần, do đó không có khóa độc quyền và dữ liệu kết quả là không hợp lệ. Ngoài ra, liên kết IBM không cung cấp mã nguồn cho điểm chuẩn cơ bản để không thể xác định liệu thử nghiệm có được tiến hành chính xác hay không.


Khi nào bạn nên sử dụng ReentrantLocks? Theo bài viết trên ...

Câu trả lời khá đơn giản - sử dụng nó khi bạn thực sự cần thứ gì đó mà nó cung cấp synchronizedkhông như chờ đợi khóa thời gian, chờ khóa gián đoạn, khóa không có cấu trúc khối, nhiều biến điều kiện hoặc bỏ phiếu khóa. ReentrantLockcũng có lợi ích về khả năng mở rộng và bạn nên sử dụng nó nếu bạn thực sự có một tình huống thể hiện sự tranh chấp cao, nhưng hãy nhớ rằng phần lớn các synchronizedkhối hầu như không bao giờ thể hiện bất kỳ sự tranh chấp nào, chứ đừng nói là tranh chấp cao. Tôi sẽ khuyên bạn nên phát triển với đồng bộ hóa cho đến khi đồng bộ hóa được chứng minh là không đầy đủ, thay vì chỉ đơn giản là giả sử "hiệu suất sẽ tốt hơn" nếu bạn sử dụngReentrantLock. Hãy nhớ rằng, đây là những công cụ nâng cao cho người dùng nâng cao. (Và người dùng thực sự tiên tiến có xu hướng thích các công cụ đơn giản nhất họ có thể tìm thấy cho đến khi họ tin rằng các công cụ đơn giản là không đủ.) Như mọi khi, hãy làm cho đúng trước, sau đó lo lắng về việc bạn có phải làm cho nó nhanh hơn hay không.


26
Liên kết 'được biết là có khả năng mở rộng hơn' đến lycog.com nên bị xóa. Trong thử nghiệm khóa reentrant, một khóa mới được tạo ra mỗi lần, do đó không có khóa độc quyền và dữ liệu kết quả là không hợp lệ. Ngoài ra, liên kết IBM không cung cấp mã nguồn cho điểm chuẩn cơ bản để không thể xác định liệu thử nghiệm có được tiến hành chính xác hay không. Cá nhân, tôi chỉ xóa toàn bộ dòng về khả năng mở rộng, vì toàn bộ yêu cầu về cơ bản không được hỗ trợ.
Dev

2
Tôi đã sửa đổi bài viết trong ánh sáng phản ứng của bạn.
oldrinb

6
Nếu hiệu suất là mối quan tâm cao đối với bạn, đừng quên tìm kiếm một cách mà bạn hoàn toàn KHÔNG cần đồng bộ hóa.
mcoolive

2
Điều hiệu suất không có ý nghĩa với tôi cả. Nếu khóa reantrant sẽ hoạt động tốt hơn thì tại sao không đồng bộ hóa không chỉ được thực hiện giống như cách khóa reantrant trên thực tế?
tObi

2
@ ReentrantLockPseudoRandomuser2761895 mã trong liên kết Lycog đang sử dụng khóa hoàn toàn mới, không bị kiểm soát mỗi lần gọi setSeednext
oldrinb

14

ReentrantReadWriteLocklà một khóa chuyên dụng trong khi đó synchronized(this)là một khóa mục đích chung. Chúng tương tự nhau nhưng không hoàn toàn giống nhau.

Bạn đúng ở chỗ bạn có thể sử dụng synchronized(this)thay vì ReentrantReadWriteLocknhưng điều ngược lại không phải lúc nào cũng đúng.

Nếu bạn muốn hiểu rõ hơn điều gì làm cho ReentrantReadWriteLockđặc biệt tìm kiếm một số thông tin về đồng bộ hóa chủ đề của nhà sản xuất-người tiêu dùng.

Nói chung, bạn có thể nhớ rằng đồng bộ hóa toàn bộ phương thức và đồng bộ hóa mục đích chung (sử dụng synchronizedtừ khóa) có thể được sử dụng trong hầu hết các ứng dụng mà không cần suy nghĩ quá nhiều về ngữ nghĩa của đồng bộ hóa nhưng nếu bạn cần loại bỏ hiệu suất khỏi mã của mình, bạn có thể cần phải thực hiện khám phá các cơ chế đồng bộ hóa hạt mịn hoặc mục đích đặc biệt khác.

Nhân tiện, việc sử dụng synchronized(this)- và nói chung là khóa bằng cách sử dụng một thể hiện của lớp công khai - có thể có vấn đề vì nó mở mã của bạn đến các khóa chết tiềm năng bởi vì ai đó không cố ý có thể cố gắng khóa đối tượng của bạn ở một nơi khác trong chương trình.


để ngăn chặn các khóa chết tiềm năng vì ai đó không cố ý có thể cố gắng khóa đối tượng của bạn ở một nơi khác trong progame, hãy sử dụng một đối tượng riêng tư làm đồng bộ hóa Giám sát như thế này: public class MyLock { private final Object protectedLongLockingMonitor = new Object(); private long protectedLong = 0L; public void incrementProtectedLong() { synchronized(protectedLongLockingMonitor) { protectedLong++; } } }
sushicutta

9

Từ trang tài liệu oracle về ReentrantLock :

Khóa loại trừ lẫn nhau được gửi lại với cùng một hành vi và ngữ nghĩa cơ bản giống như khóa màn hình ngầm được truy cập bằng các phương thức và câu lệnh được đồng bộ hóa, nhưng có khả năng mở rộng.

  1. Một ReentrantLock thuộc sở hữu của các chủ đề mới nhất khóa thành công, nhưng vẫn chưa mở khóa nó. Một khóa gọi luồng sẽ trở lại, có được khóa thành công, khi khóa không thuộc sở hữu của một luồng khác. Phương thức sẽ trả về ngay lập tức nếu luồng hiện tại đã sở hữu khóa.

  2. Hàm tạo cho lớp này chấp nhận một tham số công bằng tùy chọn . Khi được đặt thành đúng, dưới sự tranh chấp, các khóa sẽ ưu tiên cấp quyền truy cập cho chuỗi chờ đợi lâu nhất . Nếu không, khóa này không đảm bảo bất kỳ thứ tự truy cập cụ thể.

Các tính năng chính của ReentrantLock theo bài viết này

  1. Khả năng khóa gián đoạn.
  2. Khả năng hết thời gian chờ trong khi chờ khóa.
  3. Sức mạnh để tạo khóa công bằng.
  4. API để có được danh sách các chuỗi chờ cho khóa.
  5. Linh hoạt để thử khóa mà không chặn.

Bạn có thể sử dụng ReentrantReadWriteLock.ReadLock, ReentrantReadWriteLock.WriteLock để tiếp tục kiểm soát khóa hạt trên các hoạt động đọc và ghi.

Hãy xem bài viết này của Stewamen về việc sử dụng các loại ReentrantLocks khác nhau


2

Bạn có thể sử dụng khóa reentrant với chính sách công bằng hoặc thời gian chờ để tránh chết đói luồng. Bạn có thể áp dụng một chính sách công bằng chủ đề. nó sẽ giúp tránh một luồng chờ mãi mãi để lấy tài nguyên của bạn.

private final ReentrantLock lock = new ReentrantLock(true);
//the param true turns on the fairness policy. 

"Chính sách công bằng" chọn luồng có thể chạy tiếp theo để thực thi. Nó dựa trên mức độ ưu tiên, thời gian kể từ lần chạy trước, blah blah

Ngoài ra, Đồng bộ hóa có thể chặn vô thời hạn nếu không thể thoát khỏi khối. Reentrantlock có thể có thời gian chờ thiết lập.


1

Khóa được đồng bộ hóa không cung cấp bất kỳ cơ chế nào của hàng đợi trong đó sau khi thực hiện một luồng, bất kỳ luồng nào chạy song song đều có thể có được khóa. Do đó, luồng có trong hệ thống và chạy trong một thời gian dài hơn không bao giờ có cơ hội truy cập vào tài nguyên được chia sẻ, do đó dẫn đến chết đói.

Khóa Reentrant rất linh hoạt và có chính sách công bằng, trong đó nếu một luồng đang chờ trong thời gian dài hơn và sau khi hoàn thành chuỗi đang thực thi, chúng ta có thể đảm bảo rằng chuỗi chờ dài hơn có cơ hội truy cập vào tài nguyên được chia sẻ thông lượng của hệ thống và làm cho nó tốn nhiều thời gian hơn.


1

Giả sử mã này đang chạy trong một luồng:

private static ReentrantLock lock = new ReentrantLock();

void accessResource() {
    lock.lock();
    if( checkSomeCondition() ) {
        accessResource();
    }
    lock.unlock();
}

Vì luồng sở hữu khóa nên nó sẽ cho phép nhiều lệnh gọi khóa (), do đó, nó nhập lại khóa. Điều này có thể đạt được với số tham chiếu để không phải lấy lại khóa.


0

Một điều cần lưu ý là:

Tên ' ReentrantLock ' đưa ra một thông báo sai về cơ chế khóa khác mà chúng không được đăng ký lại. Đây không phải là sự thật. Khóa có được thông qua 'được đồng bộ hóa' cũng được cấp lại trong Java.

Điểm khác biệt chính là 'được đồng bộ hóa' sử dụng khóa nội tại (một loại mà mọi Đối tượng đều có) trong khi API Khóa thì không.


0

Tôi nghĩ rằng các phương thức chờ / thông báo / thông báo Tất cả không thuộc về lớp Đối tượng vì nó gây ô nhiễm tất cả các đối tượng bằng các phương thức hiếm khi được sử dụng. Chúng có ý nghĩa hơn nhiều đối với một lớp Khóa chuyên dụng. Vì vậy, từ quan điểm này, có lẽ tốt hơn là sử dụng một công cụ được thiết kế rõ ràng cho công việc trong tay - tức là ReentrantLock.

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.