![](/img/trans.png)
[英]How can I perform a thread-safe get then remove with ConcurrentHashMap?
[英]How Can I remove an item from ConcurrentHashMap when no have a thread holding certain object anymore?
假设我有一个临时集合(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();
}
}
}
我为您整理了一个可行的示例,可以帮助您解决问题。 我对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();
}
}
将其全部扔掉,然后使用java.util.WeakHashMap.
它已经完全可以满足您的需求。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.