English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

Korrekte Implementierung eines Redis-Distributed Locks in der Java-Sprache beschreiben

1Inhaltsverzeichnis

.Grundbegriffe/O-Befehlen ab. IO ist der Prozess der Kopie von Daten zwischen dem Hauptspeicher und den externen Geräten (Festplatten, Terminals und Netzwerken usw.). IO ist die unterste Funktionalität der Betriebssysteme, die auf der Ebene der I

All Sprachen Runtime-Systeme bieten die Ausführung von I/O-Tools auf höherem Niveau. (c-printfscanf, java-objektorientierte Verpackung)

2.Java-Standard-IO-Überblick

Die Java-Standard-IO-Bibliothek ist eine objektorientierte Abstraktion der IO. Basierend auf der Bottom-up-Implementierung der lokalen Methoden müssen wir uns nicht um die Unterlagen kümmern. InputStream\OutputStream(ByteArray-Stream): Ein Byte nach dem anderen übertragen. Reader\Writer(CharArray-Stream): Ein Zeichen nach dem anderen übertragen.

3.nio-Übersicht

nio ist die Abkürzung für javaNewIO, in JDK1.4Neue API in der Verfügung gestellt. Die von Sun offiziell bewarbene Eigenschaft ist wie folgt:

–Bietet (Buffer) Pufferunterstützung für alle ursprünglichen Typen.

–Lösung für die Kodierung und Dekodierung von Zeichensätzen.

–Channel: Ein neuer ursprünglicher I/O-Abstraktion.

–Bietet eine Dateizugriffs-Schnittstelle, die Sperren und Memory-Mapped-Dateien unterstützt.

–Bietet Multi-Weg(non-bloking) nicht-blockierende, hoch skalierbare NetzwerkI/O.

Dieser Artikel wird sich um diese Eigenschaften drehen und sie erlernen und vorstellen.

4.Buffer&Chanel

Channel und buffer sind zwei grundlegende Daten-Typ-Abstraktionen in NIO.

Buffer:

–Ist ein kontinuierlicher Speicherblock.

–Ist der Zwischenpunkt für das Lesen oder Schreiben von NIO-Daten.

Channel:

–Quelle oder Ziel der Daten

–Ist der einzige Interface für den buffer zu Daten hinzufügen oder Daten aus dem buffer lesen, das einzigartige Interface des buffer-Objekts.

–Asynchrone I/O-Unterstützung

例子 1:CopyFile.java:

package sample;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public class CopyFile {
	public static void main(String[] args) throws Exception {
		String infile = "C:\\copy.sql";
		String outfile = "C:\\copy.txt";
		// Erhalten Sie die Eingabe-Ausgabe-Streams der Quelldatei und der Zieldatei 
		FileInputStream fin = new FileInputStream(infile);
		FileOutputStream fout = new FileOutputStream(outfile);
		// Erhalten Sie den Eingabe-Ausgabe-Kanal 
		FileChannel fcin = fin.getChannel();
		FileChannel fcout = fout.getChannel();
		// Puffer erstellen 
		ByteBuffer buffer = ByteBuffer.allocate(1024);
		while (true) {
			// Die clear-Methode setzt den Puffer zurück, so dass er neue Daten empfangen kann 
			buffer.clear();
			// Daten werden aus dem Eingabe-Kanal in den Puffer gelesen 
			int r = fcin.read(buffer);
			// Das read-Methode gibt die Anzahl der gelesenen Bytes zurück, kann Null sein, wenn der Kanal am Ende des Flusses angelangt ist, dann wird-1 
			if (r == -1) {
				break;
			}
			// Die flip-Methode ermöglicht es dem Puffer, neue gelesene Daten in einen anderen Kanal zu schreiben 
			buffer.flip();
			// Daten werden aus dem Ausgabe-Kanal in den Puffer geschrieben 
			fcout.write(buffer);
		}
	}
}

Die interne Struktur des Buffers ist wie folgt (Kopiert aus den Unterlagen):

Grafik2:Buffer-Internstruktur

Ein Buffer wird hauptsächlich von den drei Variablen position, limit und capacity gesteuert, um den Les- und Schreibprozess zu kontrollieren. Die Bedeutung dieser Variablen ist in der folgenden Tabelle dargestellt:

Parameter

Schreibmodus    

Lesemodus

position

Die aktuelle Anzahl der geschriebenen yksilöitä.

Der aktuelle Leseposition der yksilöitä.

limit

Stellt die maximale Anzahl der schreibbaren Daten yksilöitä dar und ist gleich der Kapazität.

Stellt die maximale Anzahl der lesbaren Daten yksilöitä dar, und ist identisch mit der Anzahl der geschriebenen yksilöitä.

capacity

Buffer-Kapazität

