how to use a synchronized linked hash map correctly - java

trying to make an lru map by subclassing linked hash map.
the map is run through collections.synchronized.
all usages of the map are surrounded by a synchronized block. the unit test also fails if they are all removed. one would think they are not necessary since the map was run through collections.synchronized.
one thread puts sequential numbers (0,1,2,3 ...) into the map. removals are handled by removed eldest entry. no one else removes entries from the map.
the other thread gets the data from the map.
the following unit test fails usually at "oops". this is when a non zero number shows up in the first position (it should be zero until the map gets full). other strange things can happen like null values in the entry set.
any pointers will be appreciated.
thanks
import static org.junit.Assert.*;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
class LruMap<K,V> extends LinkedHashMap<K,V> {
public LruMap() {
super(defaultMaxSize+1,.75f,true);
maxSize=defaultMaxSize;
}
public LruMap(int arg0) {
super(arg0+1,.75f,true);
maxSize=arg0;
}
public LruMap(int arg0,float arg1) {
super(arg0+1,arg1,true);
maxSize=arg0;
}
public LruMap(int arg0,float arg1,boolean arg2) {
super(arg0+1,arg1,arg2);
if(!arg2)
throw new RuntimeException("you did not construct an lru map!");
maxSize=arg0;
}
public LruMap(Map<K,V> arg0) {
super(arg0);
throw new RuntimeException("you did not construct an lru map!");
}
public boolean removeEldestEntry(Map.Entry<K,V> eldest) {
return size()>maxSize;
}
public final int maxSize;
public static final int defaultMaxSize=2048;
static final long serialVersionUID=0;
}
class Server implements Runnable {
public Server(final int pieces,final int period) {
this.pieces=pieces;
this.period=period;
lruMap=Collections.synchronizedMap(new LruMap<Long,Long>(3*pieces/2));
}
#Override public void run() {
t0=System.currentTimeMillis();
while(piece<stopAtPiece) {
final long dt=System.currentTimeMillis()-t0;
final long target=piece(dt);
System.out.println("adding "+(target-piece+1)+" items");
for(;piece<=target;piece++) {
synchronized(lruMap) {
lruMap.put(piece,piece);
}
}
checkMap(piece,true);
try {
Thread.sleep(100);
} catch(InterruptedException e) {
e.printStackTrace();
break;
}
}
}
Map.Entry<Long,Long>[] checkMap(final long n,boolean print) {
synchronized(lruMap) {
Map.Entry<Long,Long>[] entries=null;
if(lruMap.size()>0) {
final Set<Map.Entry<Long,Long>> entrySet=lruMap.entrySet();
entries=new Map.Entry[entrySet.size()];
entrySet.toArray(entries);
long first=entries[0].getKey();
long last=entries[entries.length-1].getKey();
if(print)
for(Map.Entry<Long,Long> entry:entries)
System.out.print(entry.getKey()+" ");
System.out.println();
if(n<pieces&&first!=0) {
System.out.println("lru: first!=0! "+first);
if(throwWhenfirstIsNotZero) { throw new RuntimeException("oops"); }
}
for(int i=0;i<entries.length-1;i++) {
long p0=entries[i].getKey();
long p1=entries[i+1].getKey();
if(p0>p1)
System.out.println("out of order! "+p0+" "+p1);
else if(p0==p1)
System.out.println("dupicate "+p0+" "+p1);
else if(p0+1==p1)
; // ok
else if(p0+1<p1)
System.out.println("skipped "+p0+" "+p1);
else System.out.println("some case i mssed!");
}
}
return entries;
}
}
public long piece(final long dt) {
return dt/period*pieces+dt%period*pieces/period;
}
public boolean throwWhenfirstIsNotZero=true;
protected long piece;
public long t0;
protected long stopAtPiece=Long.MAX_VALUE;
public final int period;
public final int pieces;
public final Map<Long,Long> lruMap;
}
public class ServerTestCase {
#Before public void setUp() throws Exception {}
#After public void tearDown() throws Exception {}
#Test public void testRun() {
server.stopAtPiece=server.pieces;
server.throwWhenfirstIsNotZero=true;
Thread thread=new Thread(server);
thread.setName("server");
thread.start();
while(thread.isAlive()) {
for(long i=0;i<server.piece;i++)
synchronized(server.lruMap) {
server.lruMap.get(i);
}
}
}
final int period=2*1000;
final int serverPieces=100;
Server server=new Server(serverPieces,period);
}

