111. Wie tausche ich Daten zwischen Threads aus?
Um Daten zwischen Threads auszutauschen, können Sie viele verschiedene Ansätze und Mittel verwenden: Verwenden Sie beispielsweise atomare Variablen, synchronisierte Sammlungen und ein Semaphor. Aber um dieses Problem zu lösen, werde ich ein Beispiel mit Exchanger geben . Exchanger ist eine Synchronisationsklasse aus dem Concurrent- Paket, die den Austausch von Elementen zwischen einem Thread-Paar durch die Erstellung eines gemeinsamen Synchronisationspunkts erleichtert. Seine Verwendung vereinfacht den Datenaustausch zwischen zwei Threads. Die Funktionsweise ist recht einfach: Es wartet darauf, dass zwei separate Threads seine Methode „exchange()“ aufrufen . Zwischen ihnen entsteht so etwas wie ein Austauschpunkt: Der erste Thread legt sein Objekt ab und erhält im Gegenzug das Objekt des anderen, und dieser wiederum empfängt das Objekt des ersten und stellt sein eigenes ab. Das heißt, der erste Thread verwendet die Methode „exchange()“ und ist inaktiv, bis ein anderer Thread die Methode „exchange()“ für dasselbe Objekt aufruft und Daten zwischen ihnen ausgetauscht werden. Betrachten Sie als Beispiel die folgende Implementierung der Thread- Klasse :public class CustomThread extends Thread {
private String threadName;
private String message;
private Exchanger<String> exchanger;
public CustomThread(String threadName, Exchanger<String> exchanger) {
this.threadName = threadName;
this.exchanger = exchanger;
}
public void setMessage(final String message) {
this.message = message;
}
@Override
public void run() {
while (true) {
try {
message = exchanger.exchange(message);
System.out.println(threadName + " поток получил сообщение: " + message);
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
Im Thread-Konstruktor definieren wir ein Exchanger- Objekt , das Objekte vom Typ String akzeptiert , und beim Start (in der Ausführungsmethode ) verwenden wir dessen Exchange() , um eine Nachricht mit einem anderen Thread auszutauschen, der diese Methode im selben Exchanger verwendet . Lassen Sie es uns in main ausführen :
Exchanger<String> exchanger = new Exchanger<>();
CustomThread first = new CustomThread("Первый ", exchanger);
first.setMessage("Сообщение первого потока");
CustomThread second = new CustomThread("Второй", exchanger);
second.setMessage("Сообщение второго потока");
first.start();
second.start();
Die Konsole zeigt Folgendes an:
112. Was ist der Unterschied zwischen der Thread-Klasse und der Runnable-Schnittstelle?
Das erste, was mir auffällt, ist , dass Thread eine Klasse und Runnable eine Schnittstelle ist, was ein sehr offensichtlicher Unterschied ist =D Ich sage auch, dass Thread Runnable (Komposition) verwendet . Das heißt, wir haben zwei Möglichkeiten:-
Von Thread erben , die run-Methode überschreiben, dann dieses Objekt erstellen und den Thread über die start()- Methode starten .
-
Implementieren Sie Runnable in einer bestimmten Klasse, implementieren Sie deren run()- Methode und erstellen Sie dann ein Thread- Objekt , indem Sie diese Objektimplementierung der Runnable- Schnittstelle ihrem Konstruktor zuweisen . Nun, am Ende starten Sie das Thread- Objekt mit der start()- Methode .
-
Wenn Sie die Runnable- Schnittstelle implementieren , ändern Sie das Verhalten des Threads nicht. Im Wesentlichen geben Sie dem Thread nur etwas zum Laufen. Und das ist unsere Komposition, die wiederum als guter Ansatz gilt.
-
Durch die Implementierung von Runnable erhält Ihre Klasse mehr Flexibilität. Wenn Sie von Thread erben , erfolgt die Aktion, die Sie ausführen, immer im Thread. Aber wenn Sie Runnable implementieren , muss es nicht nur ein Thread sein. Schließlich können Sie es entweder in einem Thread ausführen oder an einen Executor-Dienst übergeben. Nun, oder übergeben Sie es einfach irgendwo als Aufgabe in einer Single-Thread-Anwendung.
-
Mit Runnable können Sie die Aufgabenausführung logisch von der Thread-Steuerungslogik trennen.
-
In Java ist nur eine einzelne Vererbung möglich, sodass nur eine Klasse erweitert werden kann. Gleichzeitig ist die Anzahl der erweiterbaren Schnittstellen unbegrenzt (na ja, nicht ganz unbegrenzt, aber 65535 , aber Sie werden diese Grenze wahrscheinlich nie erreichen).
113. Es gibt die Threads T1, T2 und T3. Wie kann man sie nacheinander umsetzen?
Das allererste und einfachste, was mir in den Sinn kommt, ist die Verwendung der Methode „join()“ . Es unterbricht die Ausführung des aktuellen Threads (der die Methode aufgerufen hat), bis die Ausführung des Threads, für den die Methode aufgerufen wurde, abgeschlossen ist. Erstellen wir unsere eigene Thread-Implementierung:public class CustomThread extends Thread {
private String threadName;
public CustomThread(final String threadName){
this.threadName = threadName;
}
@Override
public void run() {
System.out.println(threadName + " - начал свою работу");
try {
// происходит некая логика
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(threadName + " - закончил свою работу");
}
}
Lassen Sie uns nacheinander drei solcher Threads mit join() starten :
CustomThread t1 = new CustomThread("Первый поток");
t1.start();
t1.join();
CustomThread t2 = new CustomThread("Второй поток");
t2.start();
t2.join();
CustomThread t3 = new CustomThread("Третий поток");
t3.start();
t3.join();
Konsolenausgabe:
Praktische Aufgaben
114. Matrix-Diagonalsumme (Leetcode-Problem)
Bedingung: Berechnen Sie die Summe aller Elemente auf der Hauptdiagonale und aller Elemente auf der Zusatzdiagonale, die nicht Teil der Hauptdiagonale sind. 1. Mit einer Matrix der Form: mat = [[1,2,3], [4,5,6], [7,8,9]] Die Ausgabe sollte - 25 sein. 2. Mit einer Matrix - mat = [[1,1 ,1,1], [1,1,1,1], [1,1,1,1], [1,1,1,1]] Die Ausgabe sollte - 8 3 sein. Mit a Matrix - mat = [[ 5]] Die Schlussfolgerung sollte lauten: - 5 Unterbrechen Sie das Lesen und setzen Sie Ihre Entscheidung um. Meine Lösung wäre folgende:public static int countDiagonalSum(int[][] matrix) {
int sum = 0;
for (int i = 0, j = matrix.length - 1; i < matrix.length; i++, j--) {
sum += matrix[i][i];
if (j != i) {
sum += matrix[i][j];
}
}
return sum;
}
Alles geschieht mit einem Durchgang durch das Array, bei dem wir zwei Indizes für den Bericht haben: i – für die Meldung der Zeilen des Arrays und der Spalten der Hauptdiagonale, j – für die Meldung der Spalten der Zusatzdiagonale. Wenn die Zelle der Hauptdiagonale und die Zusatzdiagonale zusammenfallen, wird einer der Werte bei der Berechnung der Summe ignoriert. Überprüfen wir anhand der Matrizen der Bedingung:
int[][] arr1 = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}};
System.out.println(countDiagonalSum(arr1));
int[][] arr2 = {
{1, 1, 1, 1},
{1, 1, 1, 1},
{1, 1, 1, 1},
{1, 1, 1, 1}};
System.out.println(countDiagonalSum(arr2));
int[][] arr3 = {{5}};
System.out.println(countDiagonalSum(arr3));
Konsolenausgabe:
115. Nullen verschieben (Leetcode-Herausforderung)
Bedingung: Verschieben Sie in einem ganzzahligen Array alle Nullen an das Ende und behalten Sie dabei die relative Reihenfolge der Elemente ungleich Null bei. 1. Mit einem Array: [0,1,0,3,12] Die Ausgabe sollte sein: [1,3,12,0,0] 2. Mit einem Array: [0] Die Ausgabe sollte sein: [0] Halten Sie inne und schreiben Sie meine Entscheidung auf ... Meine Entscheidung:public static void moveZeroes(int[] nums) {
int counterWithoutNulls = 0;
int counterWithNulls = 0;
int length = nums.length;
while (counterWithNulls < length) {
if (nums[counterWithNulls] == 0) {// находим нулевые элементы и увеличиваем счётчик
counterWithNulls++;
} else { // сдвигаем элементы на количество найденных нулевых элементов слева
nums[counterWithoutNulls++] = nums[counterWithNulls++];
}
}
while (counterWithoutNulls < length) {
nums[counterWithoutNulls++] = 0;// заполняем последние элементы массива нулями согласно счётчику нулей
}
}
Untersuchung:
int[] arr1 = {1, 2, 0, 0, 12, 9};
moveZeroes(arr1);
System.out.println(Arrays.toString(arr1));
int[] arr2 = {0};
moveZeroes(arr2);
System.out.println(Arrays.toString(arr2));
Konsolenausgabe:
116. Gegebene Listen-<String>-Namen. Entfernen Sie den ersten Buchstaben jedes Namens und drehen Sie die sortierte Liste
1. Das erste, was mir in den Sinn kommt, sind die Methoden der Collections- Klasse , die viele Hilfsmethoden für Sammlungen enthält:public static List<String> processTheList(List<String> nameList) {
for (int i = 0; i < nameList.size(); i++) {
nameList.set(i, nameList.get(i).substring(1));
}
Collections.sort(nameList);
return nameList;
}
2. Wenn wir außerdem Java Version 8 und höher verwenden, müssen wir die Lösung einfach über Streams anzeigen:
public static List<String> processTheList(List<String> nameList) {
return nameList.stream()
.map(x -> x.substring(1))
.sorted().collect(Collectors.toList());
}
Unabhängig von der gewählten Lösung kann die Prüfung wie folgt aussehen:
List<String> nameList = new ArrayList();
nameList.add("John");
nameList.add("Bob");
nameList.add("Anna");
nameList.add("Dmitriy");
nameList.add("Peter");
nameList.add("David");
nameList.add("Igor");
System.out.println(processTheList(nameList));
Konsolenausgabe:
117. Drehen Sie das Array um
Lösung 1 Auch hier fällt mir als Erstes die Verwendung der Methoden der Hilfsdienstprogrammklasse Collections ein . Da wir aber ein Array haben, müssen wir es zunächst in eine Sammlung (Liste) umwandeln:public static Integer[] reverse(Integer[] arr) {
List<Integer> list = Arrays.asList(arr);
Collections.reverse(list);
return list.toArray(arr);
}
Lösung 2 Da es sich bei der Frage um ein Array handelte, halte ich es für notwendig, die Lösung zu zeigen, ohne vorgefertigte, sofort einsatzbereite Funktionen zu verwenden, und zwar sozusagen nach den Klassikern:
public static Integer[] reverse(Integer[] arr) {
for (int i = 0; i < arr.length / 2; i++) {
int temp = arr[i];
arr[i] = arr[arr.length - 1 - i];
arr[arr.length - 1 - i] = temp;
}
return arr;
}
Untersuchung:
Integer[] arr = {1, 2, 3, 4, 5, 6, 7, 8, 9};
System.out.println(Arrays.toString(reverse(arr)));
Konsolenausgabe:
118. Überprüfen Sie, ob eine Zeichenfolge ein Palindrom ist
Lösung 1 Es lohnt sich, sich sofort an StringBuilder zu erinnern : Es ist flexibler und verfügt über zahlreiche verschiedene Methoden im Vergleich zum regulären String . Uns interessiert vor allem die umgekehrte Methode :public static boolean isPalindrome(String string) {
string = string.toLowerCase(); //приводит всю строку к нижнему регистру
StringBuilder builder = new StringBuilder();
builder.append(string);
builder.reverse(); // перевочиваем строку методом Builder-а
return (builder.toString()).equals(string);
}
Lösung: Der nächste Ansatz besteht darin, die standardmäßigen „Lücken“ nicht zu nutzen. Wir vergleichen die Zeichen von hinten mit den entsprechenden Zeichen von vorne:
public static boolean isPalindrome(String string) {
string = string.toLowerCase();
int length = string.length();
int fromBeginning = 0;
int fromEnd = length - 1;
while (fromEnd > fromBeginning) {
char forwardChar = string.charAt(fromBeginning++);
char backwardChar = string.charAt(fromEnd--);
if (forwardChar != backwardChar)
return false;
}
return true;
}
Und wir prüfen beide Ansätze:
boolean isPalindrome = isPalindrome("Tenet");
System.out.println(isPalindrome);
Konsolenausgabe:
119. Schreiben Sie einen einfachen Sortieralgorithmus (Bubble, Selection oder Shuttle). Wie kann es verbessert werden?
Als einfachen Algorithmus zur Implementierung habe ich die Auswahlsortierung gewählt – Selection Sort:public static void selectionSorting(int[] arr) {
for (int i = 0; i < arr.length - 1; i++) {
int min = i;
for (int j = i + 1; j < arr.length; j++) {
if (arr[j] < arr[min]) {
min = j; // выбираем минимальный элемент в текущем числовом отрезке
}
}
int temp = arr[min]; // меняем местами минимальный элемент с элементом под индексом i
arr[min] = arr[i]; // так Wie отрезок постоянно уменьшается
arr[i] = temp; // и выпадающие из него числа будут минимальными в текущем отрезке
} // и Wie итог - числа оставшиеся вне текущей итерации отсортированы от самого наименьшего к большему
}
Die verbesserte Version würde so aussehen:
public static void improvedSelectionSorting(int[] arr) {
for (int i = 0, j = arr.length - 1; i < j; i++, j--) { // рассматриваемый отрезок с каждой итерацией
// будет уменьшаться с ДВУХ сторон по одному элементу
int min = arr[i];
int max = arr[i];
int minIndex = i;
int maxIndex = i;
for (int n = i; n <= j; n++) { // выбираем min и max на текущем отрезке
if (arr[n] > max) {
max = arr[n];
maxIndex = n;
} else if (arr[n] < min) {
min = arr[n];
minIndex = n;
}
}
// меняем найденный минимальный элемент с позиции с индексом min на позицию с индексом i
swap(arr, i, minIndex);
if (arr[minIndex] == max) {// срабатывает, если элемент max оказался смещен предыдущей перестановкой -
swap(arr, j, minIndex); // на старое место min, поэтому с позиции с индексом min смещаем его на позицию j
} else {
swap(arr, j, maxIndex); // простое обмен местами элементов с индексами max и j
}
}
}
static int[] swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
return arr;
}
Nun müssen wir uns vergewissern, ob sich die Sortierung wirklich verbessert hat. Vergleichen wir die Leistung:
long firstDifference = 0;
long secondDifference = 0;
long primaryTime;
int countOfApplying = 10000;
for (int i = 0; i < countOfApplying; i++) {
int[] arr1 = {234, 33, 123, 4, 5342, 76, 3, 65,
3, 5, 35, 75, 255, 4, 46, 48, 4658, 44, 22,
678, 324, 66, 151, 268, 433, 76, 372, 45, 13,
9484, 499959, 567, 774, 473, 3, 32, 865, 67, 43,
63, 332, 24, 1};
primaryTime = System.nanoTime();
selectionSorting(arr1);
firstDifference += System.nanoTime() - primaryTime;
int[] arr2 = {234, 33, 123, 4, 5342, 76, 3, 65,
3, 5, 35, 75, 255, 4, 46, 48, 4658, 44, 22,
678, 324, 66, 151, 268, 433, 76, 372, 45, 13,
9484, 499959, 567, 774, 473, 3, 32, 865, 67, 43,
63, 332, 24, 1};
primaryTime = System.nanoTime();
improvedSelectionSorting(arr2);
secondDifference += System.nanoTime() - primaryTime;
}
System.out.println(((double) firstDifference / (double) secondDifference - 1) * 100 + "%");
Beide Sorten begannen im selben Zyklus, weil Wenn es separate Schleifen gäbe, würde die Sortierung im obigen Code ein schlechteres Ergebnis liefern, als wenn sie an zweiter Stelle platziert würde. Das liegt daran, dass sich das Programm „aufwärmt“ und dann etwas schneller arbeitet. Aber ich gehe ein wenig vom Thema ab. Nach fünf Durchläufen dieser Überprüfung in der Konsole sah ich eine Leistungssteigerung um: 36,41006735635892 % 51,46131097160771 % 41,88918834013988 % 48,091980705743566 % 37,120220461591444 % Für mich ist das ziemlich gut Ergebnis.
120. Schreiben Sie einen Algorithmus (Abfolge von Aktionen) zum Zusammensetzen eines Literals vom Typ int mit einem Literal vom Typ byte. Erklären Sie, was mit dem Gedächtnis passiert
-
Der Bytewert wird in int konvertiert. Dafür wird nicht 1 Byte Speicher zugewiesen, sondern wie bei allen int-Werten – 4, wenn dieser Wert noch nicht auf dem int-Stack liegt. Wenn ja, wird einfach ein Link dazu empfangen.
-
Es werden zwei int-Werte addiert und der dritte erhalten. Dafür wird ein neuer Speicherabschnitt zugewiesen – 4 Bytes (oder es wird ein Verweis vom Int-Stack auf den vorhandenen Wert empfangen).
In diesem Fall wird der Speicher von zwei Ints weiterhin belegt und ihre Werte werden jeweils auf dem Int-Stack gespeichert.
GO TO FULL VERSION