zcxv

Легендарный
Проверенный
Победитель в номинации 2015
Сообщения
562
Розыгрыши
0
Репутация
812
Реакции
1 360
Баллы
1 703
Так как сегодня день рождение у форума, то надо бы выложить чето полезное и интересное. Я подумал, что людям будет приятно поюзать более "легкие" локи, чем есть в самой джаве, поэтому представляю на суд общественности CAS спинлоки. Ничего магического в этом нет, каждый сам может их написать, но велосипедить никому не хочется:)
Данные локи не предназначены для длительных блокировок, а так же для высококонкурентной среды, но работают они в десятки раз шустрее стандартных локов, т.к. не уходят в ядерную блокировку (не происходит паркинг треда), что занимает достаточное время.

Первый 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 обработка) и другие места.
 
Назад
Сверху Снизу