If you are accessing the collection inside a synchronized(lruMap) block, then you probably don't want to wrap it in Collections.synchronizedMap() - use one or the other. This is because they will probably be using different locks - in fact it's almost certain, because it's extremely unlikely that synchronizedMap() is using synchronized(this) internally.
Also I recommend enter link description here

Related

ConcurrentSkipListMap firstKey() throws NoSuchElementException even though it contains data

I wrote a small application that receives data from a web socket, which I store in static ConcurrentSkipListMap.
The application initially creates a new thread where it runs infinitely while loop calling ConcurrentSkipListMap.firstKey(). After a while, this call throws a NoSuchElementException, even though the ConcurrentSkipListMap contains data.
break point in catch block
Example of my application:
I have cacher class that contains websocket implementation and NavigableMap init:
package solvethat.net.triobot.Example;
import com.binance.api.client.BinanceApiCallback;
import com.binance.api.client.BinanceApiClientFactory;
import com.binance.api.client.domain.event.DepthEvent;
import com.binance.api.client.domain.market.OrderBook;
import com.binance.api.client.domain.market.OrderBookEntry;
import java.math.BigDecimal;
import java.util.Comparator;
import java.util.List;
import java.util.NavigableMap;
import java.util.concurrent.ConcurrentSkipListMap;
public class AskCacher {
private long updateId;
private final BinanceApiClientFactory factory;
public AskCacher() {
factory = BinanceApiClientFactory.newInstance();
initAsks();
runWebsocket();
}
/**
* Init data getting order book snapshot
*/
private void initAsks() {
try {
OrderBook orderBook = factory.newRestClient().getOrderBook("btcusdt".toUpperCase(), 10);
updateId = orderBook.getLastUpdateId();
NavigableMap<Double, Double> asks = new ConcurrentSkipListMap<>(Comparator.naturalOrder());
for (OrderBookEntry ask : orderBook.getAsks()) {
asks.put(Double.parseDouble(ask.getPrice()), Double.parseDouble(ask.getQty()));
}
StaticData.ask = asks;
} catch (Exception e) {
System.err.println(e.getMessage());
}
}
private void runWebsocket() {
factory.newWebSocketClient().onDepthEvent("btcusdt", new BinanceApiCallback<>() {
/**
* Set ask price and call analysis method
*/
#Override
public void onResponse(DepthEvent depthEvent) {
if (depthEvent.getFinalUpdateId() > updateId) {
updateId = depthEvent.getFinalUpdateId();
updateOrderBook(depthEvent.getAsks());
}
}
/**
* Just print err message
*/
#Override
public void onFailure(final Throwable cause) {
System.err.println(cause.getMessage());
}
});
}
/**
* Updates an order book (asks) with a delta received from the server.
* Whenever the qty specified is ZERO, it means the price should was removed from the order book.
*/
private void updateOrderBook(List<OrderBookEntry> orderBookDeltas) {
for (OrderBookEntry orderBookDelta : orderBookDeltas) {
Double price = Double.parseDouble(orderBookDelta.getPrice());
BigDecimal qty = new BigDecimal(orderBookDelta.getQty());
if (qty.compareTo(BigDecimal.ZERO) == 0) {
// qty=0 means remove this level
StaticData.ask.remove(price);
} else {
StaticData.ask.put(price, Double.parseDouble(orderBookDelta.getQty()));
}
}
// Print best ask to see if cacher is alive
System.out.println("btc-usdt best ask: " + StaticData.ask.firstKey());
// Edit map length
if (StaticData.ask.size() > 10) {
StaticData.ask.tailMap((Double) StaticData.ask.keySet().toArray()[10], true).clear();
}
}}
Then infinite loop:
package solvethat.net.triobot.Example;
public class InfiniteLoop {
public void loopProcess() {
Analyzer analyzer = new Analyzer();
while (true) {
analyzer.analyze(StaticData.ask.firstEntry());
}
}}
And analyzer class:
package solvethat.net.triobot.Example;
import java.util.Map;
public class Analyzer {
public void analyze(Map.Entry<Double, Double> entry) {
StaticData.AnalyzeObject analyzeObject = new StaticData.AnalyzeObject();
analyzeObject.setBestAsk(entry.getKey());
if (analyzeObject.getBestAsk() > 50000) {
System.out.println("It is a good price!!");
}
}
}
Static data model:
package solvethat.net.triobot.Example;
import java.util.NavigableMap;
public class StaticData {
public static NavigableMap<Double, Double> ask;
public static class AnalyzeObject {
double bestAsk;
public double getBestAsk() {
return bestAsk;
}
public void setBestAsk(double bestAsk) {
this.bestAsk = bestAsk;
}
}
}
Main class for example run:
package solvethat.net.triobot.Example;
public class Main {
public static void main(String[] arguments) {
new AskCacher();
new Thread(new InfiniteLoop()::loopProcess).start();
}
}
The example only shows how the application is composed, but I was not able to use it to raise an error but I opened my repo as public:
https://github.com/Sick-E/TrioBot
Can anyone please help me?
Thank you.
Tomas
You can replace your code with something like that (no exception handling is required)
Optional.ofNullable(trio.getThirdPair().getBids().firstEntry())
.map(Map.Entry::getKey)
.ifPresent(trio.getTrioAnalysis()::setBidThird);

