Performance of ArrayBlockingQueue and SubmissionPublisher - java

I have written performance tests of single producer single consumer scenario against ArrayBlockingQueue and SubmissionPublisher. I am surprised that SubmissionPublisher performs far better than ArrayBlockingQueue (or my tests are wrong).
Both use array as the buffer, so why is the result so different?
Benchmark Mode Cnt Score Error Units
ArrayBlockingQueuePerfTest.runBenchmark ss 5 16.772 ± 0.538 s/op
SubmissionPublisherPerfTest.runBenchmark ss 5 8.138 ± 0.061 s/op
Test for ArrayBlockingQueue
#State(Scope.Benchmark)
public class ArrayBlockingQueuePerfTest {
private BlockingQueue<Event> queue;
private CountDownLatch latch;
private ExecutorService executor;
private TestRunnable testRunnable;
private Future<?> future;
public static void main(String[] args) throws Exception {
org.openjdk.jmh.Main.main(new String[] { ArrayBlockingQueuePerfTest.class.getCanonicalName() });
}
#Benchmark
#BenchmarkMode(Mode.SingleShotTime)
#Fork(value = 1)
#Warmup(iterations = 5)
#Measurement(iterations = 5)
public void runBenchmark() throws Exception {
for (int i=0; i<100_000_000; ++i) {
queue.put(new Event(i));
}
latch.await();
}
#Setup(Level.Trial)
public void setupAll() {
queue = new ArrayBlockingQueue<>(16384);
executor = Executors.newSingleThreadExecutor();
testRunnable = new TestRunnable(queue);
future = executor.submit(testRunnable);
}
#Setup(Level.Iteration)
public void setup() {
latch = new CountDownLatch(100_000_000);
testRunnable.reset(latch);
}
#TearDown(Level.Trial)
public void tearDown() {
future.cancel(true);
executor.shutdown();
}
static class TestRunnable implements Runnable {
private final BlockingQueue<Event> queue;
private CountDownLatch latch;
public TestRunnable(BlockingQueue<Event> queue) {
this.queue = queue;
}
public void reset(CountDownLatch latch) {
this.latch = latch;
}
#Override
public void run() {
while (true) {
try {
queue.take();
latch.countDown();
} catch (InterruptedException e) {
break;
}
}
}
}
}
Test for SubmissionPublisher
#State(Scope.Benchmark)
public class SubmissionPublisherPerfTest {
private ExecutorService executor;
private SubmissionPublisher<Event> publisher;
private TestSubscriber subscriber;
private CountDownLatch latch;
public static void main(String[] args) throws Exception {
org.openjdk.jmh.Main.main(new String[] { SubmissionPublisherPerfTest.class.getCanonicalName() });
}
#Benchmark
#BenchmarkMode(Mode.SingleShotTime)
#Fork(value = 1)
#Warmup(iterations = 5)
#Measurement(iterations = 5)
public void runBenchmark() throws Exception {
for (int i=0; i<100_000_000; ++i) {
publisher.submit(new Event(i));
}
latch.await();
}
#Setup(Level.Trial)
public void setupAll() {
executor = Executors.newFixedThreadPool(1);
publisher = new SubmissionPublisher<>(executor, 16384);
subscriber = new TestSubscriber();
publisher.subscribe(subscriber);
}
#Setup(Level.Iteration)
public void setup() {
latch = new CountDownLatch(100_000_000);
subscriber.reset(latch);
}
#TearDown(Level.Trial)
public void tearDownAll() {
executor.shutdown();
}
static class TestSubscriber implements Flow.Subscriber<Event> {
private CountDownLatch latch;
private Flow.Subscription subscription;
public void reset(CountDownLatch latch) {
this.latch = latch;
}
#Override
public void onSubscribe(Flow.Subscription subscription) {
(this.subscription = subscription).request(1);
}
#Override
public void onNext(Event item) {
this.subscription.request(1);
latch.countDown();
}
#Override
public void onError(Throwable throwable) {}
#Override
public void onComplete() {}
}
}
The Event object
public class Event {
private int value;
public Event() {
}
public Event(int value) {
this.value = value;
}
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
}

