Условие. Необходимо написать программу на Java, которая напечатает последовательно числа от 0 до N — 1 (или от 1 до N) при помощи нескольких потоков.
Решение.
Для этого объявим переменную i, доступ к которой будет только после лока на одном и том же объекте для всех потоков: lock.
В synchronized блоке будем печатать и увеличивать i. Также перед печатью нужно проверить, что она еще не превысила заданное значение. Если превысила, то нужно просто выйти из функции run потока при помощи метода return.
Реализация:
class PrintSequenceThread extends Thread {
private static Object lock = new Object();
private static int i = 0;
private final int max;
public PrintSequenceThread(int max) {
this.max = max;
}
public void run() {
while (true) {
synchronized (lock) {
if (i >= max) {
return;
}
System.out.println(i + " " + Thread.currentThread().getName());
i++;
}
}
}
}
public class Main {
public static void main(String[] args) {
int numberOfThreads = 4;
int maxValueToPrint = 10;
Thread[] threads = new Thread[numberOfThreads];
for (int i = 0; i < numberOfThreads; i++) {
threads[i] = new PrintSequenceThread(maxValueToPrint);
}
for (Thread t : threads) {
t.start();
}
}
}
0 Thread-0
1 Thread-0
2 Thread-0
3 Thread-0
4 Thread-0
5 Thread-0
6 Thread-0
7 Thread-0
8 Thread-0
9 Thread-0
Несмотря на то, что такая реализация работает и она Thread Safe, у нас с большой вероятностью все числа напечатает один и тот же поток. Он получит лок, напечатает, увеличит, отпустит лок. Потом, с большой вероятность, он снова получит лок и сделает тоже самое.
Что если мы хотим, чтобы 0 напечатал первый поток, 1 второй поток и т.д. ?
В таком случае задача очень похожа на другую задачу про Ping Pong.
Будем использовать wait-notify механизм. Я подробно разбирал его тут: Методы Object и Ping Pong.
Для этого модифицируем предыдущую версию. Добавим wait на объекте lock, пока не настанет время текущим потоком печатать нужное число. Это можно проверить так:
while (i % numberOfThreads != threadIndex) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Пока остаток от деления индекса на число потоков не равно номеру потока, мы ждем, пока работают и печатают другие потоки.
Как только наступит наша очередь, то мы напечатаем число (предварительно проверив, что оно еще не превысило максимальное), сделаем инкремент и разбудим другие потоки, чтобы они продолжили печать:
class PrintSequenceThread extends Thread {
private static Object lock = new Object();
private static int i = 0;
private final int max;
private final int numberOfThreads;
private final int threadIndex;
public PrintSequenceThread(int max, int numberOfThreads, int threadIndex) {
this.max = max;
this.numberOfThreads = numberOfThreads;
this.threadIndex = threadIndex;
}
public void run() {
while (i < max) {
synchronized (lock) {
while (i % numberOfThreads != threadIndex) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (i < max) {
System.out.println(i + " " + Thread.currentThread().getName());
}
i++;
lock.notifyAll();
}
}
}
}
public class Main {
public static void main(String[] args) {
int numberOfThreads = 4;
int maxValueToPrint = 10;
Thread[] threads = new Thread[numberOfThreads];
for (int i = 0; i < numberOfThreads; i++) {
threads[i] = new PrintSequenceThread(maxValueToPrint, numberOfThreads, i);
}
for (Thread t : threads) {
t.start();
}
}
}
0 Thread-0
1 Thread-1
2 Thread-2
3 Thread-3
4 Thread-0
5 Thread-1
6 Thread-2
7 Thread-3
8 Thread-0
9 Thread-1
Top comments (0)