线程崩溃

时间:2016-06-29 18:56:59

标签: java multithreading

我有一个简单的项目,我正在努力更好地理解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;
    }
}

2 个答案:

答案 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