JavaRush /Blog Java /Random-PL /Przerwa kawowa #112. Jak deklarować, inicjować i iterować...

Przerwa kawowa #112. Jak deklarować, inicjować i iterować po tablicy w Javie. Jak zatrzymać wątek Java bez użycia Thread.stop()?

Opublikowano w grupie Random-PL

Jak deklarować, inicjować i iterować po tablicy w Javie

Źródło: FreeCodeCamp W tym artykule porozmawiamy o tablicach w Javie. Przyjrzymy się kilku przykładom, które pomogą Ci zrozumieć, czym jest tablica, jak ją zadeklarować i jak używać jej w kodzie Java. Przerwa kawowa #112.  Jak deklarować, inicjować i iterować po tablicy w Javie.  Jak zatrzymać wątek Java bez użycia Thread.stop()?  - 1

Co to jest tablica?

W Javie używamy tablicy do przechowywania wielu wartości tego samego typu danych w jednej zmiennej. Można go również traktować jako zbiór wartości tego samego typu danych. Oznacza to, że jeśli na przykład zamierzasz przechowywać w swojej tablicy ciągi znaków, to wszystkie wartości w Twojej tablicy muszą być ciągami znaków.

Jak zadeklarować tablicę w Javie

Aby zadeklarować tablicę, używamy nawiasów kwadratowych [ ] . Tak:
String[] names;
Tutaj zadeklarowaliśmy zmienną zwaną nazwami , która będzie przechowywać tablicę ciągów znaków. Jeśli będziemy musieli zadeklarować zmienną dla liczb całkowitych (całkowitych), zrobimy to w następujący sposób:
int[] myIntegers;
Tak więc, aby utworzyć tablicę, określamy typ danych, które będą przechowywane w tablicy, po czym podajemy nawiasy kwadratowe, a następnie nazwę tablicy.

Jak zainicjować tablicę w Javie

Zainicjowanie tablicy oznacza przypisanie wartości do tablicy. Zainicjujmy tablice, które zadeklarowaliśmy w poprzedniej sekcji:
String[] names = {"John", "Jade", "Love", "Allen"};
int[] myIntegers = {10, 11, 12};
Inicjowaliśmy nasze tablice przekazując wartości tego samego typu danych, oddzielając każdą wartość przecinkiem. Gdybyśmy chcieli uzyskać dostęp do elementów/wartości w naszej tablicy, uzyskalibyśmy dostęp do ich numeru indeksu w tablicy. Indeks pierwszego elementu wynosi 0. Oto przykład:
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
Teraz, gdy wiemy, jak uzyskać dostęp do każdego elementu, zmieńmy wartość trzeciego elementu.
String[] names = {"John", "Jade", "Love", "Allen"};
names[2] = "Victor";

System.out.println(names[2]);
// Victor
Długość tablicy możemy także sprawdzić za pomocą właściwości długości . Oto przykład:
String[] names = {"John", "Jade", "Love", "Allen"};
System.out.println(names.length);
// 4

Jak iterować po tablicy w Javie

Możemy użyć pętli for do iteracji po elementach tablicy .
String[] names = {"John", "Jade", "Love", "Allen"};
for (int i = 0; i < names.length; i++) {
  System.out.println(names[i]);
}

// John
// Jade
// Love
// Allen
Pętla powyżej wydrukuje elementy naszej tablicy. Użyliśmy właściwości długości , aby określić, ile razy pętla powinna zostać wykonana.

Wniosek

W tym artykule dowiedzieliśmy się, jak deklarować i inicjować tablice w kodzie Java. Widzieliśmy także, jak uzyskać dostęp do każdego elementu tablicy i jak przeglądać te elementy w pętli. Miłego kodowania!

Jak zatrzymać wątek Java bez użycia Thread.stop()?

Źródło: 4comprehension Jeśli to czytasz, prawdopodobnie zastanawiasz się, dlaczego i co najważniejsze, jak zatrzymać wątek, jeśli nie możesz po prostu polegać na metodzie Thread#stop() , która jest przestarzała od wersji Java 1.2 ? Przerwa kawowa #112.  Jak deklarować, inicjować i iterować po tablicy w Javie.  Jak zatrzymać wątek Java bez użycia Thread.stop()?  - 2Krótka odpowiedź jest taka, że ​​powinieneś używać przerwań, ponieważ Thread#stop jest niebezpieczny. Ale jeśli chcesz zrozumieć, dlaczego, jak i do czego służy ten nieznośny InterruptedException , czytaj dalej.

Problem z wątkiem#stop()

