外部共享资源(智能卡)的Java并发模式
我有一个web服务器服务,客户端请求智能卡计算并获得结果。 可用在服务器正常运行期间,智能卡数量可以减少或增加,例如,我可以从读卡器中实际添加或删除智能卡(或许多其他事件,如异常等) 智能卡计算可能需要一段时间,因此,如果存在对web服务器的并发请求,我必须优化这些作业以使用所有可用的智能卡 我想使用智能卡线程池。不寻常的是,至少对我来说,池的大小不应该根据客户端的请求而改变,而应该只根据智能卡的可用性来改变外部共享资源(智能卡)的Java并发模式,java,multithreading,concurrency,smartcard,threadpoolexecutor,Java,Multithreading,Concurrency,Smartcard,Threadpoolexecutor,我有一个web服务器服务,客户端请求智能卡计算并获得结果。 可用在服务器正常运行期间,智能卡数量可以减少或增加,例如,我可以从读卡器中实际添加或删除智能卡(或许多其他事件,如异常等) 智能卡计算可能需要一段时间,因此,如果存在对web服务器的并发请求,我必须优化这些作业以使用所有可用的智能卡 我想使用智能卡线程池。不寻常的是,至少对我来说,池的大小不应该根据客户端的请求而改变,而应该只根据智能卡的可用性来改变 import java.util.*; import java.util.concu
import java.util.*;
import java.util.concurrent.*;
/**
* A resource pool that expects shared resources
* to be added and removed from the pool by an external process
* (i.e. not done by the pool itself, see {@link #add(Object)} and {@link #remove(Object)}.
* <br>A {@link ResourcePoolValidator} can optionally be used.
* @param <T> resource type handed out by the pool.
*/
public class ResourcePool<T> {
private final Set<T> registered = Collections.newSetFromMap(new ConcurrentHashMap<T, Boolean>());
/* Use a linked list as FIFO queue for resources to lease. */
private final List<T> available = Collections.synchronizedList(new LinkedList<T>());
private final Semaphore availableLock = new Semaphore(0, true);
private final ResourcePoolValidator<T> validator;
public ResourcePool() {
this(null);
}
public ResourcePool(ResourcePoolValidator<T> validator) {
super();
this.validator = validator;
}
/**
* Add a resource to the pool.
* @return true if resource is not already in the pool.
*/
public synchronized boolean add(T resource) {
boolean added = false;
if (!registered.contains(resource)) {
registered.add(resource);
available.add(resource);
availableLock.release();
added = true;
}
return added;
}
/**
* Removes a resource from the pool.
* The resource might be in use (see {@link #isLeased(Object)})
* in which case {@link ResourcePoolValidator#abandoned(Object)} will be called
* when the resource is no longer used (i.e. released).
* @return true if resource was part of the pool and removed from the pool.
*/
public synchronized boolean remove(T resource) {
// method is synchronized to prevent multiple threads calling add and remove at the same time
// which could in turn bring the pool in an invalid state.
return registered.remove(resource);
}
/**
* If the given resource is (or was, see also {@link #remove(Object)} part of the pool,
* a returned value true indicates the resource is in use / checked out.
* <br>This is a relative expensive method, do not call it frequently.
*/
public boolean isLeased(T resource) {
return !available.contains(resource);
}
/**
* Try to get a shared resource for usage.
* If a resource is acquired, it must be {@link #release(Object)}d in a finally-block.
* @return A resource that can be exclusively used by the caller.
* @throws InterruptedException When acquiring a resource is interrupted.
* @throws TimeoutException When a resource is not available within the given timeout period.
*/
public T tryAcquire(long timeout, TimeUnit tunit) throws InterruptedException, TimeoutException {
T resource = null;
long timeRemaining = tunit.toMillis(timeout);
final long tend = System.currentTimeMillis() + timeRemaining;
do {
if (availableLock.tryAcquire(timeRemaining, TimeUnit.MILLISECONDS)) {
resource = available.remove(0);
if (registered.contains(resource)) {
boolean valid = false;
try {
valid = (validator == null ? true : validator.isValid(resource));
} catch (Exception e) {
// TODO: log exception
e.printStackTrace();
}
if (valid) {
break; // return the "checked out" resource
} else {
// remove invalid resource from pool
registered.remove(resource);
if (validator != null) {
validator.abandoned(resource);
}
}
}
// resource was removed from pool, try acquire again
// note that this implicitly lowers the maximum available resources
// (an acquired permit from availableLock goes unused).
// TODO: retry puts us at the back of availableLock queue but should put us at the front of the queue
resource = null;
}
timeRemaining = tend - System.currentTimeMillis();
} while (timeRemaining > 0L);
if (resource == null) {
throw new TimeoutException("Unable to acquire a resource within " + tunit.toMillis(timeout) + " ms.");
}
return resource;
}
/**
* This method must be called by the caller / client whenever {@link #tryAcquire(long, TimeUnit)}
* has returned a resource. If the caller has determined the resource is no longer valid,
* the caller should call {@link #remove(Object)} before calling this method.
* @param resource no longer used.
*/
public void release(T resource) {
if (resource == null) {
return;
}
if (registered.contains(resource)) {
available.add(resource);
availableLock.release();
} else {
if (validator != null) {
validator.abandoned(resource);
}
}
}
/** An array (copy) of all resources registered in the pool. */
@SuppressWarnings("unchecked")
public T[] getRegisteredResources() {
return (T[]) registered.toArray(new Object[registered.size()]);
}
}
我研究了许多例子:
- BlockingQueue:存储请求并停止线程等待处理看起来不错
- FutureTask:我可以使用这个类让客户端等待它的答案,但是哪种类型的执行者应该执行这个任务呢
- ThreadPoolExecutor:这似乎是我需要的,但我无法更改池大小,而且每个线程都应该链接到一个智能卡插槽。如果我可以更改池大小(插入智能卡时添加线程,卸下智能卡时删除线程),并且可以为每个线程分配特定的智能卡,则这可能是一个解决方案
public class SmartcardWrapper{
private int slot;
public SmartcardWrapper(int slot) {
this.slot=slot;
}
public byte[] compute(byte[] input) {
byte[] out=new byte[];
SmartcardApi.computerInput(slot,input,out); //Native method
return out;
}
}
我尝试创建每个智能卡一个线程的线程池:
private class SmartcardThread extends Thread{
protected SmartcardWrapper sw;
public SmartcardThread(SmartcardWrapper sw){
this.sw=sw;
}
@Override
public void run() {
while(true){
byte[] input=queue.take();
byte output=sw.compute(input);
// I have to return back the output to the client
}
}
}
每个人都在同一输入队列中等待某些内容:
BlockingQueue<byte[]> queue=new BlockingQueue<byte[]>();
BlockingQueue queue=new BlockingQueue();
但是如何将智能卡线程的输出返回到Web服务器客户端呢?这让我觉得阻塞队列不是我的解决方案
如何处理这个问题?我应该遵循哪种并发模式?
每个智能卡分配一个线程是正确的还是我可以简单地使用信号量?通过查看需求,最好的体系结构是将智能卡的计算与web服务解耦 依赖Web服务等待处理器密集型任务将导致超时 最好的解决方案是使用定期作业预计算智能卡,并将这些插槽、计算对存储在缓存服务器(如Redis)中 智能卡同步器作业是一个独立的J2SE独立应用程序,它定期检查哪个智能卡可用且处于活动状态(无错误),并将插槽和计算作为密钥/值对更新Redis缓存。如果智能卡不可用,它将从缓存中删除 Web服务只需检查Redis缓存中的特定插槽键,如果找到值,则返回该值,否则返回该插槽的未找到值(不可用或错误) 此设计在智能卡端和客户端请求端均可扩展。您的假设: ThreadPoolExecutor:这似乎是我所需要的,但我无法更改池大小,而且每个线程都应该链接到一个智能卡插槽 这是不对的
您可以动态设置线程池大小。
看看下面的API
设置允许的最大线程数。这将覆盖构造函数中设置的任何值。如果新值小于当前值,则多余的现有线程将在下次空闲时终止
设置核心线程数。这将覆盖构造函数中设置的任何值。如果新值小于当前值,则多余的现有线程将在下次空闲时终止。如果较大,如果需要,将启动新线程以执行任何排队的任务
ThreadPoolExecutor
将根据corePoolSize
和maximumPoolSize
设置的边界自动调整池大小
在方法execute(java.lang.Runnable)
中提交新任务时,如果运行的线程少于corePoolSize
线程,则会创建一个新线程来处理该请求,即使其他工作线程处于空闲状态
如果运行的线程数大于corePoolSize
但小于maximumPoolSize
线程数,则仅当队列已满时才会创建新线程
通过将maximumPoolSize
设置为基本无界值,例如Integer.MAX_value
,可以允许池容纳任意数量的并发任务。但是我不建议有那么多的线程。小心设置此值
最典型的情况是,核心池和最大池大小仅在构建时设置,但也可以使用setCorePoolSize(int
)和setMaximumPoolSize(int)
动态更改
编辑:
为了更好地利用线程池,如果您知道最大卡数为6,则可以使用
ExecutorService executor = Executors.newFixedThreadPool(6);
或者您考虑过使用吗
您需要维护一个SmartcardWrapper对象池,其中每个SmartcardWrapper将表示一个物理智能卡。无论何时需要进行新计算,都可以从池中借用对象,进行计算并返回池中的对象,以便下一个线程可以重用它
池本身是线程安全的,并且在没有可用对象时阻塞。您只需实现一个api即可将SmartcardWrapper对象添加/删除到池中。在回答有关如何将结果返回给调用者的问题时: 每个人都在同一输入队列中等待某些内容:
BlockingQueue<byte[]> queue=new BlockingQueue<byte[]>();
BlockingQueue=新建BlockingQueue()
但是如何将智能卡线程的输出返回到
Web服务器客户端?这让我觉得阻塞队列不是我的职责
解决方案
你的信
ExecutorService executor = Executors.newFixedThreadPool(6);
BlockingQueue<JobSubmitRec> queue=new BlockingQueue<JobSubmitRec>();
class JobSubmitRec
{
byte[] data;
BlockingQueue<JobSubmitResult> result=new LinkedBlockingQueue<JobSubmitResult>();
}
public void run() {
while(true){
JobSubmitRec submitrec = queue.take();
byte[] input = submitrec.data;
byte output = sw.compute(input);
submitrec.result.put( new JobSubmitResult(output) );
}
}
JobSubmitRec jsr = new JobSubmitRec( data );
queue.put( jsr );
JobSubmitResult result = jsr.result.take();
// use result here
import java.util.*;
import java.util.concurrent.*;
/**
* A resource pool that expects shared resources
* to be added and removed from the pool by an external process
* (i.e. not done by the pool itself, see {@link #add(Object)} and {@link #remove(Object)}.
* <br>A {@link ResourcePoolValidator} can optionally be used.
* @param <T> resource type handed out by the pool.
*/
public class ResourcePool<T> {
private final Set<T> registered = Collections.newSetFromMap(new ConcurrentHashMap<T, Boolean>());
/* Use a linked list as FIFO queue for resources to lease. */
private final List<T> available = Collections.synchronizedList(new LinkedList<T>());
private final Semaphore availableLock = new Semaphore(0, true);
private final ResourcePoolValidator<T> validator;
public ResourcePool() {
this(null);
}
public ResourcePool(ResourcePoolValidator<T> validator) {
super();
this.validator = validator;
}
/**
* Add a resource to the pool.
* @return true if resource is not already in the pool.
*/
public synchronized boolean add(T resource) {
boolean added = false;
if (!registered.contains(resource)) {
registered.add(resource);
available.add(resource);
availableLock.release();
added = true;
}
return added;
}
/**
* Removes a resource from the pool.
* The resource might be in use (see {@link #isLeased(Object)})
* in which case {@link ResourcePoolValidator#abandoned(Object)} will be called
* when the resource is no longer used (i.e. released).
* @return true if resource was part of the pool and removed from the pool.
*/
public synchronized boolean remove(T resource) {
// method is synchronized to prevent multiple threads calling add and remove at the same time
// which could in turn bring the pool in an invalid state.
return registered.remove(resource);
}
/**
* If the given resource is (or was, see also {@link #remove(Object)} part of the pool,
* a returned value true indicates the resource is in use / checked out.
* <br>This is a relative expensive method, do not call it frequently.
*/
public boolean isLeased(T resource) {
return !available.contains(resource);
}
/**
* Try to get a shared resource for usage.
* If a resource is acquired, it must be {@link #release(Object)}d in a finally-block.
* @return A resource that can be exclusively used by the caller.
* @throws InterruptedException When acquiring a resource is interrupted.
* @throws TimeoutException When a resource is not available within the given timeout period.
*/
public T tryAcquire(long timeout, TimeUnit tunit) throws InterruptedException, TimeoutException {
T resource = null;
long timeRemaining = tunit.toMillis(timeout);
final long tend = System.currentTimeMillis() + timeRemaining;
do {
if (availableLock.tryAcquire(timeRemaining, TimeUnit.MILLISECONDS)) {
resource = available.remove(0);
if (registered.contains(resource)) {
boolean valid = false;
try {
valid = (validator == null ? true : validator.isValid(resource));
} catch (Exception e) {
// TODO: log exception
e.printStackTrace();
}
if (valid) {
break; // return the "checked out" resource
} else {
// remove invalid resource from pool
registered.remove(resource);
if (validator != null) {
validator.abandoned(resource);
}
}
}
// resource was removed from pool, try acquire again
// note that this implicitly lowers the maximum available resources
// (an acquired permit from availableLock goes unused).
// TODO: retry puts us at the back of availableLock queue but should put us at the front of the queue
resource = null;
}
timeRemaining = tend - System.currentTimeMillis();
} while (timeRemaining > 0L);
if (resource == null) {
throw new TimeoutException("Unable to acquire a resource within " + tunit.toMillis(timeout) + " ms.");
}
return resource;
}
/**
* This method must be called by the caller / client whenever {@link #tryAcquire(long, TimeUnit)}
* has returned a resource. If the caller has determined the resource is no longer valid,
* the caller should call {@link #remove(Object)} before calling this method.
* @param resource no longer used.
*/
public void release(T resource) {
if (resource == null) {
return;
}
if (registered.contains(resource)) {
available.add(resource);
availableLock.release();
} else {
if (validator != null) {
validator.abandoned(resource);
}
}
}
/** An array (copy) of all resources registered in the pool. */
@SuppressWarnings("unchecked")
public T[] getRegisteredResources() {
return (T[]) registered.toArray(new Object[registered.size()]);
}
}
import java.util.concurrent.TimeUnit;
/**
* Used by a {@link ResourcePool} to validate a resource before handing it out for lease
* (see {@link #isValid(Object)} and signal a resource is no longer used (see {@link #abandoned(Object)}).
*/
public class ResourcePoolValidator<T> {
/**
* Overload this method (this method does nothing by default)
* to validate a resource before handing it out for lease.
* If this method returns false or throws an exception (which it preferably should not do),
* the resource is removed from the pool.
* @return true if the resource is valid for leasing
*/
public boolean isValid(T resource) {
return true;
}
/**
* Called by the {@link ResourcePool#release(Object)} method when a resource is released by a caller
* but the resource was previously removed from the pool and in use.
* <br>Called by {@link ResourcePool#tryAcquire(long, TimeUnit)} if a resource if not valid
* (see {@link #isValid(Object)}.
* <br>Overload this method (this method does nothing by default) to create a notification of an unused resource,
* do NOT do any long period of processing as this method is called from a caller (client) thread.
*/
public void abandoned(T resource) {
// NO-OP
}
}