[英]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. 假设我有一个临时集合(ConcurrentHashMap)来保存某个对象(例如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.
我尝试实现类似于该示例的示例,但出现了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.
我对Main进行了junit测试,以使其易于在您喜欢的IDE或其他地方运行。
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. 添加了CountDownLatch,以便在关闭执行程序服务并打印出结果之前,所有线程都将完成。
Elem uses an AtomicInteger so the synchronized block is no longer necessary. Elem使用AtomicInteger,因此不再需要同步块。
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. 该代码最重要的修复方法是在Sum类的构造函数中增加计数器,以便在每个线程都有机会处理之前,不会从映射中删除Elem。 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.
否则,一个线程可能会一直运行并删除Elem,然后其他线程才有机会执行。
-- 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.
将其全部扔掉,然后使用
java.util.WeakHashMap.
It already does exactly what you're looking for. 它已经完全可以满足您的需求。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.