1

假设我有一个临时集合 (ConcurrentHashMap) 来保存某个对象的引用,例如 HttpSession。当至少一个线程正在使用会话时(在请求的时刻),它不能被删除。但是,当没有更多线程同时使用会话时,应该将其删除以释放内存。我尝试实现一个类似的例子,但我得到了一个 NullPointerException。我做错了什么?:(

    class Elem {
      // AtomicInteger saldo = new AtomicInteger(1000);
      Integer saldo = 1000;
    }

    class Sum implements Runnable {

    Map<String, Elem> saldos;
    AtomicInteger n;

    public Sum(Map<String, Elem> saldos, AtomicInteger n) {
        this.saldos = saldos;
        this.n = n;
    }

    @Override
    public void run() {

        Random rand = new Random();

        int r = rand.nextInt(1000);

        Elem e = this.saldos.get("saldo");

        //Null Pointer Exception occurs here!
        synchronized (e) {

            this.n.incrementAndGet();

            if (r % 2 == 0) {

                Integer saldoLido = e.saldo;

                e.saldo += r;

                Integer saldoAtual = e.saldo;

                System.out.println("saldo lido: " + saldoLido + " somado: " + r
                                   + " saldo atual: " + (saldoAtual) + " "
                                   + System.currentTimeMillis());

            } else {

                Integer saldoLido = e.saldo;

                e.saldo -= r;

                Integer saldoAtual = e.saldo;

                System.out.println("saldo lido: " + saldoLido + " subtraído: "
                                   + r + " saldo atual: " + (saldoAtual) + " "
                                   + System.currentTimeMillis());
            }


             if(this.n.decrementAndGet() == 0)
                 this.saldos.remove("saldo");

        }

    }

    }

    public class Main {

    public static void main(String[] args) throws Exception {

        Map<String, Elem> saldos = new ConcurrentHashMap<>(20, 0.9f, 1);

        AtomicInteger n = new AtomicInteger(0);

        saldos.put("saldo", new Elem());

        ExecutorService exec = Executors.newFixedThreadPool(20);

        try {

            for (int i = 0; i < 20; ++i)
                exec.execute(new Sum(saldos, n));

            exec.shutdown();

            while (!exec.isTerminated()) {}

            System.out.println("got elem: " + saldos.get("saldo") + " " + n);

        } catch (Exception ex) {

            exec.shutdownNow();
            ex.printStackTrace();
        }

    }

    }
4

2 回答 2

3

我为您整理了一个可以帮助您解决问题的工作示例。我让 Main 成为一个 junit 测试,以便在你最喜欢的 IDE 或其他地方轻松运行。

有几点需要注意。

添加了一个 CountDownLatch,以便所有线程在执行程序服务关闭并打印出结果之前完成。

Elem 使用 AtomicInteger,因此不再需要同步块。

对代码最重要的修复是增加 Sum 类的构造函数中的计数器,以便在每个线程有机会处理之前不会从映射中删除 Elem。否则,一个线程可能会一直运行并在其他线程有机会执行之前删除 Elem。

——帕特里克

import java.util.Map;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;

import org.junit.Test;

public class Main
{
    @Test
    public void testExecute() throws Exception
    {
        int threadCount = 20;
        final CountDownLatch threadsCompleteLatch = new CountDownLatch( threadCount );

        Map<String, Elem> saldos = new ConcurrentHashMap<>( threadCount, 0.9f, 1 );
        AtomicInteger counter = new AtomicInteger( 0 );
        Elem element = new Elem();
        saldos.put( "saldo", element );

        ExecutorService exec = Executors.newFixedThreadPool( threadCount );

        try
        {
            for ( int i = 0; i < threadCount; ++i )
            {
                exec.execute( new Sum( threadsCompleteLatch, counter, saldos ) );
            }

            threadsCompleteLatch.await();
            exec.shutdown();

            System.out.println( "got elem: " + saldos.get( "saldo" ) + " counter: " + counter );
            System.out.println( "resulting element: " + element );
        }
        catch ( Exception ex )
        {
            exec.shutdownNow();
            ex.printStackTrace();
        }
    }

}

class Elem
{
    private final AtomicInteger saldo = new AtomicInteger( 1000 );

    public int add( int value )
    {
        return saldo.getAndAdd( value );
    }

    int getSaldo()
    {
        return saldo.get();
    }

    @Override
    public String toString()
    {
        return "Elem{ " +
                "saldo=" + saldo.get() +
                " }";
    }
}

class Sum implements Runnable
{
    private final Random rand = new Random();

    private final CountDownLatch latch;
    private final AtomicInteger counter;
    private final Map<String, Elem> saldos;

    Sum( CountDownLatch latch, AtomicInteger counter, Map<String, Elem> saldos )
    {
        this.latch = latch;
        this.saldos = saldos;
        this.counter = counter;
        counter.incrementAndGet();
    }

    @Override
    public void run()
    {
        int randomValue = rand.nextInt( 1000 );
        Elem element = saldos.get( "saldo" );

        if ( randomValue % 2 != 0 )
        {
            randomValue = -randomValue;
        }

        int saldoLido = element.add( randomValue );
        int saldoAtual = element.getSaldo();
        System.out.println(
                "saldo lido: " + saldoLido + " somado: " + randomValue + " saldo atual: " + (saldoAtual) + " " + System.currentTimeMillis() );

        if ( counter.decrementAndGet() == 0 )
        {
            saldos.remove( "saldo" );
        }

        latch.countDown();
    }
}
于 2013-10-24T00:17:13.627 回答
1

把它全部扔掉并使用java.util.WeakHashMap.它已经完全符合您的要求。

于 2013-10-23T22:59:16.183 回答