Библиотека Java SE5 java.utiLconcurrent также содержит явный механизм управления мьютексами, определенный в java.util.concurrent.locks. Объект Lock можно явно создать в программе, установить или снять блокировку; правда, полученный код будет менее элегантным, чем при использовании встроенной формы. С другой стороны, он обладает большей гибкостью при решении некоторых типов задач. Вот как выглядит пример SynchronizedEvenGenerator.java с явным использованием объектов Lock:
II: concurrency/MutexEvenGenerator.java
// Предотвращение потоковых конфликтов с использованием мьютексов.
II {RunByHand}
import java.util.concurrent.locks.*;
public class MutexEvenGenerator extends IntGenerator { private int currentEvenValue = 0, private Lock lock = new ReentrantLockO; public int nextO { lock lockO; try {
++currentEvenValue; Thread.yieldO; // Ускоряем сбой ++currentEvenValue; return currentEvenValue; } finally {
lock.unlockO,
}
}
public static void main(String[] args) {
EvenChecker test(new MutexEvenGeneratorO).
}
} /// ~
MutexEvenGenerator добавляет мьютекс с именем lock и использует методы lock() и unlock() для создания критической секции в next(). При использовании объектов Lock следует применять идиому, показанную в примере: сразу же за вызовом lock() необходимо разместить конструкцию try-finally, при этом в секцию finally включается вызов unlock() — только так можно гарантировать снятие блокировки.
Хотя try-finally требует большего объема кода, чем ключевое слово synchronized, явное использование объектов Lock обладает своими преимуществами. При возникновении проблем с ключевым словом synchronized происходит исключение, но вы не получите возможность выполнить завершающие действия, чтобы сохранить корректное состояние системы. При работе с объектами Lock можно сделать все необходимое в секции finally.
В общем случае использование synchronized уменьшает объем кода, а также радикально снижает вероятность ошибки со стороны программиста, поэтому явные операции с объектами Lock обычно выполняются только при решении особых задач. Например, с ключевым словом synchronized нельзя попытаться получить блокировку с неудачным исходом или попытаться получить блокировку в течение некоторого промежутка времени с последующим отказом — в подобных случаях приходится использовать библиотеку concurrent:
//: concurrency/AttemptLocking java
// Объекты Lock из библиотеки concurrent делают возможными
// попытки установить блокировку в течение некоторого времени
import java.util.concurrent *;
import java util concurrent.locks.*;
public class AttemptLocking {
private ReentrantLock lock = new ReentrantLockO;
public void untimedO {
boolean captured = lock.tryLockO, try {
System.out printlnCtryLockO: " + captured); } finally {
if(captured)
lock unlockO;
}
}
public void timedO {
boolean captured = false; try {
captured = lock tryLock(2, TimeUnit SECONDS); } catch(InterruptedException e) {
throw new RuntimeException(e);
}
try {
System out println("tryLock(2. TimeUnit SECONDS): " +
captured),
} finally {
if(captured)
lock unlockO,
}
}
public static void main(String[] args) {
final AttemptLocking al = new AttemptLocking(),
al untimedO. // True -- блокировка доступна al timedO. // True -- блокировка доступна // Теперь создаем отдельную задачу для установления блокировки new ThreadO {
{ setDaemon(true), } public void run() {
al lock lockO.
System.out printlnC'acquired");
}
} startO,
Thread yieldO, // Даем возможность 2-й задаче al untimedO; // False -- блокировка захвачена задачей al.timedO. // False -- блокировка захвачена задачей
}
} /* Output-tryLockO. true
tryLock(2, TimeUnit.SECONDS): true acquired
tryLockO false
tryLock(2, TimeUnit SECONDS)- false */// ~
Класс ReentrantLock делает возможной попытку получения блокировки с последующим отказом от нее. Таким образом, если кто-то уже захватил блокировку, вы можете отказаться от своих намерений (вместо того, чтобы дожидаться ее освобождения). В методе timed() делается попытка установления блокировки, которая может завершиться неудачей через 2 секунды (обратите внимание на использование класса Java SE5 TimeUnit для определения единиц времени). В main() отдельный объект Thread создается в виде безымянного класса и устанавливает блокировку, чтобы методы untimed() и timed() могли с чем-то конкурировать.
Атомарные операции и ключевое слово volatile
В дискуссиях, посвященных механизму потоков в Java, часто можно услышать такое утверждение: «Атомарные операции не требуют синхронизации».