我有一个简单的项目,旨在更好地理解Java中的锁定和线程。本质上,我有一个缓存对象,该对象启动清理线程以删除超过给定期限的项目。测试类“ Tester”运行两个附加线程,一个用于添加要缓存的项目,另一个用于打印出缓存的内容。由于某些原因,当清理线程修改了嵌入在Cache中的HashMap时,它将停止任何进一步的迭代。我尝试同步访问器/更改器方法以及在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;
}
}
考虑使用ConcurrentHashMap
which是本机线程安全的映射实现,而不是的情况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());
}
}
您还需要使ObjectWrapper
线程安全,因为它是要共享的,否则您的代码将仍然不是线程安全的。最简单的方法是使其不可变,如下所示:
public class ObjectWrapper
{
private final Object obj;
private final long expireTime;
public ObjectWrapper(Object obj, long expireTime)
{
this.obj = obj;
this.expireTime = expireTime;
}
public Object getObj()
{
return obj;
}
public long getExpireTime()
{
return expireTime;
}
}
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句