我有一个简单的项目,我正在努力更好地理解Java中的锁定和线程。基本上我有一个缓存对象,它启动一个清理线程来删除超过给定年龄的项目。测试类“Tester”运行另外两个线程,一个用于向缓存添加项目,另一个用于打印缓存内容。出于某种原因,当清理线程修改嵌入在Cache中的HashMap时,它会停止任何进一步的迭代。我已经尝试同步accessor / mutator方法以及在Cache中围绕LOCK对象进行同步。任何想法或帮助将是muy beueno。 ;)
public class Cache
{
private HashMap<Object, ObjectWrapper> cachedObjects = new HashMap<>();
private static Cache cache = null;
private static int TIME_TO_KEEP = 60000; // 60 seconds
private final static Object LOCK = new Object();
public static Cache getInstance()
{
if (cache == null)
{
cache = new Cache();
}
return cache;
}
private Cache()
{
ScheduledExecutorService executor = Executors.newScheduledThreadPool(2);
Runnable task = () -> {
synchronized(LOCK)
{
System.out.println("Cleanup");
Set<Object> keys = cachedObjects.keySet();
final long now = System.currentTimeMillis();
keys.forEach(k -> {
try
{
{
ObjectWrapper ow = cachedObjects.get(k);
if(ow.getExpireTime() < now)
{
int size = cachedObjects.size();
cachedObjects.remove(k, ow);
System.out.println("DEL:" + k + ", from " + size + " to " + cachedObjects.size());
}
}
}
catch (Throwable t)
{
t.printStackTrace();
}
});
}
};
executor.scheduleWithFixedDelay(task, 5, 15, TimeUnit.SECONDS);
}
public void addObject(Object key, Object obj)
{
synchronized(LOCK)
{
ObjectWrapper ow = new ObjectWrapper(obj, System.currentTimeMillis() + TIME_TO_KEEP);
cachedObjects.put(key, ow);
}
}
public ObjectWrapper getObj(Object key)
{
synchronized(LOCK)
{
return cachedObjects.get(key);
}
}
public Collection<ObjectWrapper> getValues()
{
synchronized(LOCK)
{
return cachedObjects.values();
}
}
public Set<Object> getKeys()
{
synchronized(LOCK)
{
return cachedObjects.keySet();
}
}
public static void main(String[] args)
{
Cache cache = Cache.getInstance();
}
}
import java.util.*;
import java.util.concurrent.*;
public class Tester
{
private static Integer id = 0;
private static Cache cache = Cache.getInstance();
public static void main(String[] args)
{
ScheduledExecutorService executor = Executors.newScheduledThreadPool(2);
Runnable adder = () -> {
System.out.println("Adding id:" + ++id);
Object o = new Object();
cache.addObject(id, o);
};
executor.scheduleWithFixedDelay(adder, 5, 10, TimeUnit.SECONDS);
Runnable tester = () -> {
long currTime = System.currentTimeMillis();
System.out.println("Test: ");
Set<Object> keys = cache.getKeys();
keys.forEach(k -> {
ObjectWrapper o = cache.getObj(k);
System.out.println(k + ">" + currTime + "::" + o.getExpireTime());
});
};
executor.scheduleWithFixedDelay(tester, 20, 10, TimeUnit.SECONDS);
}
}
public class ObjectWrapper
{
private Object obj;
private long expireTime;
public ObjectWrapper(Object obj, long expireTime)
{
this.obj = obj;
this.expireTime = expireTime;
}
public Object getObj()
{
return obj;
}
public void setObj(Object obj)
{
this.obj = obj;
}
public long getExpireTime()
{
return expireTime;
}
public void setExpireTime(long expireTime)
{
this.expireTime = expireTime;
}
}
答案 0 :(得分:2)
考虑使用integer
这是本地线程安全的地图实现,而不是supplementals_ids
的情况。
你的错误主要在这里:
ConcurrentHashMap
这样做是不够的,因为您共享非线程安全集合中实际提供的密钥和HashMap
的值,这样您在迭代其内容时需要同步访问权限,或者您可以简单地如下所示返回安全副本:
public Collection<ObjectWrapper> getValues()
{
synchronized(LOCK)
{
return cachedObjects.values();
}
}
public Set<Object> getKeys()
{
synchronized(LOCK)
{
return cachedObjects.keySet();
}
}
您还需要使HashMap
线程安全,因为它意味着要共享,否则您的代码将不再是线程安全的。对此最简单的方法是使其不可变:
public Collection<ObjectWrapper> getValues()
{
synchronized(LOCK)
{
return new ArrayList<>(cachedObjects.values());
}
}
public Set<Object> getKeys()
{
synchronized(LOCK)
{
return new HashSet<>(cachedObjects.keySet());
}
}
答案 1 :(得分:1)
您正在迭代它时从keySet中删除元素,从而导致在下一次迭代时抛出的ConcurrentModificationException
(在try-catch块之外)。如果在循环外移动try-catch块:
private Cache()
{
ScheduledExecutorService executor = Executors.newScheduledThreadPool(2);
Runnable task = () -> {
synchronized(LOCK)
{
try
{
System.out.println("Cleanup");
Set<Object> keys = cachedObjects.keySet();
final long now = System.currentTimeMillis();
keys.forEach(k -> {
{
ObjectWrapper ow = cachedObjects.get(k);
if(ow.getExpireTime() < now)
{
int size = cachedObjects.size();
cachedObjects.remove(k, ow);
System.out.println("DEL:" + k + ", from " + size + " to " + cachedObjects.size());
}
}
});
}
catch (Throwable t)
{
t.printStackTrace();
}
}
};
executor.scheduleWithFixedDelay(task, 5, 15, TimeUnit.SECONDS);
}
你会得到堆栈跟踪:
java.util.ConcurrentModificationException
at java.util.HashMap$KeySet.forEach(HashMap.java:935)
at Cache.lambda$new$1(Cache.java:32)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:308)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$301(ScheduledThreadPoolExecutor.java:180)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:294)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
相反,在keySet上使用迭代器:
private Cache()
{
ScheduledExecutorService executor = Executors.newScheduledThreadPool(2);
Runnable task = () -> {
synchronized(LOCK)
{
try
{
System.out.println("Cleanup");
final long now = System.currentTimeMillis();
final Iterator<Map.Entry<Object, ObjectWrapper>> iter = cachedObjects.entrySet().iterator();
while (iter.hasNext()) {
final Map.Entry<Object, ObjectWrapper> entry = iter.next();
final ObjectWrapper ow = entry.getValue();
if(ow.getExpireTime() < now)
{
final Object k = entry.getKey();
int size = cachedObjects.size();
iter.remove();
System.out.println("DEL:" + k + ", from " + size + " to " + cachedObjects.size());
}
}
}
catch (Throwable t)
{
t.printStackTrace();
}
}
};
executor.scheduleWithFixedDelay(task, 5, 15, TimeUnit.SECONDS);
}
这样做会产生预期的输出:
Cleanup
Adding id:1
Adding id:2
Cleanup
Test:
1>1467228864763::1467228909763
2>1467228864763::1467228919764
Adding id:3
Test:
1>1467228874766::1467228909763
2>1467228874766::1467228919764
3>1467228874766::1467228929764
Cleanup
Adding id:4
Test:
1>1467228884766::1467228909763
2>1467228884766::1467228919764
3>1467228884766::1467228929764
4>1467228884766::1467228939764
Adding id:5
Cleanup
Test:
1>1467228894770::1467228909763
2>1467228894770::1467228919764
3>1467228894770::1467228929764
4>1467228894770::1467228939764
5>1467228894770::1467228949765
Adding id:6
Test:
1>1467228904771::1467228909763
2>1467228904771::1467228919764
3>1467228904771::1467228929764
4>1467228904771::1467228939764
5>1467228904771::1467228949765
6>1467228904771::1467228959765
Cleanup
Adding id:7
Test:
1>1467228914771::1467228909763
2>1467228914771::1467228919764
3>1467228914771::1467228929764
4>1467228914771::1467228939764
5>1467228914771::1467228949765
6>1467228914771::1467228959765
7>1467228914771::1467228969765
Adding id:8
Cleanup
DEL:1, from 8 to 7
DEL:2, from 7 to 6
Test:
3>1467228924772::1467228929764
4>1467228924772::1467228939764
5>1467228924772::1467228949765
6>1467228924772::1467228959765
7>1467228924772::1467228969765
8>1467228924772::1467228979765
Adding id:9
Test:
3>1467228934772::1467228929764
4>1467228934772::1467228939764
5>1467228934772::1467228949765
6>1467228934772::1467228959765
7>1467228934772::1467228969765
8>1467228934772::1467228979765
9>1467228934772::1467228989766
Cleanup
DEL:3, from 7 to 6
Adding id:10
Test:
4>1467228944773::1467228939764
5>1467228944773::1467228949765
6>1467228944773::1467228959765
7>1467228944773::1467228969765
8>1467228944773::1467228979765
9>1467228944773::1467228989766
10>1467228944773::1467228999766
Adding id:11
Cleanup
DEL:4, from 8 to 7
DEL:5, from 7 to 6
Test:
6>1467228954773::1467228959765
7>1467228954773::1467228969765
8>1467228954773::1467228979765
9>1467228954773::1467228989766
10>1467228954773::1467228999766
11>1467228954773::1467229009766
Adding id:12
Test:
6>1467228964774::1467228959765
7>1467228964774::1467228969765
8>1467228964774::1467228979765
9>1467228964774::1467228989766
10>1467228964774::1467228999766
11>1467228964774::1467229009766
12>1467228964774::1467229019767
Cleanup
DEL:6, from 7 to 6
Adding id:13