多少线程安全性是太多?

我最近一直在读Java Concurrency in Practice——一本很棒的书。如果你认为你知道并发是如何工作的,但大多数时候你面对真正的问题,感觉SWAG是你能做的最多的事情,那么这本书肯定会对这个话题有所启发。当你试图在线程之间共享数据时,有多少事情实际上会出错,这有点可怕。我想这可能让我对线程安全有点疯狂。现在我担心的是,由于同步有点太多,我可能会遇到一些活动问题。下面是一段代码来说明:

   private final Hashtable<String, AtomicInteger> userSessions =
new Hashtable<String, AtomicInteger>();

   public void registerUser(String userLogin) {
       synchronized(userSessions) {
           AtomicInteger sessionCount = userSessions.get(userLogin);
           if (sessionCount != null) {
               sessionCount.incrementAndGet();
           } else {
               userSessions.put(userLogin, new AtomicInteger(1));
           }
       }
   }

   public void unregisterUser(String userLogin) {
       synchronized(userSessions) {
           AtomicInteger sessionCount = userSessions.get(userLogin);
           if (sessionCount != null) {
               sessionCount.decrementAndGet();
           }
       }
   }

   public boolean isUserRegistered(String userLogin) {
       synchronized(userSessions) {
           AtomicInteger sessionCount = userSessions.get(userLogin);
           if (sessionCount == null) {
               return false;
           }
           return sessionCount.intValue() > 0;
       }
   }

我尝试让它变得正确:同步集合在静态部分中构造并存储在静态最终引用中以便安全发布,锁定集合(而不是 - 这样我就不会阻止代码所在的整个类),并对基元使用原子包装器类。书中提到,过度这样做也可能会导致问题,但似乎我需要更多的时间来完全理解它。如何使此代码线程安全,并确保它不会受到活动性和性能问题的影响?this

编辑:把它变成实例方法和变量,最初一切都被声明为静态 - 糟糕,糟糕的设计。还使userSessions私有(不知何故,我之前将其公开)。


答案 1

使用 ,以便您可以使用 .您无需编写代码即可进行同步。ConcurrentHashMapputIfAbsentAtomicInteger

   public final ConcurrentMap<String, AtomicInteger> userSessions =
       new ConcurrentHashMap<String, AtomicInteger>();

   public void registerUser(String userLogin) {
       AtomicInteger newCount = new AtomicInteger(1);
       AtomicInteger oldCount = userSessions.putIfAbsent(userLogin, newCount);
       if (oldCount != null) {
           oldCount.incrementAndGet();
       }
   }

   public void unregisterUser(String userLogin) {
       AtomicInteger sessionCount = userSessions.get(userLogin);
       if (sessionCount != null) {
           sessionCount.decrementAndGet();
       }
   }

   public boolean isUserRegistered(String userLogin) {
       AtomicInteger sessionCount = userSessions.get(userLogin);
       return sessionCount != null && sessionCount.intValue() > 0;
   }

注意,这会泄漏...

尝试非泄漏版本:

   public final ConcurrentMap<String, Integer> userSessions =
       new ConcurrentHashMap<String, Integer>();

   public void registerUser(String userLogin) {
       for (;;) {
           Integer old = userSessions.get(userLogin);
           if (userSessions.replace(userLogin, old, old==null ? 1 : (old+1)) {
                break;
           }
       }
   }
   public void unregisterUser(String userLogin) {
       for (;;) {
           Integer old = userSessions.get(userLogin);
           if (old == null) {
               // Wasn't registered - nothing to do.
               break;
           } else if (old == 1) {
               // Last one - attempt removal.
               if (userSessions.remove(userLogin, old)) {
                   break;
               }
           } else {
               // Many - attempt decrement.
               if (userSessions.replace(userLogin, old, old-1) {
                   break;
               } 
           }
       }
   }
   public boolean isUserRegistered(String userLogin) {serLogin);
       return userSessions.containsKey(userLogin);
   }

答案 2

首先:不要使用哈希表!它很旧,而且很慢。
补充:如果您已经在较高级别上进行同步,则不需要在较低级别上进行同步(对于AtomicInteger事物也是如此)。

我在这里看到了不同的方法,根据这里需要的用例。

读/写方法

假设您经常调用该方法,并且只是不时地调用其他方法,那么一个好方法是读写锁定:允许同时进行多次读取,但只有一个写锁定来统治它们(只有在没有获得其他锁定的情况下才能获得)。isUserRegistered

private static final Map<String, Integer> _userSessions =
  new HashMap<String, Integer>();

private ReadWriteLock rwLock =
  new ReentrantReadWriteLock(false); //true for fair locks

public static void registerUser(String userLogin) {
  Lock write = rwLock.writeLock();
  write.lock();
  try {
       Integer sessionCount = _userSessions.get(userLogin);
       if (sessionCount != null) {
           sessionCount = Integer.valueOf(sessionCount.inValue()+1);
       } else {
           sessionCount = Integer.valueOf(1)
       }
       _userSessions.put(userLogin, sessionCount);
   } finally {
     write.unlock();
   }
}

public static void unregisterUser(String userLogin) {
  Lock write = rwLock.writeLock();
  write.lock();
  try {
       Integer sessionCount = _userSessions.get(userLogin);
       if (sessionCount != null) {
           sessionCount = Integer.valueOf(sessionCount.inValue()-1);
       } else {
           sessionCount = Integer.valueOf(0)
       }
       _userSessions.put(userLogin, sessionCount);
   } finally {
     write.unlock();
   }
}

public static boolean isUserRegistered(String userLogin) {
  boolean result;

  Lock read = rwLock.readLock();
  read.lock();
  try {
       Integer sessionCount = _userSessions.get(userLogin);
       if (sessionCount != null) {
           result = sessionCount.intValue()>0
       } else {
           result = false;
       }
   } finally {
     read.unlock();
   }

   return false;
}

优点:简单易懂
缺点:如果频繁调用写入方法,则不会扩展

小型原子操作方法

这个想法是做一些小步骤,这些都是原子的。无论如何,这将导致非常好的性能,但是这里有很多隐藏的陷阱。

public final ConcurrentMap<String, AtomicInteger> userSessions =
   new ConcurrentHashMap<String, AtomicInteger>();
//There are other concurrent Maps for different use cases

public void registerUser(String userLogin) {
  AtomicInteger count;
  if (!userSession.containsKey(userLogin)){
    AtomicInteger newCount = new AtomicInteger(0);
    count = userSessions.putIfAbsent(userLogin, newCount);
    if (count == null){
      count=newCount;
    }
    //We need ifAbsent here, because another thread could have added it in the meantime
  } else {
    count = userSessions.get(userLogin);
  }
  count.incrementAndGet();
}

public void unregisterUser(String userLogin) {
  AtomicInteger sessionCount = userSessions.get(userLogin);
  if (sessionCount != null) {
    sessionCount.decrementAndGet();
  }
}

public boolean isUserRegistered(String userLogin) {
  AtomicInteger sessionCount = userSessions.get(userLogin);
  return sessionCount != null && sessionCount.intValue() > 0;
}

优点:缩放得很好
缺点:不直观,很快就会变得复杂,并不总是可能的,许多隐藏的陷阱

“每个用户锁定”方法

这将为不同的用户创建锁,假设有很多不同的用户。您可以使用一些小的原子操作创建锁定或监视器,并锁定这些而不是完整列表。
对于这个小示例来说,这将是过度的,但对于非常复杂的结构,它可能是一个优雅的解决方案。


推荐