Niezależnie od tego, jak intuicyjnie może to zabrzmieć, przerwanie już działającego wątku, rozpoczęcie i zatrzymanie to dwie zupełnie różne sprawy. Dlaczego? Rozpoczynając nowy wątek, zaczynamy od zera, co jest stanem ściśle określonym, ale gdy próbujemy zatrzymać istniejący wątek, może się to zdarzyć w trakcie operacji, której nie można natychmiast przerwać . Wyobraź sobie, że wątek wprowadza zmiany w strukturze danych bezpiecznej dla wątków. Póki jest w środku sekcji krytycznej... i wtedy wątek w magiczny sposób znika - blokady zostają zwolnione i już inny wątek może obserwować pozostawioną w niespójnym stanie strukturę danych. Rozważmy następujący przykład licznika bezpiecznego dla wątków z pewnym stanem wewnętrznym:
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;
    }
}
Jak widać, metoda getAndInkrement() zwiększa licznik i zmienia flagę brudnej . Blokady zapewniają, że za każdym razem, gdy wątek wchodzi do funkcji getAndInkrement(), flaga brudnej wartości jest ustawiona na wartość false . Stwórzmy teraz wątek i szybko go zakończmy:
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
Teraz instancja threadSafeCounter , mimo że została poprawnie zaimplementowana, jest trwale uszkodzona z powodu nagłego zatrzymania wątku. Jak to naprawić?

Przerwanie wątku kooperacyjnego

Zamiast zatrzymywać wątek powinniśmy polegać na kooperacyjnym przerywaniu wątku. Mówiąc najprościej, musimy poprosić wątek, aby zatrzymał się w odpowiednim momencie, używając Thread#interrupt . Jednak wywołanie Thread#interrupt zamiast Thread#stop nie wystarczy. Bezpieczne zakończenie wątku można osiągnąć jedynie poprzez zastosowanie przerwania współdzielonego wątku, co oznacza, że ​​w wątku musimy obsługiwać sygnały przerwań, które występują w dwóch typach:
  • Flaga przerwania logicznego

  • Przerwany wyjątek

Przerywalna obsługa flag

Będąc wewnątrz wątku możemy sprawdzić czy został on przerwany wywołując jedną z metod:
Thread.currentThread().isInterrupted(); // reads the interrupted flag
Thread.interrupted(); // reads and resets the interrupted flag
Ponieważ nie ma uniwersalnego sposobu obsługi przerwania, musimy zastosować akcję odpowiednią do naszego kontekstu. W przykładzie z poprzedniej sekcji wątek zwiększa nasz licznik w nieskończonej pętli. Zamiast natychmiastowo się zatrzymywać, moglibyśmy po prostu poczekać, aż wątek zakończy swoją pracę rosnącą, a następnie wyjść z pętli:
var threadSafeCounter = new ThreadSafeCounter();

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

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

threadSafeCounter.incrementAndGet();
Teraz wątek kończy się bezpiecznie we właściwym czasie, nie powodując chaosu.

Obsługa przerwanego wyjątku

Co się stanie, jeśli spróbujemy przerwać oczekujący wątek? Oczywiście nie możemy brać pod uwagę flagi przerwania , ponieważ wątek jest zajęty operacją blokującą. Dlatego istnieje InterruptedException . Nie jest to standardowy wyjątek, ale inna forma sygnału przerwania, która istnieje wyłącznie w celu obsługi przerwań blokujących! Podobnie jak Thread#interrupted , resetuje flagę przerwania . Zmieńmy powyższy przykład jeszcze raz i wprowadźmy pauzy przed każdą inkrementacją. Teraz musimy obsłużyć wyjątek InterruptedException zgłoszony przez 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);
W naszym przypadku wystarczyło po prostu wyjść z pętli. Ale co, jeśli nie uważasz, że za obsługę przerwań powinna odpowiadać konkretna metoda? co jeśli nie można zmienić podpisu metody? W takim przypadku musimy przywrócić flagę przerwania i/lub przepakować wyjątek, na przykład:
try {
    Thread.sleep(1000);
} catch (InterruptedException e) {
    Thread.currentThread().interrupt();
}
try {
    Thread.sleep(1000);
} catch (InterruptedException e) {
    Thread.currentThread().interrupt();
    throw new RuntimeException(e);
}

Wniosek:

  • Thread#stop nie jest przestarzały bez powodu — w rzeczywistości jest bardzo niepewny.

  • Wątki są zatrzymywane poprzez przerwania - sygnały interpretowane przez sam wątek.

  • InterruptedException nie jest zwykłym wyjątkiem — jest to wbudowany w Javę sposób sygnalizowania przerwania wątku.

  • Thread.currentThread().isInterrupted() i Thread.interrupted() to nie to samo. Pierwszy po prostu odczytuje flagę przerwania , a drugi później ją resetuje.

  • Nie ma uniwersalnego sposobu obsługi sygnałów przerwań – „to zależy od okoliczności”.

Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION