Reputation: 1778
Consider a Swing application with a JList or JTable, when the selection changes a SwingWorker is started and loads related data from database and updates UI. This works fine and the UI is responsive.
But if the user is quickly changing the selected row (holding key-up/down) I want to be sure that the last selected row is the one that is loaded last, and also I don't wanna query the DB in vain. So what I want is an single threaded Executor with a LIFO queue of size=1. So submitting a task to it removes any previous submitted tasks and making it execute at most 1 task at a time and having at most 1 task waiting for execution.
I couldn't find anything like this in java.util.concurrent so I wrote my own Executor. Was I right in doing that or am I missing something from the concurrent package? Is the solution acceptable or is there better ways of achieving what I want?
public class SingleLIFOExecutor implements Executor
{
private final ThreadPoolExecutor executor;
private Runnable lastCommand;
public SingleLIFOExecutor()
{
executor = new ThreadPoolExecutor(0, 1, 0, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(1));
}
@Override
public void execute(Runnable command)
{
executor.remove(lastCommand);
lastCommand = command;
executor.execute(command);
}
}
And here's an example showing how it could be used:
final Executor executor = new SingleLIFOExecutor();
JList jList = createMyList();
jList.addListSelectionListener(new ListSelectionListener()
{
@Override
public void valueChanged(ListSelectionEvent e)
{
if (!e.getValueIsAdjusting())
{
executor.execute(new MyWorker());
}
}
});
Upvotes: 3
Views: 1337
Reputation: 1778
This was the solution I implemented, works great for the problem I tried to solve :)
/**
* A "Single Last-In-First-Out Executor".
* <p>
* It maintains a queue of <b>one</b> task and only one task may execute simultaneously,
* submitting a new task to {@link #execute(Runnable)} will discard any previous submitted not yet started tasks.
*/
public class SingleLIFOExecutor implements Executor
{
private final ThreadPoolExecutor executor;
private Runnable lastCommand;
public SingleLIFOExecutor()
{
executor = new ThreadPoolExecutor(0, 1, 0, MILLISECONDS, new ArrayBlockingQueue<Runnable>(1));
}
/**
* @see java.util.concurrent.Executor#execute(java.lang.Runnable)
*/
@Override
public void execute(Runnable command)
{
executor.remove(lastCommand);
lastCommand = command;
executor.execute(command);
}
}
Upvotes: 0
Reputation: 7100
LinkedBlockingDeque seems to still use Queues with ThreadPoolExecutor.
So instead I used a wrapper and used it with the ThreadPoolExecutor:
package util;
import java.util.Collection;
import java.util.Iterator;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.TimeUnit;
/**
* LIFO BlockingQueue to be used with the ExecutorService.
* @author Daniel
* @param <T>
*/
public class LinkedBlockingStack<T> implements BlockingQueue<T>{
private final LinkedBlockingDeque<T> stack = new LinkedBlockingDeque<T>();
@Override
public T remove() {
return stack.remove();
}
@Override
public T poll() {
return stack.poll();
}
@Override
public T element() {
return stack.element();
}
@Override
public T peek() {
return stack.peek();
}
@Override
public int size() {
return stack.size();
}
@Override
public boolean isEmpty() {
return stack.isEmpty();
}
@Override
public Iterator<T> iterator() {
return stack.iterator();
}
@Override
public Object[] toArray() {
return stack.toArray();
}
@Override
public <S> S[] toArray(final S[] a) {
return stack.toArray(a);
}
@Override
public boolean containsAll(final Collection<?> c) {
return stack.containsAll(c);
}
@Override
public boolean addAll(final Collection<? extends T> c) {
return stack.addAll(c);
}
@Override
public boolean removeAll(final Collection<?> c) {
return stack.removeAll(c);
}
@Override
public boolean retainAll(final Collection<?> c) {
return stack.removeAll(c);
}
@Override
public void clear() {
stack.clear();
}
@Override
public boolean add(final T e) {
return stack.offerFirst(e); //Used offerFirst instead of add.
}
@Override
public boolean offer(final T e) {
return stack.offerFirst(e); //Used offerFirst instead of offer.
}
@Override
public void put(final T e) throws InterruptedException {
stack.put(e);
}
@Override
public boolean offer(final T e, final long timeout, final TimeUnit unit)
throws InterruptedException {
return stack.offerLast(e, timeout, unit);
}
@Override
public T take() throws InterruptedException {
return stack.take();
}
@Override
public T poll(final long timeout, final TimeUnit unit)
throws InterruptedException {
return stack.poll();
}
@Override
public int remainingCapacity() {
return stack.remainingCapacity();
}
@Override
public boolean remove(final Object o) {
return stack.remove(o);
}
@Override
public boolean contains(final Object o) {
return stack.contains(o);
}
@Override
public int drainTo(final Collection<? super T> c) {
return stack.drainTo(c);
}
@Override
public int drainTo(final Collection<? super T> c, final int maxElements) {
return stack.drainTo(c, maxElements);
}
}
Upvotes: 1
Reputation: 7100
BlockingDeque I believe is what you want. It supports stacks.
What I have in my code:
private transient final ExecutorService threadPool=
new ThreadPoolExecutor(3, 10,10,
TimeUnit.MILLISECONDS,
new LinkedBlockingDeque<Runnable>());
Upvotes: 0