JavaRush /Java блог /Random UA /Кава-брейк #112. Як оголосити, ініціалізувати та перебрат...

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

Стаття з групи Random UA

Як оголосити, ініціалізувати та перебрати масив у 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 , а інший скидає його після.

  • Універсального способу обробки сигналів переривання немає — це залежить від обставин.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