Related

Design Executor service based on task weightage

Need help in designing my executor service or use existing if these functionalities are available.
Let say, I have a total computational capacity up to 10.
I'll assign each task some weightage (2,4,6).
Submitted tasks should run based on weightage to use max 10. for example (5 thread of 2 weightage task, or 2,2,6 or 4,6).
Maybe, it's implementation you need:
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;
interface WeightageTask extends Runnable {
WeightageTask POISON = new WeightageTask() {};
default void run() {}
default int getWeightage() { return 0; }
}
public class WeightageExecutorService implements ExecutorService {
private volatile boolean shutdown;
private volatile boolean active;
private final Thread[] threads;
private final int numberOfThreads;
private final Queue<Queue<WeightageTask>> taskQueues;
private final Map<Integer, Queue<WeightageTask>> taskQueueByWeightage;
private final Map<Integer, Integer> threadCountAllocation;
protected WeightageExecutorService(Builder builder) {
taskQueues = new LinkedList<>();
taskQueueByWeightage = new HashMap<>();
threadCountAllocation = builder.threadCountAllocation;
numberOfThreads = threadCountAllocation.values()
.stream()
.reduce(0, Integer::sum);
threads = new Thread[numberOfThreads];
int threadIndex = 0;
for (Integer weightage : threadCountAllocation.keySet()) {
final int threadCount = threadCountAllocation.get(weightage);
for(int i = 0 ; i < threadCount ; ++i) {
threads[threadIndex] = new Thread(() -> {
while (true) {
try {
WeightageTask task = takeTask();
if (task == WeightageTask.POISON) {
break;
}
task.run();
}
catch (Throwable e) {
e.printStackTrace();
}
}
});
threads[threadIndex].setName("weightage-thread-" + weightage + "-" + (i + 1));
++ threadIndex;
}
}
for(Thread thread : threads) {
thread.start();
}
}
public void execute(WeightageTask task) {
final Integer weightage = task.getWeightage();
if(task != WeightageTask.POISON && !threadCountAllocation.containsKey(weightage)) {
throw new IllegalArgumentException("there is no allocated thread for weightage: " + weightage);
}
synchronized (taskQueues) {
final Queue<WeightageTask> taskQueue = taskQueueByWeightage
.computeIfAbsent(weightage, k -> new LinkedList<>());
final boolean addToQueue = taskQueue.isEmpty();
taskQueue.offer(task);
if (addToQueue) {
taskQueues.offer(taskQueue);
}
active = true;
taskQueues.notifyAll();
}
}
#Override
public void shutdown() {
for(int i = 0 ; i < numberOfThreads ; ++i) {
execute(WeightageTask.POISON);
}
shutdown = true;
}
#Override
public List<Runnable> shutdownNow() {
for(Thread thread : threads) {
thread.interrupt();
}
shutdown = true;
return taskQueues.stream()
.flatMap(Queue::stream)
.collect(Collectors.toList());
}
#Override
public boolean isShutdown() {
return shutdown;
}
#Override
public boolean isTerminated() {
return shutdown;
}
private WeightageTask takeTask() throws InterruptedException {
synchronized (taskQueues) {
while (!active) {
taskQueues.wait();
}
final Queue<WeightageTask> taskQueue = taskQueues.poll();
final WeightageTask task = taskQueue.poll();
if (!taskQueue.isEmpty()) {
taskQueues.offer(taskQueue);
}
active = !taskQueues.isEmpty();
return task;
}
}
public static Builder builder() {
return new Builder();
}
#Override
public void execute(Runnable command) {
execute((WeightageTask) command);
}
#Override
public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
return false;
}
#Override
public <T> Future<T> submit(Callable<T> task) {
throw new UnsupportedOperationException("let implement by yourself");
}
#Override
public <T> Future<T> submit(Runnable task, T result) {
throw new UnsupportedOperationException("let implement by yourself");
}
#Override
public Future<?> submit(Runnable task) {
throw new UnsupportedOperationException("let implement by yourself");
}
#Override
public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException {
throw new UnsupportedOperationException("let implement by yourself");
}
#Override
public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
throws InterruptedException {
throw new UnsupportedOperationException("let implement by yourself");
}
#Override
public <T> T invokeAny(Collection<? extends Callable<T>> tasks)
throws InterruptedException, ExecutionException {
throw new UnsupportedOperationException("let implement by yourself");
}
#Override
public <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException {
throw new UnsupportedOperationException("let implement by yourself");
}
public static class Builder {
private final Map<Integer, Integer> threadCountAllocation = new HashMap<>();
public Builder allocateThreads(int weightage, int threadCount) {
this.threadCountAllocation.put(weightage, threadCount);
return this;
}
public WeightageExecutorService build() {
return new WeightageExecutorService(this);
}
}
public static void main(String[] args) throws Exception {
ExecutorService executorService = WeightageExecutorService.builder()
.allocateThreads(2, 4)
.allocateThreads(3, 6)
.build();
executorService.execute(new WeightageTask() {
#Override
public void run() {
System.out.print("Hello ");
}
#Override
public int getWeightage() {
return 2;
}
});
executorService.execute(new WeightageTask() {
#Override
public void run() {
System.out.print("World ");
}
#Override
public int getWeightage() {
return 3;
}
});
Thread.sleep(1000);
executorService.shutdown();
}
}

