Làm cách nào để xác định xem một đối tượng có bị khóa (đồng bộ hóa) để không bị chặn trong Java hay không?


81

Tôi có một tiến trình A chứa một bảng trong bộ nhớ với một tập hợp các bản ghi (recordA, recordB, v.v.)

Bây giờ, quá trình này có thể khởi chạy nhiều luồng ảnh hưởng đến các bản ghi và đôi khi chúng ta có thể có 2 luồng đang cố gắng truy cập vào cùng một bản ghi - tình huống này phải bị từ chối. Cụ thể là nếu một bản ghi bị KHÓA bởi một luồng, tôi muốn luồng kia hủy bỏ (tôi không muốn CHẶN hoặc CHỜ).

Hiện tại tôi làm một việc như sau:

synchronized(record)
{
performOperation(record);
}

Nhưng điều này đang gây ra cho tôi vấn đề ... bởi vì trong khi Process1 đang thực hiện hoạt động, nếu Process2 đi kèm với nó sẽ chặn / chờ câu lệnh được đồng bộ hóa và khi Process1 kết thúc, nó sẽ thực hiện hoạt động. Thay vào đó, tôi muốn một cái gì đó như thế này:

if (record is locked)
   return;

synchronized(record)
{
performOperation(record);
}

Bất kỳ manh mối nào về cách điều này có thể được thực hiện? Bất kì sự trợ giúp nào đều được đánh giá cao. Cảm ơn,

Câu trả lời:


87

Một điều cần lưu ý là ngay khi bạn nhận được thông tin như vậy, nó đã cũ. Nói cách khác, bạn có thể được thông báo rằng không ai có khóa, nhưng sau đó khi bạn cố gắng lấy nó, bạn chặn vì một luồng khác đã lấy ra khóa giữa séc và bạn đang cố lấy nó.

Brian đã đúng khi chỉ ra Lock, nhưng tôi nghĩ điều bạn thực sự muốn là tryLockphương pháp của nó :

Lock lock = new ReentrantLock();
......
if (lock.tryLock())
{
    // Got the lock
    try
    {
        // Process record
    }
    finally
    {
        // Make sure to unlock so that we don't cause a deadlock
        lock.unlock();
    }
}
else
{
    // Someone else had the lock, abort
}

Bạn cũng có thể gọi điện tryLockvới một khoảng thời gian chờ đợi - vì vậy bạn có thể cố gắng lấy nó trong một phần mười giây, sau đó hủy bỏ nếu bạn không thể nhận được (ví dụ).

(Tôi nghĩ thật đáng tiếc khi Java API không - theo như tôi biết - cung cấp chức năng tương tự cho khóa "tích hợp sẵn", như Monitorlớp làm trong .NET. Sau đó, có rất nhiều những thứ khác mà tôi không thích ở cả hai nền tảng khi nói đến luồng - ví dụ: mọi đối tượng đều có khả năng có màn hình!)


Đúng. Đó là một điểm hay. Tôi lấy ví dụ mã theo nghĩa đen, trong khi ở trên chắc chắn là một việc thực hiện mạnh mẽ hơn
Brian Agnew

5
Nhưng làm cách nào để sử dụng khóa cho mỗi bản ghi? Hiện tại, các bản ghi được lưu trữ trong HashTable gồm các bản ghi ... vì vậy tôi cần một Hashtable of Locks phù hợp? Tôi đang cố gắng đảm bảo rằng tôi có sự đồng thời nhất có thể, vì vậy nếu một quy trình muốn truy cập vào recordC thì vẫn ổn (nếu chỉ có recordB bị khóa) - Tôi sử dụng KHÓA toàn cục thì về cơ bản nó giống như khóa toàn bộ bảng băm. ... có ý nghĩa gì không?
Shaitan00

@ Shaitan00: Cách dễ nhất là có một khóa trong hồ sơ. Về cơ bản, bạn muốn một khóa được liên kết với mỗi bản ghi - vì vậy hãy đặt nó vào đối tượng.
Jon Skeet

Rõ ràng :) Tôi cho rằng tôi không cần quản lý việc mở khóa? Có nghĩa là khi thoát khỏi {} của .tryLock (), nó sẽ tự động và ngay lập tức được mở khóa đúng không?
Shaitan00