Sending data within three threads in java, one after another

I wanted to make an elevator system where you send information from the following:
elevator (data) -> scheduler (buffer) -> floor (receive)
The Floor subsystem and the Elevators are the clients in the system; the Scheduler is the server.
when I pressed run some of the issues were:
class elevator is shown below:
package elevator;
import java.util.HashSet;
import java.util.Set;
public class elevator {
public enum State {
MOVING_UP, MOVING_DOWN, STOPPED
}
private int floor;
private State state;
#SuppressWarnings({ })
private Set<Integer> pressedButtons = (Set<Integer>) new HashSet<Integer>();
public elevator() {
state = State.STOPPED;
}
public int getFloor() {
return floor;
}
public void setFloor(int floor) {
this.floor = floor;
pressedButtons.remove(floor);
}
public State getState() {
return state;
}
public void setState(State s) {
state = s;
}
public boolean isMoving() {
return state == State.MOVING_UP || state == State.MOVING_DOWN;
}
public void buttonPressed(int i) {
pressedButtons.add(i);
}
public Set<Integer> getButtons() {
return pressedButtons;
}
public String toString() {
return "Floor: " + floor + "\n" + "\t State: " + state + "\n";
}
}
Exception in thread "Thread-0" java.lang.ClassCastException: class elevator.HashSet cannot be cast to class java.util.Set (elevator.HashSet is in unnamed module of loader 'app'; java.util.Set is in module java.base of loader 'bootstrap')
at elevator.elevator.(elevator.java:21)
at elevator.elevatorExchange.retrieveData(elevatorExchange.java:30)
at elevator.elevatorExchange.run(elevatorExchange.java:19) at java.base/java.lang.Thread.run(Thread.java:835)
Thread 1: elevator
package elevator;
import java.util.concurrent.BlockingQueue;
public class elevatorExchange implements Runnable{
private BlockingQueue<elevator> messages;
public elevatorExchange(BlockingQueue<elevator> messages) {
this.messages = messages;
}
#Override
public void run() {
try {
elevator elevatorData = retrieveData();
messages.put(elevatorData);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private elevator retrieveData() throws InterruptedException {
Thread.sleep(5000);
elevator elevatorData = new elevator();
return elevatorData;
}
}
Thread 2: Scheduler. Scheduler is only being used as a communication channel from the Floor thread to the Elevator thread
package scheduler;
import java.util.concurrent.BlockingQueue;
import elevator.elevator;
public class Scheduler implements Runnable {
private BlockingQueue<elevator> messages;
public Scheduler(BlockingQueue<elevator> messages) {
this.messages = messages;
}
#Override
public void run() {
elevator elevatorData = messages.take();
}
Thread 3: The floor (this will receive it from the scheduler). This is the part I'm struggling with the most, I am trying to make sure the data passed down to the floor is from the scheduler and not the elevator, but my IDE keeps making changes to the data type that's running a lot of exceptions.
package floor;
import java.util.concurrent.BlockingQueue;
import elevator.elevator;
public class FloorReceiver implements Runnable{
private BlockingQueue<elevator> messages;
public FloorReceiver(BlockingQueue<elevator> messages) {
this.messages = messages;
}
#Override
public void run() {
try {
System.out.println("waiting for data from elevator");
elevator elevatorData = messages.take();
System.out.println("data from elevator" + elevatorData);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
Testing:
package floor;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import elevator.elevator;
import elevator.elevatorExchange;
public class elevatorToFloorTest {
public static void main(String[] args) {
BlockingQueue<elevator> messages = new ArrayBlockingQueue<elevator>(1);
elevatorExchange retriever = new elevatorExchange(messages);
FloorReceiver receiver = new FloorReceiver(messages);
new Thread(retriever).start();
new Thread(receiver).start();
}
}
This line is the problem:
private Set<Integer> pressedButtons = (Set<Integer>) new HashSet<Integer>();
The exception message tells us that the HashSet is not a java.util.HashSet but is something you wrote:
class elevator.HashSet cannot be cast to class java.util.Set
i.e., the HashSet is defined in the elevator package.
Your HashSet is not implementing the Set interface. If it were, then you would not need the cast. Since it isn't, the cast cannot magically make it work.
It seems the compiler would have told you, except you used #SuppressWarnings. (I'm not sure what the empty list does).
On the other hand, the code for the elevator class that you have now posted shows an import for java.util.HashSet, not a homebrew implementation. This cannot produce the error shown. Have you failed to rebuild everything?

mutual exclusion over variables does not work

i am trying to simulate visitors going into a theater and taking an aailable sit(the number of available sits is shared) and once all the sits are taken the rest go to sleep.
i am having trouble over the availablesits variable in the getsit method inside the visitor class.
please help me
i tried synchronizing the threads as well as making the variable volatile. but for some reasons all threads arrive to that getsit part at the same time? i dont understand why!
//main
import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
int numVisitors = 23;
int theaterCapacity =5;
int party_ticket=3;
Clock clock = new Clock();
Theater theater=new Theater(theaterCapacity,clock);
ArrayList<Visitor> visitorlist = new ArrayList<Visitor>();
for(int i=1;i<=numVisitors;i++) {
visitorlist.add(new Visitor(i,clock,theater));
}
clock.start();
for(Visitor visitor:visitorlist)
visitor.start();
}
}
//visitor class
public class Visitor extends Thread{
private static Clock clock;
private static Theater theater;
public int id;
public boolean sawPresentation=false;
public volatile int priority= 10;
public static long time = System.currentTimeMillis();
public void msg(String m) {
System.out.println("["+(System.currentTimeMillis()-time)+"] "+getName()+": "+m);
}
Visitor(int id, Clock clock, Theater theater){
this.id=id;
this.clock=clock;
this.theater=theater;
}
public void run(){
heArrives();
while(!sawPresentation) {
while(!clock.presentationIsOpen()) {
//busy wait
}
getASit();
}
}
public void heArrives() {
msg("the visitor arrived");
}
public synchronized void getASit(){
if(theater.availableSits>0){
msg("the visitor got a sit");
theater.availableSits--;
watchPresentation();
}
}
public void watchPresentation(){
msg("the visitor is watching the presentation");
}
}
//clock class
import java.util.Timer;
import java.util.TimerTask;
public class Clock extends Thread {
public static long time = System.currentTimeMillis();
public static int secondsPassed=6;
Timer timer= new Timer();
TimerTask task = new TimerTask() {
#Override
public void run() {
secondsPassed++;
//System.out.println("seconds passed: "+secondsPassed);
}
};
public void run(){
timer.scheduleAtFixedRate(task,0,1000);
}
public boolean presentationIsOpen(){
if(secondsPassed%6==0) return true;
return false;
}
}
//theater class
class Theater extends Thread{
public static Clock clock;
public int capacity;
public volatile int availableSits=5;
Theater(int capacity,Clock clock){
this.capacity=capacity;
this.clock=clock;
}
}
Your main problem is the synchronization of the getASit method.You are synchronizing against Visitor object instance so every thread synchronized against diferent object. Instead you have to synchronized against the object that is shared. In your case against the theater. Change your method to something like this:
public void getASit(){
synchronized(theater){
if(theater.availableSits>0){
msg("the visitor got a sit");
theater.availableSits--;
watchPresentation();
}
}
}
Its better not to use busy wait, its burn cpu too much, better is to use sleep for example:
while(!clock.presentationIsOpen()) {
try {
Thread.sleep(1_000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}

How do i set up a DelayQueue's Delay

I'm just starting out coding in java i'm in struggling with setting up a DelayQueue,
I wanted to have it so,
DelayQueue queue = new DelayQueue();
If (counter > 0){
queue.offer(Integer, *A custom delay*)
} Else {
queue.offer(Integer, *A different custom delay*)
}
I'm just trying to learn all the basics and ive read over the API and cant seem to grasp it.
Thanks in advance
this implementation of Delayed is good because:
implementation of compareTo() does not do any class casting, eliminatig the possibility of throwing a ClassCastException
implementation of compareTo() uses Math.min and Math.max functions before casting to int in order to properly prevent overflow errors
implementation of getDelay() properly converts the units and actually returns the time remaining
TestDelay class implements Delayed:
import org.jetbrains.annotations.NotNull;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;
public class TestDelay implements Delayed
{
public final Long delayMillis;
public final Long expireTimeMillis;
public TestDelay(Long delayMillis)
{
this.delayMillis = delayMillis;
this.expireTimeMillis = System.currentTimeMillis()+delayMillis;
}
#Override
public final int compareTo(#NotNull Delayed o)
{
long diffMillis = getDelay(TimeUnit.MILLISECONDS)-o.getDelay(TimeUnit.MILLISECONDS);
diffMillis = Math.min(diffMillis,1);
diffMillis = Math.max(diffMillis,-1);
return (int) diffMillis;
}
#Override
public final long getDelay(#NotNull TimeUnit unit)
{
long delayMillis = expireTimeMillis-System.currentTimeMillis();
return unit.convert(delayMillis,TimeUnit.MILLISECONDS);
}
}
JUnit unit test showing an example of using the TestDelay class:
import org.junit.Test;
import java.util.concurrent.DelayQueue;
public class DelayQueueTest
{
#Test
public final void generalTest() throws InterruptedException
{
DelayQueue<TestDelay> q = new DelayQueue<>();
q.put(new TestDelay(500L));
q.put(new TestDelay(2000L));
q.put(new TestDelay(1000L));
q.put(new TestDelay(10L));
q.put(new TestDelay(3000L));
while (!q.isEmpty())
{
System.out.println(q.take().delayMillis);
}
}
}
output of DelayQueueTest:
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;
public class DelayQueueExample {
public static void main(String[] args) {
BlockingQueue<DelayedElement> blockingQueue = new DelayQueue<DelayedElement>();
try {
blockingQueue
.put(new DelayedElement(4000, "Message with delay 4s"));
blockingQueue
.put(new DelayedElement(2000, "Message with delay 2s"));
blockingQueue
.put(new DelayedElement(9000, "Message with delay 9s"));
} catch (InterruptedException ie) {
}
while (!blockingQueue.isEmpty()) {
try {
System.out.println(">>" + blockingQueue.take());
} catch (InterruptedException ie) {
}
}
}
}
class DelayedElement implements Delayed {
private long duration = 0;
private String message;
public DelayedElement(long duration, String name) {
this.duration = System.currentTimeMillis() + duration;
this.message = name;
}
#Override
public int compareTo(Delayed o) {
return (int) (this.duration - ((DelayedElement) o).getDuration());
}
#Override
/*
* Expiration occurs when an element's getDelay(TimeUnit unit) method
* returns a value less than or equal to zero.
*/
public long getDelay(TimeUnit unit) {
long diff = duration - System.currentTimeMillis();
return unit.convert(diff, TimeUnit.MILLISECONDS);
}
public long getDuration() {
return duration;
}
public void setDuration(long duration) {
this.duration = duration;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
#Override
public String toString() {
return "DelayedElement [duration=" + duration + ", message=" + message
+ "]";
}
}
Your "custom delay" classes must return the delay from the getDelay(TimeUnit timeUnit) method specified in the Delayed interface.
E.g.
public class MyClass implements Delayed {
public long getDelay(TimeUnit timeUnit) {
long delay = calculateDelaySomehow();
return delay;
}
}
Note that you also need to provide an implementation for compareTo().
The DelayQueue keeps the elements internally until a certain delay has expired. The elements must implement the interface java.util.concurrent.Delayed.
For example I have created a class DelayedTest extending Delayed interface. This will implement compareTo and getDelay() method
public class A{
public static void main(String... args){
DelayQueue dq=new DelayQueue();
DeleyedTest ob1=new DeleyedTest(10);
DeleyedTest ob2=new DeleyedTest(5);
DeleyedTest ob3=new DeleyedTest(15);
dq.offer(ob1);
dq.offer(ob2);
dq.offer(ob3);
Iterator itr=dq.iterator();
while(itr.hasNext()){
DeleyedTest dt=(DeleyedTest)itr.next();
System.out.println(dt.deleyTime);
}
}
}
class DeleyedTest implements Delayed{
public long deleyTime=0;
DeleyedTest(long deleyTime){
this.deleyTime=deleyTime;
}
#Override
public int compareTo(Delayed ob) {
if(this.deleyTime<((DeleyedTest)ob).deleyTime){
return -1;
}else if(this.deleyTime>((DeleyedTest)ob).deleyTime){
return 1;
}
return 0;
}
#Override
public long getDelay(TimeUnit unit) {
return unit.convert(deleyTime-System.currentTimeMillis(),TimeUnit.NANOSECONDS);
}
}
Result:
5
10
15

Producer-Consumer with Predicate

I'm looking for a java collection that supports blocking read()s on a predicate. I wrote a simple version but it seems like this must have been invented already?
For example:
interface PredicateConsumerCollection<T> {
public void put(T t);
#Nullable
public T get(Predicate<T> p, long millis) throws InterruptedException;
}
put() delivers its argument to a waiting consumer with a matching predicate, or stashes it in a store. A get() returns immediately if a suitable T is already in the store, or blocks till a suitable value is put(), or times out. Consumers compete but fairness isn't critical in my case.
Anyone aware of a such a collection?
There is no immediate class that can solve your problem, but a combination of a ConcurrentHashMap and a BlockingQueue could be a solution.
The hash map is defined as:
final ConcurrentHashMap<Predicate, LinkedBlockingQueue<Result>> lookup;
The put needs to ensure, that for each Predicate a queue is added to the map, this can be done thread-safe using putIfAbsent.
If you have a fixed set of Predicates, you can simply pre-fill the list, then a Consumer can simply call lookup.get(Predicate).take()
If the amount of Predicates is unknown/too many, you need to write a wait/notify implementation for Consumers in case a Predicate is not yet in the list on your own.
I also need something very similar for testing that a certain JMS asynchronous message has been received within a certain timeout. It turns out that your question is relatively easy to implement by using basic wait/notify as explained in the Oracle tutorials. The idea is to make the put and query methods synchronized and let the query method do a wait. The put method calls notifyAll to wake up any waiting threads in the query method. The query method must then check if the predicate is matched. The most tricky thing is getting the timeout right due to waking up when the predicate does not match and due to possible " spurious wakeups". I found this stackoverflow post that provides the answer.
Here is the implementation I came up with:
import java.util.ArrayList;
import java.util.List;
// import net.jcip.annotations.GuardedBy;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
public class PredicateConsumerCollectionImpl<T> implements
PredicateConsumerCollection<T> {
// #GuardedBy("this")
private List<T> elements = new ArrayList<>();
#Override
public synchronized void put(T t) {
elements.add(t);
notifyAll();
}
#Override
public synchronized T query(Predicate<T> p, long millis)
throws InterruptedException {
T match = null;
long nanosOfOneMilli = 1000000L;
long endTime = System.nanoTime() + millis * nanosOfOneMilli;
while ((match = Iterables.find(elements, p, null)) == null) {
long sleepTime = endTime - System.nanoTime();
if (sleepTime <= 0) {
return null;
}
wait(sleepTime / nanosOfOneMilli,
(int) (sleepTime % nanosOfOneMilli));
}
return match;
}
synchronized boolean contains(T t) {
return elements.contains(t);
}
}
And here is a JUnit test that proves that the code works as intended:
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import org.junit.Before;
import org.junit.Test;
import com.google.common.base.Predicate;
/**
* Unit test for the {#link PredicateConsumerCollection} implementation.
*
* <p>
* The tests act as consumers waiting for the test Producer to put a certain
* String.
*/
public class PredicateConsumerCollectionTest {
private static class Producer implements Runnable {
private PredicateConsumerCollection<String> collection;
public Producer(PredicateConsumerCollection<String> collection) {
this.collection = collection;
collection.put("Initial");
}
#Override
public void run() {
try {
int millis = 50;
collection.put("Hello");
Thread.sleep(millis);
collection.put("I");
Thread.sleep(millis);
collection.put("am");
Thread.sleep(millis);
collection.put("done");
Thread.sleep(millis);
collection.put("so");
Thread.sleep(millis);
collection.put("goodbye!");
} catch (InterruptedException e) {
e.printStackTrace();
fail("Unexpected InterruptedException");
}
}
}
private PredicateConsumerCollectionImpl<String> collection;
private Producer producer;
#Before
public void setup() {
collection = new PredicateConsumerCollectionImpl<>();
producer = new Producer(collection);
}
#Test(timeout = 2000)
public void wait_for_done() throws InterruptedException {
assertTrue(collection.contains("Initial"));
assertFalse(collection.contains("Hello"));
Thread producerThread = new Thread(producer);
producerThread.start();
String result = collection.query(new Predicate<String>() {
#Override
public boolean apply(String s) {
return "done".equals(s);
}
}, 1000);
assertEquals("done", result);
assertTrue(collection.contains("Hello"));
assertTrue(collection.contains("done"));
assertTrue(producerThread.isAlive());
assertFalse(collection.contains("goodbye!"));
producerThread.join();
assertTrue(collection.contains("goodbye!"));
}
#Test(timeout = 2000)
public void wait_for_done_immediately_happens() throws InterruptedException {
Thread producerThread = new Thread(producer);
producerThread.start();
String result = collection.query(new Predicate<String>() {
#Override
public boolean apply(String s) {
return "Initial".equals(s);
}
}, 1000);
assertEquals("Initial", result);
assertFalse(collection.contains("I"));
producerThread.join();
assertTrue(collection.contains("goodbye!"));
}
#Test(timeout = 2000)
public void wait_for_done_never_happens() throws InterruptedException {
Thread producerThread = new Thread(producer);
producerThread.start();
assertTrue(producerThread.isAlive());
String result = collection.query(new Predicate<String>() {
#Override
public boolean apply(String s) {
return "DONE".equals(s);
}
}, 1000);
assertEquals(null, result);
assertFalse(producerThread.isAlive());
assertTrue(collection.contains("goodbye!"));
}
}

Categories

Resources