简体   繁体   中英

How Can I remove an item from ConcurrentHashMap when no have a thread holding certain object anymore?

Supposing I have a temporary collection (ConcurrentHashMap) to hold a reference for a certain object, eg, a HttpSession. While at least one thread is using a session (in the moment of a request), it can't be removed. But, when no more threads are using a session concurrently, it should be removed to release memory. I tried implement a example similar to that, but I got a NullPointerException. What I'm doing wrong? :(

    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();
        }

    }

    }

I put together a working example for you that may help you with your issue. I made Main a junit test to make it easy to run in your favorite IDE or elsewhere.

A couple of points to note.

A CountDownLatch was added so that all threads would complete before the executor service was shutdown and the result printed out.

Elem uses an AtomicInteger so the synchronized block is no longer necessary.

The most important fix to the code was to increment the counter in the constructor of the Sum class so that the Elem wasn't removed from the map until each thread had a chance to process. Otherwise, it was possible for a thread to run all the way through and remove the Elem before other threads got a chance to execute.

-- Patrick

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();
    }
}

Throw it all away and use a java.util.WeakHashMap. It already does exactly what you're looking for.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM