Так как сегодня день рождение у форума, то надо бы выложить чето полезное и интересное. Я подумал, что людям будет приятно поюзать более "легкие" локи, чем есть в самой джаве, поэтому представляю на суд общественности CAS спинлоки. Ничего магического в этом нет, каждый сам может их написать, но велосипедить никому не хочется
Данные локи не предназначены для длительных блокировок, а так же для высококонкурентной среды, но работают они в десятки раз шустрее стандартных локов, т.к. не уходят в ядерную блокировку (не происходит паркинг треда), что занимает достаточное время.
Первый CAS спинлок. Обычный лок, не поддерживает рекурсивную блокировку. Когда-то давно, я взял за основу код Ронна, но немного переделал его, чтобы добиться большей производительности.
Второй лок уже поддерживает рекурсивную блокировку. Работает он чуть медленнее варианта без рекурсии (все равно, огромный отрыв от обычных локов).
Тест для рекурсивного лока:
Посмотреть вложение 16534
Спасибки:
Ронну, за первоначальную версию спинлока, без рекурсивной блокировки
Айзену, за ценные советы, когда я очень сильно слоупочил
Где же это все можно применить?
Везде, где важна скорость работы, а так же отсутствуют длительные блокировки, например: коллекции, сеть (но не IO обработка) и другие места.
Данные локи не предназначены для длительных блокировок, а так же для высококонкурентной среды, но работают они в десятки раз шустрее стандартных локов, т.к. не уходят в ядерную блокировку (не происходит паркинг треда), что занимает достаточное время.
Первый CAS спинлок. Обычный лок, не поддерживает рекурсивную блокировку. Когда-то давно, я взял за основу код Ронна, но немного переделал его, чтобы добиться большей производительности.
Код:
package fork2.concurrent;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
/**
* Реализация примитивного блокировщика при помощи {@link AtomicInteger} без
* поддержки рекурсивной блокировки. Рекамендуется приминется в местах с не
* более чем средней конкурнции и с короткими секциями блокировки.
* Pointer: CAS спинлок без поддержки рекурсивной блокировки
*
* @author Ronn
*/
public class PrimitiveAtomicLock implements Lock {
public static final int STATUS_LOCKED = 1;
public static final int STATUS_UNLOCKED = 0;
/** статус блокировки */
@sun.misc.Contended
private final AtomicInteger status;
public PrimitiveAtomicLock() {
this.status = new AtomicInteger();
}
/**
* @return статус блокировки.
*/
private AtomicInteger getStatus() {
return status;
}
@Override
public void lock() {
final AtomicInteger status = getStatus();
while(!status.compareAndSet(STATUS_UNLOCKED, STATUS_LOCKED)) {
Thread.yield(); //ждем окончания кванта времени
}
}
/** /Pointer add/ работает в несколько раз медленее обычного лока, т.к. стучится за статусом треда в натив */
@Override
public void lockInterruptibly() throws InterruptedException {
final AtomicInteger status = getStatus();
while(!status.compareAndSet(STATUS_UNLOCKED, STATUS_LOCKED)) {
if(Thread.currentThread().isInterrupted()) {
throw new InterruptedException();
}
Thread.yield();
}
}
@Override
public Condition newCondition() {
throw new RuntimeException("not supported.");
}
public boolean isLocked() {
return status.get() == STATUS_LOCKED;
}
@Override
public boolean tryLock() {
return status.compareAndSet(STATUS_UNLOCKED, STATUS_LOCKED);
}
@Override
public boolean tryLock(final long time, final TimeUnit unit) throws InterruptedException {
final AtomicInteger status = getStatus();
if(status.compareAndSet(STATUS_UNLOCKED, STATUS_LOCKED)) {
return true;
}
final long resultTime = unit.toMillis(time);
if(resultTime > 1) {
Thread.sleep(resultTime);
}
return status.compareAndSet(STATUS_UNLOCKED, STATUS_LOCKED);
}
@Override
public void unlock() {
final AtomicInteger status = getStatus();
status.set(STATUS_UNLOCKED);
}
}
Второй лок уже поддерживает рекурсивную блокировку. Работает он чуть медленнее варианта без рекурсии (все равно, огромный отрыв от обычных локов).
Код:
package fork2.concurrent;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
/**
* Рекурсивный CAS спинлок (поддерживается многократная блокировка одним и тем же потоком).
* Работает чуть медленее обычной реализации от Ронна, при взятии лока без конкуренции.
*
* Как и в обычных локах, максимальное значение по рекурсивной блокировке: 2147483647.
* Исключений не кидает, при превышении, поэтому на свой страх и риск:)
* @author PointerRage
*
*/
public class ReentrantSpinLock implements Lock {
private final static int STATUS_LOCKED = 1;
private final static int STATUS_UNLOCKED = 0;
@sun.misc.Contended
private final AtomicInteger counter = new AtomicInteger(STATUS_UNLOCKED);
@sun.misc.Contended
private final AtomicReference<Thread> hold = new AtomicReference<>(null);
public ReentrantSpinLock() {
}
@Override
public void lock() {
final Thread thread = Thread.currentThread();
if(hold.compareAndSet(thread, thread)) { //возможно лок уже был захвачен
counter.incrementAndGet();
return;
}
while(!counter.compareAndSet(STATUS_UNLOCKED, STATUS_LOCKED)) {
Thread.yield();
}
hold.set(thread);
}
@Override
public void lockInterruptibly() throws InterruptedException {
final Thread thread = Thread.currentThread();
if(hold.compareAndSet(thread, thread)) {
counter.incrementAndGet();
return;
}
while(!counter.compareAndSet(STATUS_UNLOCKED, STATUS_LOCKED)) {
if(thread.isInterrupted()) {
throw new InterruptedException();
}
Thread.yield();
}
hold.set(thread);
}
@Override
public boolean tryLock() {
return counter.compareAndSet(STATUS_UNLOCKED, STATUS_LOCKED);
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
throw new RuntimeException("not supported");
}
/** может выкинуть RuntimeException, если лок не был захвачен */
@Override
public void unlock() {
final Thread thread = hold.get();
final int count = counter.decrementAndGet();
if(count == STATUS_UNLOCKED) {
hold.compareAndSet(thread, null);
} else if(count < STATUS_UNLOCKED) {
counter.incrementAndGet();
throw new RuntimeException("lock cannot hold!");
}
}
@Override
public Condition newCondition() {
throw new RuntimeException("not supported");
}
public int getHoldCount() {
return counter.get();
}
public boolean isHeldByCurrentThread() {
return hold.get() == Thread.currentThread();
}
public boolean isLocked() {
return counter.get() >= STATUS_LOCKED;
}
}
Тест для рекурсивного лока:
Посмотреть вложение 16534
Спасибки:
Ронну, за первоначальную версию спинлока, без рекурсивной блокировки
Айзену, за ценные советы, когда я очень сильно слоупочил
Где же это все можно применить?
Везде, где важна скорость работы, а так же отсутствуют длительные блокировки, например: коллекции, сеть (но не IO обработка) и другие места.