1
Bạn cần quản lý mở khóa. Xem hướng dẫn mà tôi đã tham khảo trong câu trả lời của mình và phương thức unlock () trong khối {} cuối cùng
Brian Agnew

24

Hãy xem các đối tượng Khóa được giới thiệu trong các gói đồng thời của Java 5.

ví dụ

Lock lock = new ReentrantLock()
if (lock.tryLock()) {
   try {
      // do stuff using the lock...
   }
   finally {
      lock.unlock();
   }
}
   ...

Các ReentrantLock đối tượng chủ yếu được làm điều tương tự như truyền thống synchronizedcơ chế, nhưng với nhiều chức năng hơn.

CHỈNH SỬA: Như Jon đã lưu ý, isLocked()phương pháp sẽ cho bạn biết ngay lập tức và sau đó thông tin đó đã lỗi thời. Phương thức tryLock () sẽ cung cấp hoạt động đáng tin cậy hơn (lưu ý rằng bạn cũng có thể sử dụng phương thức này với thời gian chờ)

CHỈNH SỬA # 2: Ví dụ bây giờ bao gồm tryLock()/unlock()để rõ ràng hơn.


11

Tôi đã tìm thấy cái này, chúng ta có thể sử dụng Thread.holdsLock(Object obj)để kiểm tra xem một đối tượng có bị khóa hay không:

Trả về truenếu và chỉ khi luồng hiện tại giữ khóa màn hình trên đối tượng được chỉ định.

Lưu ý rằng Thread.holdsLock()sẽ trả về falsenếu khóa được giữ bởi thứ gì đó và chuỗi gọi không phải là chuỗi giữ khóa.


8

Mặc dù cách tiếp cận ở trên bằng cách sử dụng đối tượng Khóa là cách tốt nhất để thực hiện, nhưng nếu bạn phải kiểm tra khóa bằng màn hình, bạn có thể thực hiện được. Tuy nhiên, nó đi kèm với một cảnh báo về tình trạng vì kỹ thuật này không khả dụng đối với các máy ảo Oracle Java và nó có thể bị hỏng trong các phiên bản VM trong tương lai vì nó không phải là một API công cộng được hỗ trợ.

Đây là cách làm:

private static sun.misc.Unsafe getUnsafe() {
    try {
        Field field = sun.misc.Unsafe.class.getDeclaredField("theUnsafe");
        field.setAccessible(true);
        return (Unsafe) field.get(null);
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}

public void doSomething() {
  Object record = new Object();
  sun.misc.Unsafe unsafe = getUnsafe(); 
  if (unsafe.tryMonitorEnter(record)) {
    try {
      // record is locked - perform operations on it
    } finally {
      unsafe.monitorExit(record);
    }
  } else {
      // could not lock record
  }
}

Lời khuyên của tôi là chỉ sử dụng cách tiếp cận này nếu bạn không thể cấu trúc lại mã của mình để sử dụng các đối tượng Khóa java.util.concurrent cho việc này và nếu bạn đang chạy trên máy ảo Oracle.


12
@alestanis, tôi không đồng ý. Ở đây, tôi đang đọc những câu trả lời này ngày hôm nay và rất vui vì tất cả các câu trả lời / nhận xét, bất kể khi nào chúng được đưa ra.
Tom

@Tom Những câu trả lời này được gắn cờ bởi hệ thống SO, đó là lý do tại sao tôi để lại lời nhắn. Bạn sẽ thấy khi bắt đầu xem lại :)
alestanis 14/12/12

2
@alestanis, tôi nghĩ điều đó thật không may - tôi thường thấy những câu hỏi cũ có câu trả lời được chấp nhận, nhưng cũng có câu trả lời mới hơn, tốt hơn do công nghệ thay đổi hoặc vì câu trả lời được chấp nhận không thực sự chính xác hoàn toàn.
Tom

1
Ngoài thread này đã cũ và có câu trả lời hay thì câu trả lời này ở đây là không hay. Nếu bạn thực sự muốn khóa bằng màn hình, bạn có thể sử dụng Thread.holdsLock (đối tượng).
Tobias

3

Mặc dù các câu trả lời về Khóa rất tốt, nhưng tôi nghĩ mình sẽ đăng một giải pháp thay thế bằng cách sử dụng cấu trúc dữ liệu khác. Về cơ bản, các chuỗi khác nhau của bạn muốn biết bản ghi nào bị khóa và bản ghi nào không. Một cách để làm điều này là theo dõi các bản ghi bị khóa và đảm bảo rằng cấu trúc dữ liệu có các hoạt động nguyên tử phù hợp để thêm các bản ghi vào tập hợp bị khóa.