Buffer-Kapazität

Gängige Methoden von Buffer:

flip(): Wechselt vom Schreibmodus in den Lesezustand

rewind(): Setzt position auf 0, wird normalerweise für das erneute Lesen verwendet.

clear(): Leert den Buffer, bereitet ihn auf erneutes Schreiben vor (position wird 0, limit wird capacity).

compact(): Kopiert die noch nicht gelesenen Daten in den Kopf des Buffers.

mark()、reset(): mark kann einen Position markieren, reset kann auf diese Position zurücksetzen.

Gängige Typen von Buffer: ByteBuffer, MappedByteBuffer, CharBuffer, DoubleBuffer, FloatBuffer, IntBuffer, LongBuffer, ShortBuffer.

Gängige Typen von Channel: FileChannel, DatagramChannel (UDP), SocketChannel (TCP), ServerSocketChannel (TCP)

Eine einfache Leistungstestung wurde auf diesem Rechner durchgeführt. Die Leistung meines Laptops ist durchschnittlich. (Das spezifische Code kann im Anhang gefunden werden. Siehe Beispiele im Unterordner nio.sample.filecopy des Pakets.) Hier sind die Referenzdaten:

–场景1:Copy一个370M的文件

–场景2:三个线程同时拷贝,每个线程拷贝一个370M文件

场景

FileInputStream+

FileOutputStream

FileInputStream+

BufferedInputStream+

FileOutputStream

ByteBuffer+

FileChannel

MappedByteBuffer

+FileChannel

场景一时间 ( 毫秒)                 

25155

17500

19000

16500

场景二时间 ( 毫秒 )

69000

67031

74031

71016

5.nio.charset

字符编码解码:字节码本身只是一些数字,放到正确的上下文中被正确被解析。向ByteBuffer中存放数据时需要考虑字符集的编码方式,读取展示ByteBuffer数据时涉及对字符集解码。

Java.nio.charset提供了编码解码一套解决方案。

以我们最常见的http请求为例,在请求的时候必须对请求进行正确的编码。在得到响应时必须对响应进行正确的解码。

以下代码向baidu发一次请求,并获取结果进行显示。例子演示到了charset的使用。

例子2BaiduReader.java

package nio.readpage;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.net.InetSocketAddress;
import java.io.IOException;
public class BaiduReader {
	private Charset charset = Charset.forName("GBK");
	// 创建GBK字符集 
	private SocketChannel channel;
	public void readHTMLContent() {
		try {
			InetSocketAddress socketAddress = new InetSocketAddress( 
			"www.baidu.com", 80);
			//step1:打开连接 
			channel = SocketChannel.open(socketAddress);
			//step2:发送请求,使用GBK编码 
			channel.write(charset.encode("GET " + "/ HTTP/1.1" + "\r\n\r\n"));
			//step3:Daten lesen 
			ByteBuffer buffer = ByteBuffer.allocate(1024);
			// Erstellen1024Puffer der Bytes 
			while (channel.read(buffer) != -1) {
				buffer.flip();
				// Der Aufruf der Methode flip, bevor Byte-Operationen im Lese-Puffer durchgeführt werden. 
				System.out.println(charset.decode(buffer));
				// Verwendung der Methode decode von Charset, um Bytes in einen String zu konvertieren 
				buffer.clear();
				// Leeren des Puffers
			}
		}
		catch (IOException e) {
			System.err.println(e.toString());
		}
		finally {
			if (channel != null) {
				try {
					channel.close();
				}
				catch (IOException e) {
				}
			}
		}
	}
	public static void main(String[] args) {
		new BaiduReader().readHTMLContent();
	}
}

6.Nicht-blockierendes IO

Über das nicht-blockierende IO können wir aus mehreren Aspekten verstehen: Was ist blockierend, was ist nicht-blockierend, der Prinzip des nicht-blockierenden IO und die asynchronen Kern-APIs.

Was ist blockierend?

Ein häufiger Netzwerk-IO-Kommunikationsprozess sieht so aus:

Verstehen wir, was blockierend bedeutet, aus dem Netzwerk-Kommunikationsprozess:

Wenn die Verbindung in dem obigen Prozess noch nicht eingegangen ist, wird accept blockieren, und das Programm muss an diesem Punkt angehalten werden, während der CPU auf andere Threads umschaltet.

Wenn die Daten in dem obigen Prozess noch nicht bereit sind, wird read ebenfalls blockieren.

