因为,statckoverflow不允许在原始问题中为你的问题添加更多内容(你只能添加注释,而不是代码)我在这里问一个顺序问题我原来的问题: Can we use Synchronized for each entry instead of ConcurrentHashMap?
问题很简单,我不知道为什么这么简单的问题可能很多人在我之前遇到过这个问题我应该花这么多时间:/
问题是:我有一个hashmap,我想当一个线程正在处理hashMap的一个条目时,没有任何其他线程访问该对象,并且我不想锁定整个hashMap。
我知道java提供了ConcurrentHashMap,但是当你想做一些比简单的put和get更复杂的事情时,ConcurrentHashMap并没有解决问题。即使是新增的函数(在Java 8中),如合并也不足以应对复杂的场景。
例如:
假设我想要一个将字符串映射到ArrayLists的哈希映射。然后例如假设我想这样做: 对于密钥k,如果有任何条目,则将newString添加到其ArrayList,但如果没有k的条目,则为k创建条目,使其ArrayList具有newString。
我以为我可以这样做:
ArrayList<String> tm =new ArrayList<String>();
tm.add(newString);
Object result = map.putIfAbsent(k, tm);
if (result != null)
{
map.get(k).add(newString);
}
但它不起作用,为什么?假设putIfAbset返回null之外的其他内容,则表示map已经有一个带有键k的条目,所以我将尝试将newString添加到已存在条目的ArrayList中,但是在添加之前,另一个线程可能会删除该条目,并且然后我会得到NullPointerException!
所以,我发现很难正确地编写这些东西。
但我在想,如果我能简单地锁定那个条目,那么生活将是美好的!
在我之前的帖子中,我提出了一些非常简单的事实,实际上消除了对concurrentHashMap的需要,并提供了入门级锁定,但有些人认为这不是真的,因为Long不是一成不变的......我没有得到好吧。
现在,我实施并测试了它,它对我来说很好,但我不知道为什么其他更有经验的开发人员告诉我它不是线程安全的:(
这是我测试的确切代码:
MainThread:
import java.util.HashMap;
public class mainThread {
public static HashMap<String, Long> map = new HashMap<String, Long>();
public static void main (String args[])
{
map.put("k1", new Long(32));
synchronized(map.get("k1"))
{
Thread t = new Thread(new threadA());
t.start();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
ThreadA:
public class ThreadA implements Runnable {
@Override
public void run() {
mainThread.map.put("k2", new Long(21));
System.out.println(mainThread.map.get("k2"));
synchronized (mainThread.map.get("k1")) {
System.out.println("Insdie synchronized of threadA");
}
}
}
工作正常!它打印21,并且在5秒之后,mainThread释放了map.get的锁定(&#34; k1&#34;),它打印&#34; Insdie同步的threadA&#34;
那么,为什么使用这种简单的方法我们无法提供入门级锁定?!为什么并发应该是那么复杂的Lol(开玩笑)
答案 0 :(得分:5)
首先,我所知道的没有提供入门级锁定的标准地图实现。
但我认为你可以避免这种需要。例如
更新...纠正错误
ArrayList<String> tm = new ArrayList<String>();
ArrayList<String> old = map.putIfAbsent(k, tm);
if (old != null) {
tm = old;
}
synchronized (tm) {
// can now add / remove entries and this will appear as an atomic
// actions to other threads that are using `synchronized` to
// access or update the list
tm.add(string1);
tm.add(string2);
}
是的,另一个线程可能会更新此线程(可能)插入它之间的hashmap条目中的列表,并且此线程将其锁定。但是,这没关系。 (更正的)putIfAbsent
以及随后的测试确保每个人都使用并锁定相同的列表。
(假设:插入/更新条目时所有线程都使用此逻辑。)
如果列表变空是以原子方式删除列表很困难,但我认为通常没必要这样做。
更新2
有一种更好的方法:
ArrayList<String> tm = map.computeIfAbsent(k, ArrayList::new);
synchronized (tm) {
...
}
(谢谢斯图尔特)
更新3
我们也可以通过合并来实现。
也许,是的。像这样:
ArrayList<String> tm = new ArrayList<String>;
tm.add(...);
...
map.merge(key, tm, (oldV, newV) -> {oldV.addAll(newV); return oldV});
缺点是你要对tm
的所有元素进行双重处理;即添加到2个单独的列表中(其中一个是你抛出的)。
但你也可以这样做:
map.merge(key, tm, (oldV, newV) -> {
oldV.removeAll(newV);
return oldV.size() == 0 ? null : oldV}
);
我担心的是javadoc没有明确说明值oldV
会在发生这种情况时被锁定。它说:
“整个方法调用是以原子方式执行的。当计算正在进行时,其他线程可能会阻止在此映射上尝试的某些更新操作......”
...但是当发生这种情况时,它没有明确说明值上存在互斥。 (例如,将此方法与putIfAbsent
/ computeIfAbsent
和明确的synchronized
块混合使用很可能会造成危险。锁定最有可能出现在不同的对象上。)
答案 1 :(得分:0)
嗯,第一个巨大的问题是你甚至没有尝试对put
个电话进行任何锁定。对于常规HashMap,这些不是自动线程安全的。您似乎认为单独的HashMap条目是自动完全独立的,但HashMaps不会那样工作。
即使您修复了put
问题(可能还需要ConcurrentHashMap或整个地图锁),实际锁定的部分也不会安全锁定。
假设线程1 put
是条目"k1": 1
,线程2尝试get("k1")
。线程2会看到什么?
好吧,在get
调用已经完成之前,线程2甚至都没有尝试获取任何锁定。 get
电话完全没有受到保护!如果put
和get
之间没有任何先发生的关系,则get
调用可能看不到该条目,或者它可能会看到该条目,或者它可能会看到不一致的地图中间状态和可怕的崩溃。
同步get
来电的结果太晚了。
答案 2 :(得分:0)
我想我终于找到了使用合并功能的解决方案。我提供了一个示例,我将编辑此帖子以便其他人阅读,但我现在发帖以获得您的反馈。
以下是ConcurrentHashMap的示例,其中ConcurrentHashMaps作为其值(为了示例,23和1只是两个随机值):
function addPKCS5Padding($input)
{
$blockSize = 16;
$padd = "";
$length = $blockSize - (strlen($input) % $blockSize);
for ($i = 1; $i <= $length; $i++)
{
$padd .= chr($length);
}
return $input . $padd;
}
function removePKCS5Padding($input)
{
$blockSize = 16;
$padChar = ord($input[strlen($input) - 1]);
$unpadded = substr($input, 0, (-1) * $padChar);
return $unpadded;
}
function encryptAes($string, $key)
{
$string = addPKCS5Padding($string);
$crypt = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $string, MCRYPT_MODE_CBC, $key);
return strtoupper(bin2hex($crypt));
}
function decryptAes($strIn, $myEncryptionPassword)
{
#Sagepay specific - remove the '@'
$strIn = substr($strIn,1);
$strInitVector = $myEncryptionPassword;
$strIn = pack('H*', $hex);
$string = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $myEncryptionPassword, $strIn, MCRYPT_MODE_CBC,$strInitVector);
return removePKCS5Padding($string);
}
这就是代码的作用:
Long intialValue = new Long(3);
Long addedValue = new Long(10);
Long removingValue = new Long (5);
ConcurrentHashMap<Integer, ConcurrentHashMap<Integer, Long>> map = new ConcurrentHashMap<>();
//Initialization....
ConcurrentHashMap<Integer, Long> i = new ConcurrentHashMap<Integer, Long>();
i.put(1, intialValue);
map.put(23, i);
//......
//addition
ConcurrentHashMap<Integer, Long> c = new ConcurrentHashMap<Integer, Long>();
c.put(1, addedValue);
map.merge(23, c, (oldHashMap, newHashMap) -> {
oldHashMap.merge (1, c.get(1), (oldV, newV) -> {
if (oldV < newV) return newV; else return oldV;
});
return oldHashMap;
});
//removal
// we want to remove entry 1 from the inner HashMap if its value is less than 2, and if the entry is empty remove the entry from the outer HashMap
ConcurrentHashMap<Integer, Long> r = new ConcurrentHashMap<Integer, Long>();
r.put(1, removingValue);
map.merge (23, r, (oldHashMap, newHashMap) -> {
oldHashMap.merge(1, newHashMap.get(1), (oldV, newV) -> {if (oldV < newV) return newV; else return oldV;});
return oldHashMap;
});
map.remove(23, r);
if (map.containsKey(23))
{
System.out.println("Map contains key 23");
if (map.get(23).containsKey(1))
{
System.out.println("The value for <23,1> is " + map.get(23).get(1));
}
}
,用于键1。initialValue
,它会检查它的值,它会用addedValue
覆盖它,否则它将不管它。 addedValue
,它会删除该值,如果是删除后,键23的hashMap为空,它从主映射中删除键23。 我测试了这段代码。例如:
我希望它是线程安全的,因为我刚使用了merge方法。这段代码的一个缺点是我正在添加一些东西来映射然后删除它,因为ConcurrentHashMap没有类似于merge的删除方法。我希望我有这个方法:
map.remove(keyToRemove,条件)