线程安全多少太多了?

luk*_*m00 26 java concurrency multithreading thread-safety

我最近一直在阅读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;
       }
   }
Run Code Online (Sandbox Code Playgroud)

我试着把它弄好:同步集合在静态部分构造并存储在静态最终引用中以便安全发布,锁定集合(而不是this- 因此我不会阻塞代码所在的整个类)并使用原子原语的包装类.这本书提到过度使用这也可能会引起问题,但似乎我需要更多的时间来完全包裹它.你如何使这个代码线程安全,并确保它不会受到活动和性能问题的影响?

编辑:把它变成实例方法和变量,原来一切都被声明为静态 - 糟糕,糟糕的设计.也使userSessions私有(不知何故我以前把它公之于众).

Tom*_*ine 14

使用一个ConcurrentHashMap,你可以使用putIfAbsent.您无需AtomicInteger编码即可进行同步.

   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;
   }
Run Code Online (Sandbox Code Playgroud)

注意,这个泄漏......

尝试非泄漏版本:

   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);
   }
Run Code Online (Sandbox Code Playgroud)


Har*_*ded 7

首先:不要使用Hashtable!它很旧,而且很慢.
附加:如果您已经在更高级别上同步,则不需要在较低级别上进行同步(对于AtomicInteger-thing也是如此).

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

读/写方法

假设您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;
}
Run Code Online (Sandbox Code Playgroud)

Pro:简单易懂
Con:如果频繁调用write方法,则不会缩放

小原子操作方法

想法是做小步骤,这些步骤都是原子的.无论如何,这将带来非常好的表现,但这里有许多隐藏的陷阱.

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;
}
Run Code Online (Sandbox Code Playgroud)

Pro:非常好的缩放
Con:不直观,很快就会很复杂,并不总是可能,很多隐藏的陷阱

锁定每用户的方法

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


rsp*_*rsp 2

从您的代码来看,您的同步_userSessions应该足够了,因为您没有公开AtomicInteger对象。

AtomicInteger在这种情况下,不需要提供的附加安全性,因此本质上您在这里将其用作可变的Integer。如果您担心额外的开销,您可以放置​​一个包含会话计数的嵌套静态类作为映射中的唯一属性AtomicInteger(或者有点难看:将 int[1] 添加到映射中,只要它们不暴露)本课程之外。)