Eigenschaften des blockierenden Netzwerk-IO: Mehrere Verbindungen werden durch mehrere Threads bearbeitet. Jeder Thread hat seinen eigenen Stack-Speicher und belegt einige CPU-Zeit. Wenn ein Thread auf eine externe Vorbereitung wartet, wird er blockiert. Das Ergebnis ist, dass es viele Prozessor-Kontext-Wechsel gibt. Und die meisten Kontext-Wechsel könnten sinnlos sein. Zum Beispiel: Wenn ein Thread einen Port abhört, gibt es vielleicht nur wenige Anfragen pro Tag, aber der CPU muss ständig Kontext-Wechsel für diesen Thread durchführen, die meisten davon enden mit Blockierung.

Was ist nicht-blockierend?

Hier ist eine Metapher:

Auf einem Omnibus von A nach B gibt es viele Punkte, an denen Passagiere aussteigen könnten. Der Fahrer weiß nicht, an welchen Punkten welche Passagiere aussteigen werden, und wie sollte man am besten mit den Passagieren umgehen, die aussteigen müssen?

1Während der Fahrt fragt der Fahrer regelmäßig, ob jeder Passagier sein Ziel erreicht hat. Wenn jemand sagt, dass er angekommen ist, dann hält der Fahrer an und die Passagiere steigen aus. (Ähnlich blockierend)

2Jeder erzählt dem Fahrkartenverkäufer seinen Zielort und dann geht schlafen, der Fahrer kommuniziert nur mit dem Fahrkartenverkäufer, und die Passagiere werden von der Fahrkartenverkäuferin an bestimmten Punkten benachrichtigt, dass sie aussteigen sollen. (Ähnlich wie nicht-blockierend)

Es ist offensichtlich, dass jeder, der einen bestimmten Zielort erreichen möchte, als Thread betrachtet werden kann, und der Fahrer kann als CPU betrachtet werden. In blockierenden Systemen muss jeder Thread ständig abfragen und Kontext wechseln, um das Ziel zu erreichen. In nicht-blockierenden Systemen schlafen alle Passagiere (Threads) ein und werden nur wirklich geweckt, wenn die äußere Umgebung bereit ist, was sicherlich keine Blockierung verursacht.

Prinzip der nicht-blockierenden IO

Den gesamten Prozess in kleine Aufgaben umwandeln und durch Zusammenarbeit zwischen Aufgaben abschließen.

Ein spezieller Thread behandelt alle IO-Ereignisse und ist verantwortlich für die Verteilung.

Event-Driven-Mechanismus: Ereignisse werden ausgelöst, wenn sie eintreffen, anstatt synchron auf Ereignisse zu überwachen.

Thread-Kommunikation: Die Kommunikation zwischen Threads erfolgt durch wait, notify und andere Methoden. Dies stellt sicher, dass jeder Kontextwechsel sinnvoll ist und die Anzahl der sinnlosen Prozesswechsel verringert wird.

Hier ist die Struktur von asynchroner IO:

Reactor ist die metaphorische Rolle des Ticketverkäufers. Der Verarbeitungsablauf jedes Threads ist in der Regel das Lesen von Daten, Decodieren, Berechnen und Verarbeiten, Kodieren und Senden von Antworten.

Kern-API für asynchrone IO

Selector

Kernklasse für asynchrone IO, die Ereignisse auf einem oder mehreren Kanälen (channel) erkennen kann und Ereignisse weiterverteilt.

Mit einem select-Thread können mehrere Kanäle auf Ereignisse überwacht werden und entsprechende Antworten werden auf Ereignisbasis ausgelöst, ohne dass für jeden channel ein eigener Thread bereitgestellt werden muss.

SelectionKey

Enthält Informationen über den Zustand von Ereignissen und die Zuordnung von Kanälen zu Zeit.

Zusammenfassung

Das ist der gesamte Inhalt dieses Artikels über die Grundlagen von Java. Ich hoffe, es hilft Ihnen weiter. Freunde, die interessiert sind, können weiterhin andere verwandte Themen dieser Website lesen. Jegliche Unzulänglichkeiten sind willkommen, um Kommentare zu hinterlassen. Vielen Dank für die Unterstützung der Freunde dieser Website!

Erklärung: Der Inhalt dieses Artikels wurde aus dem Internet übernommen und gehört dem Urheberrecht des jeweiligen Autors. Der Inhalt wurde von Internetbenutzern freiwillig beigesteuert und hochgeladen. Diese Website besitzt keine Eigentumsrechte und hat den Inhalt nicht manuell bearbeitet. Sie übernimmt auch keine rechtlichen Verantwortlichkeiten. Wenn Sie verdächtige Urheberrechtsinhalte finden, sind Sie herzlich eingeladen, eine E-Mail an notice#w zu senden.3codebox.com (Bitte ersetzen Sie # durch @, wenn Sie eine E-Mail senden, um zu melden, und fügen Sie relevante Beweise bei. Sobald überprüft, wird die Website den verdächtigen Inhalt sofort löschen.)

Redis Anleitung