Javenue logo

Javenue

Программирование на Java

Информационные технологии

Одновременный запуск параллельных потоков - Java Threads

[Disclamer: Данная статья была написана в 2006 году и уже давно устарела. Статья вскоре будет исправлена с учетом пакета java.util.concurrent.]

Потоки в Java (Threads) - не самая легкая для понимания вещь. Но знать, как они работают и взаимодействуют, должен каждый Java-программист.

В этой статье я хочу на примере показать, как можно реализовать практически одновременный запуск нескольких потоков. Надеюсь, данная статья прольет свет на некоторые ваши вопросы по поводу многопоточности.

Для начала приведу исходный код программы:

import java.util.Random;

public class Simultaneous implements Runnable {
  protected static final int THREADS = 10;

  private static class Semaphore {
    public boolean set = false;
  }

  protected static final Semaphore[] semaphores = 
        new Semaphore[THREADS]; 
  protected static final Thread[] threads = new Thread[THREADS];
	
  protected static Random rnd = new Random();
	
  protected final int threadNum;

  protected Simultaneous(int num) {
    this.threadNum = num;
  }
	
  protected static int getRndNumber() {
    return rnd.nextInt(1000)*10;
  }
	
  public void run() {
    synchronized (semaphores) {
      semaphores[this.threadNum].set = true;
      semaphores.notify();
    }

    final long startExec = System.currentTimeMillis();
    executeTask();
    final long finishExec = System.currentTimeMillis();
    System.out.println(“Thread N“ + this.threadNum + “. Start: “ +
        startExec + “. End: “ + finishExec +
        “. Total: “ + (finishExec - startExec) + “.“);
  }
	
  public void executeTask() {
    int temp = 1000; 
    for (int i=0; i<getRndNumber(); i++) 
      for (int j=0; j<getRndNumber(); j++) 
        for (int k=0; k<getRndNumber(); k++) 
          temp = temp / temp * temp;
  }
	
  public static void main(String[] args) {
    for (int i=0; i<THREADS; i++) {
      semaphores[i] = new Semaphore();
    }
		
    System.out.println(“Starting ” + THREADS + “ threads...”);
		
    synchronized (Simultaneous.class) {
      synchronized (semaphores) {
        for (int i=0; i<THREADS; i++) {
          final Simultaneous mainThread = new Simultaneous(i);
          threads[i] = new Thread(mainThread, “Thread N” + i);
          threads[i].setDaemon(true);
          threads[i].start();
        }

        System.out.println(“Waiting for simultaneous start...”);

        boolean isAllStarted = false;
        while (!isAllStarted) {
          try {
            semaphores.wait();
          }
          catch (InterruptedException e) {
            e.printStackTrace();
          }
					
          isAllStarted = true;
          for (int i=0; i<THREADS; i++) {
            if (!semaphores[i].set) {
              isAllStarted = false;
              break;
            }
          }
        }
      }
    }
		
    System.out.println(“Waiting for threads to finish execution...”);

    for (int i=0; i<THREADS; i++) {
      try {
        threads[i].join();
      }
      catch (InterruptedException e) {
        e.printStackTrace();
        return;
      }
    }
	
    System.out.println(“Program terminated successfully.”);
  }
}

Функция executeTask - это задача, выполняемая каждым из потоков. На нее можете не обращать внимание, равно как и на getRndNumber. Класс Simultaneous реализует интерфейс Runnable. Можно также наследоваться от класса Thread. Если вы не уверены, что вам больше подходит, используйте первый способ. По крайней мере, это избавит вас от проблем при наследовании от другого класса.

Для того, чтобы синхроизировать начало выполнения потоков, я использовал внутренний класс Semaphore, а точнее массив экземпляров этого класса. Массив потоков threads будет содержать подопытные потоки. Массивы semaphores и threads относятся к классу, поэтому объявлены как static.

Ну а теперь начинается самое интересное. После инициализации semaphores в методе main идут 2 synchronized блока. Первый синхронизирует по классу Simultaneous, а второй захватывает монитор переменной semaphores.

После создания каждого потока-демона и его запуска посредством метода start(), выполнение потоков приостанавливается при входе в synchronized блок метода run(). Это происходит потому, что переменная semaphores "удерживается" в главном потоке (метод main).

Как только все 10 потоков будут созданы, выполнение программы перейдет к циклу while. Задача этого цикла - отследить тот момент времени, когда все потоки будут запущены. Посредством метода wait текущий поток "отпускает" переменную semaphores (монитор объекта). В этот момент один из подопытных потоков заходит в synchronized блок метода run(). После инициализации потоком семафора, он отдает монитор semaphores главному потоку с помощью метода notify(). Так повторяется до тех пор, пока все семафоры не будут установлены в true, что и проверяется в последнем цикле synchronized блока.

Наконец, программа ожидает окончания работы всех потоков (метод join) и успешно завершает работу.

А вот и результат выполнения программы:

Starting 10 threads...
Waiting for simultaneous start...
Waiting for threads to finish execution...
Thread N0. Start: 1141394153812. End: 1141394154500. Total: 688.
Thread N3. Start: 1141394153812. End: 1141394155781. Total: 1969.
Thread N8. Start: 1141394153828. End: 1141394155828. Total: 2000.
Thread N4. Start: 1141394153812. End: 1141394155843. Total: 2031.
Thread N6. Start: 1141394153812. End: 1141394155921. Total: 2109.
Thread N9. Start: 1141394153812. End: 1141394156140. Total: 2328.
Thread N5. Start: 1141394153812. End: 1141394156250. Total: 2438.
Thread N2. Start: 1141394153812. End: 1141394156421. Total: 2609.
Thread N7. Start: 1141394153812. End: 1141394156500. Total: 2688.
Thread N1. Start: 1141394153812. End: 1141394157046. Total: 3234.
Program terminated successfully.

Описанный выше пример очень полезен при стресс-тестировании (Stress Test). Просто воспользуйтесь своей реализацией метода executeTask.

Комментарии по поводу статьи приветствуются.



Комментариев: 0

  Выйти

  * для публикации комментариев нужно