Философия Java3
Шрифт:
Для создания критических секций также можно воспользоваться явно созданными объектами Lock:
//: concurrency/ExplicitCriticalSection.java
// Использование объектов Lock для создания критических секций.
package concurrency;
import java.util.concurrent.locks.*;
// Синхронизация целого метода: class ExplicitPairManagerl extends PairManager { private Lock lock = new ReentrantLockO; public synchronized void incrementO { lock lockO; try {
p.incrementXO; p. incrementYO; store(getPairO); } finally {
lock.unlock О;
}
}
}
// Использование критической секции: class ExplicitPairManager2 extends PairManager { private Lock lock = new ReentrantLockO: public void incrementO { Pair temp; lock.lockO: try {
p.incrementXO;
р incrementYO. temp = getPairO. } finally {
lock unlock
}
store(temp),
public class ExplicitCriticalSection {
public static void main(String[] args) throws Exception { PairManager
pmanl = new ExplicitPairManagerK), pman2 = new ExplicitPairManager2, CriticalSection.testApproaches(pmanl, pman2);
}
} /* Output
pml. Pair: x: 15, у 15 checkCounter = 174035 pm2: Pair- x- 16, у 16 checkCounter = 2608588 *///.-
В программе создаются новые типы PairManager с явным использованием объектов Lock. ExplicitPairManager2 демонстрирует создание критической секции с использованием объекта Lock; вызов store находится вне критической секции.
Синхронизация по другим объектам
Блоку synchronized необходимо передать объект, который будет использоваться для синхронизации. Чаще всего наиболее естественно передавать текущий объект, для которого был вызван метод synchronized(this), и именно такой подход применен в классе PairManager2. Таким образом, при входе в синхронизируемый блок другие синхронизированные методы объекта вызвать будет нельзя. Действие синхронизации по this фактически заключается в сужении области синхронизации.
Иногда вам нужно что-то иное, и в таких ситуациях вы создаете отдельный объект и выполняете синхронизацию, привлекая его. В таких случаях необходимо позаботиться о том, чтобы все операции синхронизировались по одному и тому же объекту. Следующий пример показывает, как два потока входят в объект, когда методы этого объекта синхронизированы различными блокировками:
У/ concurrency/SyncObject java // Синхронизация по другому объекту, import static net.mindview util.Print *;
class Dual Synch {
private Object syncObject = new Object О; public synchronized void f {
for(int i = 0; i < 5; i++) { printCfO"); Thread.yieldO;
}
}
public void gO { продолжение &
synchronizedsyncObject) {
for(int i = 0; i < 5; i++) { printC'gO"); Thread.yieldO;
public class SyncObject {
public static void main(String[] args) {
final Dual Synch ds = new DualSynchO; new ThreadO {
public void run { ds.fO;
}
}.startO; ds.gO;
}
} /* Output:
go
fO
go f go fo go fo go f *///:-
Метод f класса DualSync синхронизируется по объекту this (синхронизируя метод целиком), а метод g использует синхронизацию посредством объекта syncObject. Таким образом, два варианта синхронизации независимы. Демонстрируется этот факт методом main, в котором создается поток Thread с вызовом метода f. Поток main после этого вызывает метод д. Из результата
работы программы видно, что оба метода работают одновременно и ни один из них не блокируется соседом.Локальная память потока
Второй механизм предотвращения конфликтов доступа к общим ресурсам основан на исключении их совместного использования. Локальная память потока представляет собой механизм автоматического выделения разных областей памяти для одной переменной во всех потоках, использующих объект. Следовательно, если пять потоков используют объект с переменной х, для х будет сгенерировано пять разных областей памяти. Фактически поток связывается с некоторым состоянием.
За выделение локальной памяти потоков и управление ею отвечает класс java.lang.ThreadLocal:
//: concurrency/ThreadLocalVariableHolder.java
// Автоматическое выделение собственной памяти каждому потоку.
import java util.concurrent.*, import java util *,
class Accessor implements Runnable { private final int id. public Accessor(int idn) { id = idn; } public void run {
while( IThread.currentThreadO islnterrupted) { ThreadLocalVariableHolder incrementO; System out println(this). Thread.yieldO.
}
}
public String toStringO {
return "#" + id + " " +
ThreadLocalVari ableHolder.get.
public class ThreadLocalVariableHolder {
private static ThreadLocal<Integer> value = new ThreadLocal<Integer> {
private Random rand = new Random(47). protected synchronized Integer initialValueO { return rand.nextlnt(lOOOO).
}
}:
public static void incrementO {
value.set(value.get + 1).
}
public static int get О { return value getO. } public static void main(String[] args) throws Exception {
ExecutorService exec = Executors newCachedThreadPoolО. for(int i = 0. i < 5. i++)
exec.execute(new Accessor(i)). TimeUnit.SECONDS.sleep(3); // Небольшая задержка exec shutdownNowO, // Выход из всех объектов Accessor
}
} /* Output #0 9259 #1- 556 #2. 6694 #3- 1862 #4: 962 #0: 9260 #1- 557 #2: 6695 #3: 1863 #4: 963
*///:-
Объекты ThreadLocal обычно хранятся в статических полях. Если вы создаете объект ThreadLocal, для обращения к содержимому объекта можно использовать только методы get и set. Метод get возвращает копию объекта, ассоциированного с потоком, a set сохраняет свой аргумент в объекте потока, возвращая ранее хранившийся объект. Их использование продемонстрировано в методах increment и get класса ThreadLocalVariableHolder. Обратите внимание: методы increment^) и get не синхронизированы, потому что ThreadLocal не гарантирует отсутствия «ситуации гонки».
Взаимодействие между потоками
Итак, мы выяснили, что потоки способны конфликтовать друг с другом, и разобрались с тем, как предотвратить такие конфликты. Следующим шагом должно стать изучение возможностей взаимодействия между потоками. Ключевым моментом в этом процессе является подтверждение связи, безопасно реализуемое методами wait и notify класса Object. В многопоточной библиотеке Java SE5 также присутствуют объекты Condition с методами await и signal. Мы рассмотрим некоторые возникающие проблемы и их решения.