用信号量锁定:一个例子

并发是带来有趣挑战的一个方面。 如果处理不当,会导致种族状况,这会使人们感到困惑,因为这些问题有时会突然出现,并且有时会完美无缺地发挥作用。

当处理访问公共资源的并发线程时,Java语言提供了许多处理竞争条件的方法。 一些包括;

  1. 使用volatile关键字
  2. 使用java.util.concurrent和 java.util.concurrent.atomic中可用的类
  3. 同步块
  4. 使用信号量

当然,可能还有很多我可能不知道的事情。 今天,我想向大家展示的例子是使用
信号量 。 它是从JDK 1.5引入的,使开发人员能够无缝获取和释放锁。 我还将显示的示例是一个假设的场景,该场景仅用于描述使用信号量可以实现的效果,因此请不要看代码的固有细节:)。

因此,在这种情况下,存在一个内存缓存中保存了类型为
'人'。 用户可以使用缓存插入和检索记录。 这里的问题是我们将使用信号量来控制对内存缓存的并发访问。 现在,我不想给您带来更多文本,让我们开始业务并显示一些代码;

import java.util.concurrent.Semaphore;

/**
 * This class will allow thread to acquire and release locks as required
 * 
 * @author dinuka.arseculeratne
 * 
 */
public class PersonLock {

 /**
  * We do not want multiple lock objects lying around so we make ths class
  * singleton
  */
 private PersonLock() {

 }

 /**
  * Bill Pugh's way of lazy initializing the singleton instance
  * 
  * @author dinuka.arseculeratne
  * 
  */
 private static class SingletonHolder {
  public static final PersonLock INSTANCE = new PersonLock();
 }

 /**
  * Use this method to get a reference to the singleton instance of
  * {@link PersonLock}
  * 
  * @return the singleton instance
  */
 public static PersonLock getInstance() {
  return SingletonHolder.INSTANCE;
 }

 /**
  * In this sample, we allow only one thread at at time to update the cache
  * in order to maintain consistency
  */
 private Semaphore writeLock = new Semaphore(1);

 /**
  * We allow 10 concurrent threads to access the cache at any given time
  */
 private Semaphore readLock = new Semaphore(10);

 public void getWriteLock() throws InterruptedException {
  writeLock.acquire();
 }

 public void releaseWriteLock() {
  writeLock.release();
 }

 public void getReadLock() throws InterruptedException {
  readLock.acquire();
 }

 public void releaseReadLock() {
  readLock.release();
 }
}

此类将处理获取和释放使我们的缓存线程安全所需的锁的过程。 我在这里使用了两个单独的锁进行读写。 其背后的原理是允许用户读取数据,尽管在读取时可能已过时。

请注意,我已经使用
这里的“十”表示十个线程可以同时获取锁并出于读取目的访问缓存。 接下来,您可以在写锁中看到,我已经使用了“ 一个” ,表示一次只有一个线程可以访问缓存以将项目放入缓存。 这对于保持缓存内的一致性很重要。 也就是说,我不希望有多个线程试图将项目插入到地图中,这将导致不可预测的行为(至少在某些情况下)。 使用信号量获取锁的方式主要有两种。

1. Acquisition() :是一个阻塞调用,它等待直到释放锁或线程被中断为止

2. tryAcquire() :是一个非阻塞调用,它将立即返回并返回true或false,表示是否已获得锁定。

在这里,我使用了阻塞获取调用,因为我希望线程等待直到锁可用。 当然,这取决于您的用例。 您还可以在tryAcquire()方法中定义超时期限,以使线程不会无限期地等待锁定。

接下来,下面的存储类显示了我如何使用锁类在缓存中插入和读取数据。

import java.util.HashMap;
import java.util.Map;

/**
 * A mock storage to hold the person objects in a map
 * 
 * @author dinuka.arseculeratne
 * 
 */
public class PersonStorage {