task on java.util concurrent. Multithreading problems with polling from BlockingQueue

I've received a task about java.util.concurrent package of java. I've made it almost totally, but there is some bug or mistake. When the queue is empty and operator waits for 5 seconds method poll should retrieve null and pass it to the operator and operator goes home. But it doesn't happen. It retrieves null but doesn't pass it to the operator. Sorry for my English.)
public class Client extends Thread {
public CountDownLatch latch=new CountDownLatch(1);
private boolean waiting;
private final Random random=new Random();
public boolean isWaiting() {
return waiting;
}
public void setWaiting(boolean isWaiting) {
this.waiting = isWaiting;
}
private static final Logger LOGGER;
static {
LOGGER = Logger.getLogger(Client.class);
new DOMConfigurator().doConfigure("log4j.xml",
LogManager.getLoggerRepository());
LOGGER.setLevel(Level.INFO);
}
private int limitTime=new Random().nextInt(5000);
public void run(){
ClientQueue.enqueueClient(this);
while(waiting){
if (random.nextBoolean()){
try {
latch.await(5, TimeUnit.SECONDS);
if (!waiting) return;
ClientQueue.removeFromQueue(this);
reportTiredToWait();
sleep(random.nextInt(1000)+500);
ClientQueue.enqueueClient(this);
reportDecidedToCallAgain();
} catch (InterruptedException e) {
LOGGER.info("Exception");
}
}
}
}
public Client(String name) {
super(name);
this.waiting=true;
}
private void reportTiredToWait(){
LOGGER.info("Client "+getName()+" was tired to wait and decided to hang up");
}
private void reportDecidedToCallAgain(){
LOGGER.info("Client "+getName()+" decided to call again");
}
#Override
public String toString() {
return "Client "+getName();
}
}
public class ClientQueue {
private static final Logger LOGGER;
static {
LOGGER = Logger.getLogger(ClientQueue.class);
new DOMConfigurator().doConfigure("log4j.xml",
LogManager.getLoggerRepository());
LOGGER.setLevel(Level.INFO);
}
private static ClientQueue instance;
private BlockingQueue<Client> queue;
public static void printQueue(){
System.out.println("LIST OF CLIENTS:");
for (Client client :ClientQueue.getInstance().queue){
System.out.println("CLIENT "+client.getName());
}
System.out.println("END OF LIST OF CLIENTS:");
}
private static ClientQueue getInstance()
{
if ( instance == null )
{
instance = new ClientQueue();
}
return instance;
}
private ClientQueue()
{
this.queue = new LinkedBlockingQueue<Client>();
}
public static void enqueueClient(Client client){
getInstance().queue.add(client);
reportClientEnqueued(client.getName());
}
public static void removeFromQueue(Client client){
ClientQueue.getInstance().queue.remove(client);
reportClientDeletedFromQueue(client.getName());
}
public static Client pollFirst(long time, TimeUnit timeUnit) throws InterruptedException{
Client client=null;
client = getInstance().queue.poll(time, timeUnit);
if (client!=null){
reportClientRetrievedFromQueue(client.getName());
}
return client;
}
private static void reportClientEnqueued(String name){
LOGGER.info("Client "+name+" was put on the waiting list");
}
private static void reportClientDeletedFromQueue(String name){
LOGGER.info("Client " +name+" was deleted from waiting list");
}
private static void reportClientRetrievedFromQueue(String name){
LOGGER.info("Client " +name+" was retrieved from waiting list");
}
}
public class Operator extends Thread{
private static final Logger LOGGER;
static {
LOGGER = Logger.getLogger(Operator.class);
new DOMConfigurator().doConfigure("log4j.xml",
LogManager.getLoggerRepository());
LOGGER.setLevel(Level.INFO);
}
private boolean running;
public Operator(String name){
super(name);
running= true;
}
#Override
public void run() {
while (running){
Client client=null;
try {
client = ClientQueue.pollFirst(5, TimeUnit.SECONDS);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
if (client!=null){
String clientName=client.getName();
reportOperatorReceivedCall(clientName);
try {
client.setWaiting(false);
client.latch.countDown();
sleep(10000);
reportOperatorFinishedConversation(clientName);
} catch (InterruptedException e) {
LOGGER.error(e);
}
} else{
reportOperatorFinishedHisWork();
running=false;
}
}
}
private void reportOperatorReceivedCall(String name){
LOGGER.info("Operator "+getName()+" received call from Client "+name);
}
private void reportOperatorFinishedConversation(String name){
LOGGER.info("Operator "+getName()+" finished conversation with Client "+name);
}
private void reportOperatorFinishedHisWork(){
LOGGER.info("Operator "+getName()+" finished his work for today, he is too tired and decided to go home.");
}
}
public class Main {
public static void main(String[] args) {
ScheduledExecutorService executor = Executors.newScheduledThreadPool(10);
LinkedList<Client> clientList = new LinkedList<Client>();
clientList.add(new Client("Vasya"));
clientList.add(new Client("Tanya"));
clientList.add(new Client("Petya"));
clientList.add(new Client("Kolya"));
clientList.add(new Client("Elena"));
clientList.add(new Client("Anna"));
for(int i = 0; i < clientList.size(); i++) {
executor.schedule(clientList.get(i), i, TimeUnit.SECONDS);
}
LinkedList<Operator> operatorList = new LinkedList<Operator>();
operatorList.add(new Operator("Bob"));
operatorList.add(new Operator("Sandra"));
operatorList.add(new Operator("John"));
for(int i = 0; i < operatorList.size(); i++) {
executor.schedule(operatorList.get(i), 500, TimeUnit.MILLISECONDS);
}
}
}
You have an extra semicolon in ClientQueue.pollFirst. Here it is corrected:
public static Client pollFirst(long time, TimeUnit timeUnit) throws InterruptedException{
Client client=null;
client = getInstance().queue.poll(time, timeUnit);
if (client!=null) { // removed semicolon from this line
reportClientRetrievedFromQueue(client.getName());
}
return client;
}

Is this code thread safe on BlockingQueue

In the javadoc, I saw the addAll is not thread safe for BlockingQueue, so I suppose the following code is not thread safe, but I run it for a long time, and not exception throw, could anyone explain that ? Thanks
public class Test {
public static void main(String[] args) throws InterruptedException {
LinkedBlockingQueue<String> queue = new LinkedBlockingQueue<String>();
new Producer(queue).start();
new Consumer(queue).start();
}
public static class Producer extends Thread {
private LinkedBlockingQueue<String> queue;
public Producer(LinkedBlockingQueue<String> queue) {
this.queue = queue;
}
#Override
public void run() {
while (true) {
List<String> list = new ArrayList<String>();
for (int i = 0; i < 5; ++i) {
list.add(i + "");
}
this.queue.addAll(list);
}
}
}
public static class Consumer extends Thread {
private LinkedBlockingQueue<String> queue;
public Consumer(LinkedBlockingQueue<String> queue) {
this.queue = queue;
}
#Override
public void run() {
while (true) {
List<String> result = Lists.newArrayList();
queue.drainTo(result);
System.out.println("Take " + result.size());
}
}
}
}

Append stacktrace of caller thread into the new thread being created, for easier debugging

I have frequently faced a problem while debugging.
Sometimes a thread ends throwing an exception.
And the reason of that issue is the caller/starter of the thread.
The caller send incorrect parameter or called the thread without initializing something.
In order to find from where the particular thread was called, it takes a little extra effort,
as the stacktrace is useless.
What if we could append the stacktrace of the caller thread into the called thread.
Consider the following example :
public class ThreadTest {
Thread t = new Thread("executeNonBlocking") {
#Override public void run() {
// What would be a good way to
// append callerStackTrace to the stack
// trace of this thread
System.out.println("inside");
new Throwable().printStackTrace();
}
};
public void executeNonBlocking() {
final StackTraceElement[] callerStackTrace = new Throwable().getStackTrace();
new Throwable().printStackTrace();
t.start();
}
public static void main(String[] args) {
new ThreadTest().executeNonBlocking();
}
}
Output
java.lang.Throwable
at ThreadTest.executeNonBlocking(ThreadTest.java:27)
at ThreadTest.main(ThreadTest.java:41)
inside
java.lang.Throwable
at ThreadTest$1.run(ThreadTest.java:34)
Desired output
java.lang.Throwable
at ThreadTest.executeNonBlocking(ThreadTest.java:27)
at ThreadTest.main(ThreadTest.java:41)
inside
java.lang.Throwable
at ThreadTest.executeNonBlocking(ThreadTest.java:27)
at ThreadTest.main(ThreadTest.java:41)
at ThreadTest$1.run(ThreadTest.java:34)
Edit : Here is the solution obtained after discussions with #peter-lawrey
public class StackTraceInheritingThread {
private final Runnable r;
private volatile Thread th = null;
private String title;
private boolean daemon;
private InheritedStackTrace ist ;
private static final ThreadLocal<InheritedStackTrace> tl = new ThreadLocal<InheritedStackTrace>();
public StackTraceInheritingThread(Runnable r) {
this.r = r;
}
private final class StackTraceInheritingUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {
#Override public void uncaughtException(Thread t, Throwable e) {
if(ist!=null){
e.addSuppressed(ist);
}
e.printStackTrace(System.err);
}
}
public StackTraceInheritingThread setName(String nm){
this.title = nm;
return this;
}
public StackTraceInheritingThread setDaemon(boolean daemon) {
this.daemon = daemon;
return this;
}
public void start(){
if(th!=null){
throw new IllegalStateException("Already started");
}
th = new Thread(new Runnable() {
#Override public void run() {
tl.set(ist);
r.run();
}
},title);
th.setUncaughtExceptionHandler(new StackTraceInheritingUncaughtExceptionHandler());
if(daemon)th.setDaemon(true);
ist = new InheritedStackTrace();
th.start();
}
public static Throwable getInheritedStackTrace(){
return tl.get();
}
public static StackTraceInheritingThread make(Runnable r1){
return new StackTraceInheritingThread(r1);
}
private static final class InheritedStackTrace extends Exception {
}
public static void main(String[] args) {
StackTraceInheritingThread.make(new Runnable() {
#Override
public void run() {
System.out.println("heelo");
throw new RuntimeException();
}
}).setName("ExperimentalThread").start();
}
}
You can save the Throwable used to create a thread in a thread local variable.
public enum Throwables {
;
private static final InheritableThreadLocal<Throwable> STARTING_THREAD = new InheritableThreadLocal<>();
public static void set(Throwable t) {
STARTING_THREAD.set(t);
}
public static Throwable get() {
return STARTING_THREAD.get();
}
public static void printStartingThrowable() {
Throwable throwable = get();
if (throwable == null) return;
throwable.printStackTrace();
}
public static Thread start(Runnable run, String name, boolean daemon) {
Throwable tmp = new Throwable("Started here");
Thread t = new Thread(new Runnable() {
#Override
public void run() {
set(tmp);
run.run();
}
}, name);
t.setDaemon(daemon);
t.start();
return t;
}
public static void main(String... ignored) {
try {
method();
} catch (Throwable t) {
System.err.println("\nThrown in " + Thread.currentThread());
t.printStackTrace();
printStartingThrowable();
}
start(new Runnable() {
#Override
public void run() {
try {
method();
} catch (Throwable t) {
System.err.println("\nThrown in " + Thread.currentThread());
t.printStackTrace();
printStartingThrowable();
}
}
}, "Test thread", false);
}
private static void method() {
throw new UnsupportedOperationException();
}
}
prints
Thrown in Thread[main,5,main]
java.lang.UnsupportedOperationException
at Throwables.method(Throwables.java:59)
at Throwables.main(Throwables.java:36)
Thrown in Thread[Test thread,5,main]
java.lang.UnsupportedOperationException
at Throwables.method(Throwables.java:59)
at Throwables.access$000(Throwables.java:1)
at Throwables$2.run(Throwables.java:47)
at Throwables$1.run(Throwables.java:26)
at java.lang.Thread.run(Thread.java:744)
java.lang.Throwable: Started here
at Throwables.start(Throwables.java:21)
at Throwables.main(Throwables.java:43)

