Search code examples
javamultithreadingsemaphorejava-threadsblockingqueue

how to give priority to the threads waiting in a semaphore?


I have used a semaphore to restrict the number of threads accessing a function. I want the thread to be awakened next should be chosen by some priority which i will be giving,not by default way that semaphore awaken's them ? How can we achieve this ?

Here is the implementation :

class MyMathUtil2 implements Runnable {
    double a;
    double b;
    String name = "demo";
    Thread t;
    //static int currentCount = 0;
    static int MAX_COUNT = 2;

    private final Semaphore available = new Semaphore(MAX_COUNT, true);

    MyMathUtil2(double v1, double v2) {
        a = v1;
        b = v2;
        t = new Thread(this, name);
        System.out.println("New thread: " + t);
        t.start();
    }

    public void InternalPow(double a, double b) throws InterruptedException {
        available.acquire();
        try {
            System.out.println("Power of " + a + " and " + b + " : " + Math.pow(a, b));
        } finally {
            available.release();
        }

    }

    public void run() {
        try {
            InternalPow(a, b);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

class TestMyMathUtil2 {
    public static void main(String args[]) {
        new MyMathUtil2(10.2, 8);
        new MyMathUtil2(11, 56);
        new MyMathUtil2(10.2, 9);
        new MyMathUtil2(2, 3);
        new MyMathUtil2(4, 5);
    }
}

Solution

  • Well, a Semaphore does not support priority.

    I suggest to use a ThreadPoolExecutor with 2 fixed threads and a PriorityBlockingQueue to solve this problem.

    A ThreadPoolExecutor with 2 fixed threads can make sure that at any moment, there is at most 2 task running. The other tasks will be put in this PriorityBlockingQueue, and the thread pool will retrieve tasks from the queue based on a custom Comparator.

    Here is an example. Every Runnable in this example is supposed to print a number. It submits the Runnables in reverse order: 1000, 999, ..., 1. But the Runnable will be executed in nature order: 1, 2, ...., 1000 using a priority queue.

    import java.util.Comparator;
    import java.util.concurrent.PriorityBlockingQueue;
    import java.util.concurrent.ThreadPoolExecutor;
    import java.util.concurrent.TimeUnit;
    
    class ComparableRunnable implements Runnable {
    
        public int index;
    
        ComparableRunnable(int index) {
            this.index = index;
        }
    
        public void run() {
            System.out.println(Thread.currentThread().getName() + "-index : " + index);
            try {
                // sleep current thread, so the other thread can print
                // this is not mandatory, without this, the result might not follow strict natural order
                // for example, thread1 print 1, 
                // thread2 take 2 but did not print it immediatly, 
                // thread1 print 3, 
                // thread2 print 2
                // the result will be 1, 3, 2,
                Thread.sleep(10);
            } catch (Exception e) {
    
            }
        }
    
        public static void main(String[] args) {
            int corePoolSize = 2; // fixed thread number
            long ignore = 0L;
    
            // comparator
            Comparator<Runnable> comparator = new Comparator<Runnable>() {
                @Override
                public int compare(Runnable o1, Runnable o2) {
                    int index1 = ((ComparableRunnable)o1).index;
                    int index2 = ((ComparableRunnable)o2).index;
                    // you should implement this method based on your own order
                    return Integer.compare(index1, index2);
                }
            };
    
            // use the comparator create a priority queue
            PriorityBlockingQueue<Runnable> queue = new PriorityBlockingQueue<>(10, comparator);
    
            ThreadPoolExecutor executor =
                    new ThreadPoolExecutor(corePoolSize, corePoolSize, ignore, TimeUnit.SECONDS, queue);
    
            // Warm the thread pool up
            // this is not mandatory, without this, it will print 1000, 999, 1, 2, ...., 998
            // because the first two Runnbale will be executed once they are submitted
            for (int i = 0; i < corePoolSize; i++) {
                executor.execute(() -> {
                    try {
                        Thread.sleep(1000);
                    } catch (Exception e) {
    
                    }
                });
            }
    
            // submit in 1000, 999, ..., 1 order
            // print in 1, 2, 3, ..., 1000 order
            for (int i = 1000; i > 0; i--) {
                executor.execute(new ComparableRunnable(i));
            }
        }
    }
    

    Result:

    pool-1-thread-1-index : 1
    pool-1-thread-2-index : 2
    pool-1-thread-1-index : 3
    pool-1-thread-2-index : 4
    pool-1-thread-2-index : 5
    ...
    pool-1-thread-2-index : 996
    pool-1-thread-2-index : 997
    pool-1-thread-1-index : 998
    pool-1-thread-2-index : 999
    pool-1-thread-1-index : 1000