...
This noncompliant code example shows uses a thread-safe class Book
. However, the class does not commit to its locking strategy and does not document that callers can safely use client-side locking. The class CheckOut
uses client-side locking by synchronizing on a Book
instance. that cannot be modified by a client.
Code Block | ||
---|---|---|
final | ||
Code Block | ||
| ||
class Book {
// May change its locking policy in the future to use private internal locks
private final String title;
private Calendar dateIssued;
private Calendar dateDue;
Book(String title, int days) {
this.title = title;
}
public synchronized void issueBook(int days) {
dateIssued = Calendar.getInstance();
dateDue = Calendar.getInstance();
dateDue.add(Calendar.DAY_OF_MONTH, days);
}
public synchronized Calendar getDueDate() {
return dateDue;
}
}
|
Furthermore, the class does not commit to its locking strategy and does not document that callers can safely use client-side locking. The client class BookWrapper
uses client-side locking in the renewBook()
method by synchronizing on a Book
instance.
Code Block | ||
---|---|---|
| ||
// Client public class CheckOutBookWrapper { private final Book book; CheckOutBookWrapper(Book book) { this.book = book; } public void issueBook(int days) { book.issueBook(); } public Calendar getDueDate() { return book.getDueDate(); } public void renewBook() { synchronized(book) { if (book.getDueDate().after(Calendar.getInstance())) { throw new IllegalStateException("Book overdue"); } else { book.issueBook(14); } } } // ... } |
If class Book
changes its synchronization policy in the future, the CheckOut
BookWrapper
class's locking strategy will silently break. This is because threads that call getDueDate()
of class CheckOut
BookWrapper
may perform operations on the thread-safe Book
using its new locking policy (internal private lock), however, threads that call method renewBook()
will always synchronize on the intrinsic lock of the Book
instance. Consequently, the implementation will use two different locks.
...
Code Block | ||
---|---|---|
| ||
public class CheckOutBookWrapper { private final Book book; private final Object lock = new Object(); CheckOutBookWrapper(Book book) { this.book = book; } public void issueBook(int days) { synchronized(lock) { book.issueBook(); } } public Calendar getDueDate() { synchronized(lock) { return book.getDueDate(); } } public void renewBook() { synchronized(lock) { if (book.getDueDate().after(Calendar.getInstance())) { throw new IllegalStateException("Book overdue"); } else { book.issueBook(14); } } } // ... } |
Consequently, its locking strategy is independent of the locking policy of the Book
instance.
...