 private Map<Integer, Person> personCache = new HashMap<Integer, Person>();

 private int counter = 0;

 /**
  * This class is made singleton and hence the constructor is made private
  */
 private PersonStorage() {

 }

 /**
  * Bill Pugh's way of lazy initializing the singleton instance
  * 
  * @author dinuka.arseculeratne
  * 
  */
 private static final class SingletonHolder {
  public static final PersonStorage INSTANCE = new PersonStorage();
 }
 
 /**
  * Use this method to get a reference to the singleton instance of
  * {@link PersonStorage}
  * 
  * @return the singleton instance
  */
 public static PersonStorage getInstance()
 {
  return SingletonHolder.INSTANCE;
 }

 /**
  * Inserts the person into the map. Note that we use defensive copying so
  * that even if the client changes the object later on, those changes will
  * not be reflected in the object within the map
  * 
  * @param person
  *            the instance of {@link Person} to be inserted
  * @return the key which signifies the location of the person object
  * @throws InterruptedException
  */
 public int putPerson(Person person) throws InterruptedException {
  
  Person copyPerson = person.copyPerson();
  personCache.put(++counter, copyPerson);
  
  return counter;
 }

 /**
  * Here as well we use defensive copying so that the value of the object
  * reference within the map is not passed in to the calling party.
  * 
  * @param id
  *            the id representing the location of the object within the map
  * @return the instance of the {@link Person} represented by the key passed
  *         in
  * @throws InterruptedException
  */
 public Person retrievePerson(int id) throws InterruptedException {
  PersonLock.getInstance().getReadLock();
  if (!personCache.containsKey(id)) {
   throw new RuntimeException('Key is not found');
  }
  PersonLock.getInstance().releaseReadLock();
  return personCache.get(id).copyPerson();
 }

}

显然,代码也可以在没有锁的情况下工作,但是问题是应用程序将不一致,并且每次运行都会提供不同的结果。 这不是您希望应用程序执行的操作,因此使用锁可以确保应用程序始终运行。

最后是一个小型测试类,以展示其工作方式; 并不是在这里我们在调用putPerson()方法之前获得了锁,并在finally块中释放了该锁,以保证释放该锁。

/**
 * A test class to demonstrate the locking at work
 * 
 * @author dinuka.arseculeratne
 * 
 */
public class TestLock {

 public static void main(String[] args) throws InterruptedException {

  Thread t1 = new Thread(new Runnable() {

   @Override
   public void run() {
    
    Person p1 = new Person(1L, 'Test1', 'XYZ');
    try {
PersonLock.getInstance().getWriteLock();
PersonStorage.getInstance().putPerson(p1);
    } catch (InterruptedException e) {
     // Exception handling need to be done
     e.printStackTrace();
    }
   finally{
          PersonLock.getInstance().releaseWriteLock();
    }
   }
  });

  Thread t2 = new Thread(new Runnable() {

   @Override
   public void run() {
    
    Person p2 = new Person(2L, 'Test123', 'ABC');

    try {
PersonLock.getInstance().getWriteLock();

     PersonStorage.getInstance().putPerson(p2);
    } catch (InterruptedException e) {
     // Exception handling need to be done
    }
 finally{
          PersonLock.getInstance().releaseWriteLock();
    }
    
   }
  });

  t1.start();
  t2.start();

  System.out.println(PersonStorage.getInstance().retrievePerson(2));
 }
}

以上是我对使用Sempahores来确保代码线程安全的简短介绍的总结,对于任何想使用该代码的人来说,都可以从
在这里 。 尝试删除Storage类中的锁,并查看其在每次运行中的行为。 您将看到可能发生的比赛情况。

参考: 使用信号灯锁定:我们的JCG合作伙伴 Dinuka Arseculeratne 的示例,来自“ IT之旅”博客。


翻译自: https://www.javacodegeeks.com/2012/10/locking-with-semaphore-example.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值