JavaRush /Java блог /Random /Кофе-брейк #112. Как объявить, инициализировать и перебра...

Кофе-брейк #112. Как объявить, инициализировать и перебрать массив в Java. Как остановить поток Java без использования Thread.stop()?

Статья из группы Random

Как объявить, инициализировать и перебрать массив в Java

Источник: FreeCodeCamp В этой статье мы поговорим о массивах в Java. Мы рассмотрим несколько примеров, которые помогут понять, что такое массив, как его объявить и как использовать в коде Java. Кофе-брейк #112. Как объявить, инициализировать и перебрать массив в Java. Как остановить поток Java без использования Thread.stop()? - 1

Что такое массив?

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

Как объявить массив в Java

Для объявления массива мы используем квадратные скобки [ ]. Примерно так:

String[] names;
Здесь мы объявили переменную с именем names, которая будет содержать массив строк. Если же нам нужно объявить переменную для integers (целых чисел), то мы сделаем это так:

int[] myIntegers;
Таким образом, чтобы создать массив, мы указываем тип данных, которые будут храниться в массиве, за которыми следуют квадратные скобки, а затем имя массива.

Как инициализировать массив в Java

Инициализировать массив означает присвоить значения массиву. Давайте инициализируем массивы, которые мы объявили в предыдущем разделе:

String[] names = {"John", "Jade", "Love", "Allen"};

int[] myIntegers = {10, 11, 12};
Мы инициализировали наши массивы, передав значения с одним и тем же типом данных, разделив каждое значение запятой. Если бы мы хотели получить доступ к элементам/значениям в нашем массиве, мы бы обратились к их порядковому номеру в массиве. Индекс первого элемента равен 0. Вот пример:

String[] names = {"John", "Jade", "Love", "Allen"};

System.out.println(names[0]);
// John

System.out.println(names[1]);
// Jade

System.out.println(names[2]);
// Love

System.out.println(names[3]);
// Allen
Теперь, когда мы знаем, как получить доступ к каждому элементу, давайте изменим значение третьего элемента.

String[] names = {"John", "Jade", "Love", "Allen"};
names[2] = "Victor";

System.out.println(names[2]);
// Victor
Мы также можем проверить длину массива, используя свойство length. Вот пример:

String[] names = {"John", "Jade", "Love", "Allen"};
System.out.println(names.length);
// 4

Как перебрать массив в Java

Для перебора элементов массива мы можем использовать цикл for.

String[] names = {"John", "Jade", "Love", "Allen"};
for (int i = 0; i < names.length; i++) {
  System.out.println(names[i]);
}

// John
// Jade
// Love
// Allen
Цикл выше будет печатать элементы нашего массива. Мы использовали свойство length, чтобы указать, сколько раз цикл должен запускаться.

Заключение

В этой статье мы узнали, как объявлять и инициализировать массивы в Java-коде. Мы также увидели, как получить доступ к каждому элементу массива и как перебрать эти элементы в цикле. Удачного кодирования!

Как остановить поток Java без использования Thread.stop()?

Источник: 4comprehension Если вы это читаете, то вы, вероятно, задаетесь вопросом, почему, и, самое главное, как остановить поток, если нельзя просто полагаться на метод Thread#stop(), который устарел, начиная с Java 1.2? Кофе-брейк #112. Как объявить, инициализировать и перебрать массив в Java. Как остановить поток Java без использования Thread.stop()? - 2Краткий ответ заключается в том, что следует использовать прерывания (interruptions), потому что Thread#stop небезопасен. Но если вы хотите понять, почему, как и для чего нужно это надоедливое InterruptedException, продолжайте читать.

Проблема с Thread#stop()

Как бы интуитивно это не звучало, но прерывание уже запущенного потока, запуск и остановка — это два совершенно разных случая. Почему? Когда мы запускаем новый поток, мы начинаем с нуля, что является четко определенным состоянием, но когда мы пытаемся остановить существующий поток, это может произойти в середине операции, которую нельзя прерывать немедленно. Представьте поток, вносящий изменения в потокобезопасную структуру данных. Пока он находится в середине критической секции… и вот поток волшебным образом исчезает — блокировки снимаются, и теперь другой поток может наблюдать структуру данных, оставшуюся в несогласованном состоянии. Рассмотрим следующий пример потокобезопасного счетчика (thread-safe counter) с некоторым внутренним состоянием:

class ThreadSafeCounter {

    private volatile int counter = 0;
    private volatile boolean dirty = false;