Tôi sẽ sử dụng CopyOnWriteArrayList làm ví dụ vì nó ít "ma thuật" hơn để minh họa. CopyOnWriteArraySet là một cấu trúc thích hợp hơn. Nếu trung bình bạn có rất nhiều bản ghi bị khóa cùng một lúc thì có thể có những tác động về hiệu suất với những triển khai này. Một HashSet được đồng bộ hóa đúng cách cũng sẽ hoạt động và khóa ngắn gọn.

Về cơ bản, mã sử dụng sẽ giống như sau:

CopyOnWriteArrayList<Record> lockedRecords = ....
...
if (!lockedRecords.addIfAbsent(record))
    return; // didn't get the lock, record is already locked

try {
    // Do the record stuff
}        
finally {
    lockedRecords.remove(record);
}

Nó giúp bạn không phải quản lý khóa cho mỗi bản ghi và cung cấp một nơi duy nhất nên xóa tất cả các khóa là cần thiết vì một số lý do. Mặt khác, nếu bạn đã từng có nhiều bản ghi thì một HashSet thực với đồng bộ hóa có thể hoạt động tốt hơn vì các tra cứu thêm / xóa sẽ là O (1) thay vì tuyến tính.

Chỉ là một cách nhìn khác về mọi thứ. Chỉ phụ thuộc vào yêu cầu phân luồng thực tế của bạn là gì. Cá nhân tôi sẽ sử dụng Collections.synchronizedSet (HashSet mới ()) vì nó sẽ rất nhanh ... ngụ ý duy nhất là các luồng có thể mang lại lợi nhuận khi chúng không có.


Nếu bạn muốn có toàn quyền kiểm soát, bạn có thể đặt điều này vào Locklớp học của riêng bạn, tôi đoán vậy.
bvdb

1

Một giải pháp khác là (trong trường hợp bạn không có cơ hội với các câu trả lời được đưa ra ở đây) là sử dụng thời gian chờ. tức là bên dưới một sẽ trả về null sau 1 giây treo:

ExecutorService executor = Executors.newSingleThreadExecutor();
        //create a callable for the thread
        Future<String> futureTask = executor.submit(new Callable<String>() {
            @Override
            public String call() throws Exception {
                return myObject.getSomething();
            }
        });

        try {
            return futureTask.get(1000, TimeUnit.MILLISECONDS);
        } catch (InterruptedException | ExecutionException | TimeoutException e) {
            //object is already locked check exception type
            return null;
        }

-2

Cảm ơn vì điều này, nó đã giúp tôi giải quyết một điều kiện cuộc đua. Tôi đã thay đổi một chút để đeo cả thắt lưng và dây treo.

Vì vậy, đây là gợi ý của tôi cho AN CẢI TIẾN câu trả lời được chấp nhận:

Bạn có thể đảm bảo rằng bạn có quyền truy cập an toàn vào tryLock()phương thức bằng cách làm như sau:

  Lock localLock = new ReentrantLock();

  private void threadSafeCall() {
    boolean isUnlocked = false;

    synchronized(localLock) {
      isUnlocked = localLock.tryLock();
    }

    if (isUnlocked) {
      try {
        rawCall();
      }
      finally {
        localLock.unlock();
      }
    } else {
      LOGGER.log(Level.INFO, "THANKS! - SAVED FROM DOUBLE CALL!");
    }
  }

Điều này sẽ tránh trường hợp bạn có thể nhận được hai cuộc gọi tryLock()gần như cùng một lúc, khiến cuộc gọi trở lại có khả năng bị đầy. Tôi muốn bây giờ nếu tôi sai, tôi có thể vượt quá cảnh giác ở đây. Nhưng này! Giờ biểu diễn của tôi đã ổn định :-) ..

Đọc thêm về các vấn đề phát triển của tôi tại Blog của tôi .


1
sử dụng đồng bộ trên một đối tượng khóa là rất, rất tệ. Toàn bộ điểm của khóa là tránh đồng bộ hóa, vì vậy bạn có thể hết thời gian chờ hoặc quay lại nhanh chóng khi bạn không thể lấy được khóa.
Ajax
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.