Concurrent tests: test case scenario automatization

Task definition: I need to test custom concurrent collection or some container which manipulates with collections in concurrent environment. More precisely - I've read-API and write-API. I should test if there is any scenarios where I can get inconsistent data.
Problem: All concurrent test frameworks (like MultiThreadedTC, look at MultiThreadedTc section of my question) just provides you an ability to control the asynchronous code execution sequence. I mean you should suppose a critical scenarios by your own.
Broad question: Is there frameworks that can take annotations like #SharedResource, #readAPI, #writeAPI and check if your data will always be consistent? Is that impossible or I just leak a startup idea?
Annotation: If there is no such framework, but you find the idea attractive, you are welcome to contact me or propose your ideas.
Narrow question: I'm new in concurrency. So can you suggest which scenarios should I test in the code below? (look at PeerContainer class)
PeerContainer:
public class PeersContainer {
public class DaemonThreadFactory implements ThreadFactory {
private int counter = 1;
private final String prefix = "Daemon";
#Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r, prefix + "-" + counter);
thread.setDaemon(true);
counter++;
return thread;
}
}
private static class CacheCleaner implements Runnable {
private final Cache<Long, BlockingDeque<Peer>> cache;
public CacheCleaner(Cache<Long, BlockingDeque<Peer>> cache) {
this.cache = cache;
Thread.currentThread().setDaemon(true);
}
#Override
public void run() {
cache.cleanUp();
}
}
private final static int MAX_CACHE_SIZE = 100;
private final static int STRIPES_AMOUNT = 10;
private final static int PEER_ACCESS_TIMEOUT_MIN = 30;
private final static int CACHE_CLEAN_FREQUENCY_MIN = 1;
private final static PeersContainer INSTANCE;
private final Cache<Long, BlockingDeque<Peer>> peers = CacheBuilder.newBuilder()
.maximumSize(MAX_CACHE_SIZE)
.expireAfterWrite(PEER_ACCESS_TIMEOUT_MIN, TimeUnit.MINUTES)
.removalListener(new RemovalListener<Long, BlockingDeque<Peer>>() {
public void onRemoval(RemovalNotification<Long, BlockingDeque<Peer>> removal) {
if (removal.getCause() == RemovalCause.EXPIRED) {
for (Peer peer : removal.getValue()) {
peer.sendLogoutResponse(peer);
}
}
}
})
.build();
private final Striped<Lock> stripes = Striped.lock(STRIPES_AMOUNT);
private final ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1, new DaemonThreadFactory());
private PeersContainer() {
scheduledExecutorService.schedule(new CacheCleaner(peers), CACHE_CLEAN_FREQUENCY_MIN, TimeUnit.MINUTES);
}
static {
INSTANCE = new PeersContainer();
}
public static PeersContainer getInstance() {
return INSTANCE;
}
private final Cache<Long, UserAuthorities> authToRestore = CacheBuilder.newBuilder()
.maximumSize(MAX_CACHE_SIZE)
.expireAfterWrite(PEER_ACCESS_TIMEOUT_MIN, TimeUnit.MINUTES)
.build();
public Collection<Peer> getPeers(long sessionId) {
return Collections.unmodifiableCollection(peers.getIfPresent(sessionId));
}
public Collection<Peer> getAllPeers() {
BlockingDeque<Peer> result = new LinkedBlockingDeque<Peer>();
for (BlockingDeque<Peer> deque : peers.asMap().values()) {
result.addAll(deque);
}
return Collections.unmodifiableCollection(result);
}
public boolean addPeer(Peer peer) {
long key = peer.getSessionId();
Lock lock = stripes.get(key);
lock.lock();
try {
BlockingDeque<Peer> userPeers = peers.getIfPresent(key);
if (userPeers == null) {
userPeers = new LinkedBlockingDeque<Peer>();
peers.put(key, userPeers);
}
UserAuthorities authorities = restoreSession(key);
if (authorities != null) {
peer.setAuthorities(authorities);
}
return userPeers.offer(peer);
} finally {
lock.unlock();
}
}
public void removePeer(Peer peer) {
long sessionId = peer.getSessionId();
Lock lock = stripes.get(sessionId);
lock.lock();
try {
BlockingDeque<Peer> userPeers = peers.getIfPresent(sessionId);
if (userPeers != null && !userPeers.isEmpty()) {
UserAuthorities authorities = userPeers.getFirst().getAuthorities();
authToRestore.put(sessionId, authorities);
userPeers.remove(peer);
}
} finally {
lock.unlock();
}
}
void removePeers(long sessionId) {
Lock lock = stripes.get(sessionId);
lock.lock();
try {
peers.invalidate(sessionId);
authToRestore.invalidate(sessionId);
} finally {
lock.unlock();
}
}
private UserAuthorities restoreSession(long sessionId) {
BlockingDeque<Peer> activePeers = peers.getIfPresent(sessionId);
return (activePeers != null && !activePeers.isEmpty()) ? activePeers.getFirst().getAuthorities() : authToRestore.getIfPresent(sessionId);
}
public void resetAccessedTimeout(long sessionId) {
Lock lock = stripes.get(sessionId);
lock.lock();
try {
BlockingDeque<Peer> deque = peers.getIfPresent(sessionId);
peers.invalidate(sessionId);
peers.put(sessionId, deque);
} finally {
lock.unlock();
}
}
}
MultiThreadedTC test case sample: [optional section of question]
public class ProducerConsumerTest extends MultithreadedTestCase {
private LinkedTransferQueue<String> queue;
#Override
public void initialize() {
super.initialize();
queue = new LinkedTransferQueue<String>();
}
public void thread1() throws InterruptedException {
String ret = queue.take();
}
public void thread2() throws InterruptedException {
waitForTick(1);
String ret = queue.take();
}
public void thread3() {
waitForTick(1);
waitForTick(2);
queue.put("Event 1");
queue.put("Event 2");
}
#Override
public void finish() {
super.finish();
assertEquals(true, queue.size() == 0);
}
}
Sounds like a job for static analysis, not testing, unless you have time to run multiple trillions of test cases. You pretty much can't test multithreaded behaviour - test behaviour in a single thread, then prove the abscence of threading bugs.
Try:
http://www.contemplateltd.com/threadsafe
http://checkthread.org/

Categories

Resources