    public synchronized int incrementAndGet() {
        if (dirty) {
            throw new IllegalStateException("this should never happen");
        }
        dirty = true;
        // ...
        Thread.sleep(5000); // boilerplate not included
        // ...
        counter = counter + 1;
        dirty = false;
        return counter;
    }
}
Как видите, метод getAndIncrement() увеличивает счетчик и изменяет флаг dirty. Блокировки гарантируют, что каждый раз, когда поток входит в getAndIncrement(), флаг dirty устанавливается в false. Теперь давайте создадим поток и резко остановим его:

var threadSafeCounter = new ThreadSafeCounter();

var t1 = new Thread(() -> {
    while (true) {
        threadSafeCounter.incrementAndGet();
    }
});

t1.start();
Thread.sleep(500);
t1.stop();

threadSafeCounter.incrementAndGet(); 

// Exception in thread "main" java.lang.IllegalStateException: this should never happen
Теперь экземпляр threadSafeCounter, несмотря на правильную реализацию, постоянно поврежден из-за резкой остановки потока. Как это исправить?

Совместное прерывание потока (Cooperative Thread Interruption)

Вместо того, чтобы останавливать поток, мы должны полагаться на совместное прерывание потока. Проще говоря, мы должны попросить поток остановить себя в нужный момент, используя Thread#interrupt. Однако вызова Thread#interrupt вместо Thread#stop недостаточно. Безопасное завершение потока может быть достигнуто только с использованием совместного прерывания потока, а это означает, что внутри потока нам необходимо обрабатывать сигналы прерывания, которые бывают двух видов:
  • Логический прерванный флаг (boolean interrupted flag)

  • InterruptedException

Обработка прерываемого флага

Находясь внутри потока, мы можем проверить, был ли он прерван, вызвав один из методов:

Thread.currentThread().isInterrupted(); // reads the interrupted flag
Thread.interrupted(); // reads and resets the interrupted flag
Поскольку не существует универсального способа обработки прерывания, нам нужно применить действие, которое соответствует нашему контексту. В примере из предыдущего раздела поток увеличивает наш счетчик в бесконечном цикле. Вместо немедленной остановки мы могли бы просто подождать, пока поток завершит свою работу по возрастанию, а затем выйти из цикла:

var threadSafeCounter = new ThreadSafeCounter();

var t1 = new Thread(() -> {
    while (!Thread.interrupted()) {
        threadSafeCounter.incrementAndGet();
    }
});

t1.start();
Thread.sleep(500);
t1.interrupt();

threadSafeCounter.incrementAndGet();
Теперь поток благополучно завершается в нужный момент, не вызывая хаоса.

Обработка InterruptedException

Что будет, если мы попытаемся прервать ожидающий поток? Естественно, мы не можем учесть флаг interruption, потому что поток занят блокирующей операцией. Вот почему существует InterruptedException. Это не стандартное исключение, а другая форма сигнала прерывания, которая существует исключительно для обработки прерываний блокирующих операций! Подобно Thread#interrupted, он сбрасывает флаг interruption. Давайте снова изменим приведенный выше пример и введем паузы перед каждым приращением. Теперь нам нужно обработать InterruptedException, созданное Thread#sleep:

var threadSafeCounter = new ThreadSafeCounter();

var t1 = new Thread(() -> {
    while (!Thread.interrupted()) {
        threadSafeCounter.incrementAndGet();
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            break;
        }
    }
});

t1.start();
Thread.sleep(500);
t1.interrupt();

assertThat(threadSafeCounter.incrementAndGet()).isGreaterThan(0);
В нашем случае достаточно было просто выйти из цикла. Но как быть, если вы не считаете, что конкретный метод должен отвечать за обработку прерываний? что делать, если сигнатура метода не может быть изменена? В таком случае нам нужно восстановить флаг interruption и/или переупаковать исключение, например:

try {
    Thread.sleep(1000);
} catch (InterruptedException e) {
    Thread.currentThread().interrupt();
}

try {
    Thread.sleep(1000);
} catch (InterruptedException e) {
    Thread.currentThread().interrupt();
    throw new RuntimeException(e);
}

Заключение:

  • Thread#stop не устарел без причины — он действительно очень небезопасен.

  • Потоки останавливаются через прерывания (interruptions) — сигналы, интерпретируемые самим потоком.

  • InterruptedException — это не обычное исключение — это встроенный в Java способ сигнализации о прерывании потока.

  • Thread.currentThread().isInterrupted() и Thread.interrupted() — не одно и то же. Первый просто считывает флаг interruption, а другой сбрасывает его после.

  • Универсального способа обработки сигналов прерывания не существует — “это зависит от обстоятельств”.

Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