Простой код Java позволяет однопоточному доступу и другим потокам пропускать / двигаться дальше - PullRequest
1 голос
/ 09 сентября 2011

Другими словами, я не хочу, чтобы поток ожидал, если он не может получить доступ к блокировке (как при синхронизации), я хочу, чтобы выполнение потока просто немедленно возвращалось в тот момент, если он не может получить блокировку.

Так как простая логическая блокировка, подобная этой, потенциально может обеспечить доступ к нескольким потокам.

private static boolean lockAvailable = true;

private boolean acquireLock() {
  if(lockAvailable) {
    lockAvailable = false;
    return true;
  }
  return false;
}

Я что-то упустил? Каков наилучший / самый простой способ добиться этого?

Edit:

Спасибо, что указали на Семафор (!)

Так что, глядя на это снова, этот код является пуленепробиваемым?

private final static Semaphore lock = new Semaphore(1, true);   

public void tryAndDoSomething() {
  if(lock.tryAcquire()) {
    try {
      // only single thread can access here at one time
    } finally {
      lock.release();
    }
  }
}

Обновление:

Я понял, что мне нужна возможность повторного входа, поэтому я создал простой не блокирующий повторный вход. Размещать код для тех, кто интересуется, как вы можете это сделать. Любой, кто хочет этот тип функциональности, должен, конечно, использовать существующий Java-класс java.util.concurrent.locks.ReentrantLock: |

import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * SimpleNonBlockingLock ensures that only a single thread can call protected code at any one time,
 * while allowing other threads to by pass the protected code if the lock is unavailable.
 * The thread owning the lock can access any code protected by the lock (the lock is 'reentrant').
 * To function correctly the protected code must be executed in a try/finally blocks. The
 * finally block must call the tryRelease. Example code:
 * 
 *  private final SimpleNonBlockingLock lock = new SimpleNonBlockingLock();
 * 
 *  if(lock.tryAcquire()) {
 *      try {
 *              // access protected code
 *      } finally {
 *          lock.tryRelease();
 *      }
 *  }
 *
 * This code is for demonstration only and should not be used. I have tested it and it 'seems to' work.
 * However it may contain horrific bugs!
 * 
 * The Java class java.util.concurrent.locks.ReentrantLock has been around since Java 5.0 and contains all (and more) 
 * of this functionality. Its also been thoroughly tested!
 */
public class SimpleNonBlockingLock {

    // Atomic locking mechanism
    private final AtomicBoolean locked = new AtomicBoolean();

    // Atomic integer containing the next thread ID to be assigned
    private static final AtomicInteger nextId = new AtomicInteger(0);

    // Unique ID of thread which currently has lock
    private int threadUniqueId = -1;

    // Tracks number of tryAcquire calls made by thread with lock
    private int lockCount = 0;

    // Thread local variable containing each thread's ID
    private static final ThreadLocal<Integer> threadId = new ThreadLocal<Integer>() {
            @Override protected Integer initialValue() {
                return nextId.getAndIncrement();
        }
    };

    public synchronized boolean tryAcquire() {      
        // Allow owning thread to acquire
        if(threadUniqueId == getCurrentThreadUniqueId()) {
            lockCount++;
            return true;
        }       
        // If locked then do not allow
        if (locked.get()) {return false;}           
        // Attempt to acquire lock      
        boolean attemptAcquire = locked.compareAndSet(false, true);     
        // If successful then set threadUniqueId for the thread, and increment lock count
        if(attemptAcquire) {
            threadUniqueId = getCurrentThreadUniqueId();
            lockCount++;
        }       
        // Return result of attempt to acquire lock
        return attemptAcquire;
    }

    public synchronized boolean tryRelease() {
        if (!locked.get()) {
            // Lock is currently available - no need to try and release
            return true;
        } else {
            // Decrement the lock count
            lockCount--;
            // If lock count is zero we release lock, and reset the threadUniqueId
            if(lockCount == 0) {
                threadUniqueId = -1;
                return locked.compareAndSet(true, false);
            } 
            return false;
        }   
    }

    // Returns the current thread's unique ID, assigning it if necessary
    public static int getCurrentThreadUniqueId() {      
        return threadId.get();
    }   
}

Ответы [ 5 ]

8 голосов
/ 09 сентября 2011

Java 5 ввела явные блокировки , которые имеют операцию tryLock.Так что используйте явную блокировку вместо синхронизированных блоков, тогда вы можете позвонить tryLock:

private Lock lock = ...;

private boolean acquireLock() {
  if (lock.tryLock()) {
      ...
      return true;
  } else {
      return false;
  }
}
3 голосов
/ 09 сентября 2011

Используйте метод Semaphore и метод tryAcquire.

0 голосов
/ 09 сентября 2011

Другие отметили, что для этого есть отличная библиотека, но если вы заинтересованы в реализации такого рода вещей, вам нужно что-то, поддерживающее операцию сравнения и задания. К счастью, из Java5 есть классы java.util.concurrent.atomic.Atomic *!

public class MyLock {
  private final AtomicBoolean locked = new AtomicBoolean();

  public boolean tryLock() {
    if (locked.get()) return true;
    return locked.compareAndSet(false, true);
  }

  public boolean unlock() {
    if (l!ocked.get()) return true;
    return locked.compareAndSet(true, false);
  }
}

Выше приведена очень наивная реализация, но она показывает основы того, как вы реализуете это без блокировки.

Для получения дополнительной информации о деталях реализации параллельных структур данных см. Потрясающее «Искусство многопроцессорного программирования» Шавита и Херлихи и, конечно же, «Параллелизм Java на практике» Гетца и др.

0 голосов
/ 09 сентября 2011

Вы должны попробовать использовать класс ReentrantLock. Вы можете попытаться получить данные с помощью tryLock () или вызова lock () напрямую. Обязательно изучите API для ReentrantLock. Это краткий пример:

    Lock lock = new ReentrantLock();
    // block until lock is acquired. Make sure to call unlock in a finally
    // statement!
    try {
        lock.lock();
    } finally {
        lock.unlock();
    }
    // or try to gain the lock
    boolean success = lock.tryLock();
    if(success) {
        //some logic..
    }

    //or try to gain lock within time frame
    try {
        lock.tryLock(1, TimeUnit.SECONDS);
    } catch (InterruptedException ex) {
        ex.printStackTrace();
    }
0 голосов
/ 09 сентября 2011

да, вы что-то упустили (ваше решение не сработает)

представьте, что 2 потока одновременно получают оператор if () и пропускают его. оба устанавливают для lockAvailable значение false и возвращают значение true

Вы должны синхронизировать функцию, если не хотите нарушать правила критического раздела или использовать другой метод.

...