2 июня 2013 г.

Использование семафоров (java.util.concurrent.Semaphore)

Семафоры используются для того, чтоб перед использованием ресурса проверить его доступность.
Примером из жизни может служить тележка (общий ресурс) и два работника (потоки java). Один работник, к примеру, наполняет тележку песком. В это время второй работник, который перевозит груз и затем разгружает, не может взять тележку и отвезти ее.
В то же время, если второй работник увез тележку, то первый работник не должен ничего наполнять.
Ниже приведен небольшой пример программы из 3 классов.

1. Главнй класс
import java.util.concurrent.Semaphore;

public class SemaphoreExample {
    public static void main(String[] args) {
        Semaphore semaphore = new Semaphore(1);
        new Worker(semaphore, "Adder", true).start();
        new Worker(semaphore, "Reducer", false).start();
    }
} 

2. Класс тележки
public class Cart {
    private static int weight = 0;

    public static void addWeight(){
        weight--;
    }

    public static void reduceWeight(){
        weight++;
    }

    public static int getWaight(){
        return weight;
    }
}

3. Класс работника (boolean переменная определяет это работник по загрузке тележки либо по разгрузке)
import java.util.concurrent.Semaphore;

public class Worker extends Thread {

    private Semaphore semaphore;
    private String workerName;
    private boolean isAdder;
    
    public Worker(Semaphore semaphore, String workerName, boolean isAdder) {
        this.semaphore = semaphore;
        this.workerName = workerName;
        this.isAdder = isAdder;
    }

    @Override
    public void run() {
        System.out.println(workerName + " started working...");
        try {
            System.out.println(workerName + " waiting for cart...");
            semaphore.acquire();
            System.out.println(workerName + " got access to cart...");
            for (int i = 0 ; i < 10 ; i++) {
                if (isAdder)
                    Cart.reduceWeight();
                else
                    Cart.addWeight();
                
                System.out.println(workerName + " changed weight to: " + Cart.getWaight());
                Thread.sleep(10L);
            }
            System.out.println(workerName + " finished working with cart...");
        } catch (Exception e) {
            e.printStackTrace(System.err);
        } finally {
            semaphore.release(); 
        }
    }
}

Данный пример не гарантирует, что наполнение тележки произойдет раньше, чем ее разгрузка. Если есть такая необходимость, то одни из способов - это поставить задержку после создания первого потока Worker.

Результат работы программы:
Adder started working...
Adder waiting for cart...
Adder got access to cart...
Adder changed weight to: 1
Reducer started working...
Reducer waiting for cart...
Adder changed weight to: 2
Adder changed weight to: 3
Adder changed weight to: 4
Adder changed weight to: 5
Adder changed weight to: 6
Adder changed weight to: 7
Adder changed weight to: 8
Adder changed weight to: 9
Adder changed weight to: 10
Adder finished working with cart...
Reducer got access to cart...
Reducer changed weight to: 9
Reducer changed weight to: 8
Reducer changed weight to: 7
Reducer changed weight to: 6
Reducer changed weight to: 5
Reducer changed weight to: 4
Reducer changed weight to: 3
Reducer changed weight to: 2
Reducer changed weight to: 1
Reducer changed weight to: 0
Reducer finished working with cart...

4 комментария:

  1. Спасибо за интересную статью, коротко и ясно.
    Я собираюсь разработать проект на подобие онлайн видео компрессинг. Как мы знаем оперция компрессинга занимает не малых ресурсов процессора. И я решил разбить запросы на компрессинг по 10 потоков, как вы думаете парвильно ли будет в данном случае использовать шаблон семафоры?

    ОтветитьУдалить
    Ответы
    1. Здравствуйте, спасибо за комментарий.
      Я не знаком с задачей компрессинга, но опишу несколько проблем многопоточности:
      1. Выполнение многошаговой большой задачи, путем разбиения на мелкие. Но при условии, что мы не можем начать новый шаг, если предыдущий шаг не выполнен полностью. В данном случае подойдут "Защелки" http://movejava.blogspot.com/2013/06/javautilconcurrentcountdownlatch.html
      2. Если у нас в многопоточной среде идет работа с ресурсом, который может работать в однопоточном режиме (как пример), то тогда нужно использовать Семафоры.

      Удалить
  2. Не будет ли лучше перенести semaphore.release() в блок finally? Если Вы учли возможность появления Exception, то ресурс лучше освободить в любом случае.

    ОтветитьУдалить
  3. Все верно, спасибо за замечание.

    ОтветитьУдалить