CodeGym /Kurslar /JAVA 25 SELF /Asinxron IO: AsynchronousFileChannel (NIO2)

Asinxron IO: AsynchronousFileChannel (NIO2)

JAVA 25 SELF
Səviyyə, Dərs
Mövcuddur

1. Asinxron IO ilə tanışlıq

Gəlin dərhal terminlərlə aydınlaşaq. Klassik (sinxron) IO-da siz oxu və ya yazma metodunu çağıranda icra axınınız (məsələn, proqramın əsas axını) dayanır və əməliyyat bitənədək gözləyir. Bu, sanki dostunuza zəng etmisiniz və o, dəstəyi qaldırmayana qədər sadəcə dayanıb telefona baxıb gözləməyiniz kimidir.

Asinxron IO (AIO) — oxu/yazma əməliyyatını sistemə həvalə etdiyiniz və öz işinizə davam etdiyiniz vaxtdır. Əməliyyat tamamlandıqda — sizə geri «zəng edəcəklər» (məsələn, callback-metodunuz çağırılacaq və ya nəticə Future vasitəsilə qaytarılacaq).

Harada lazımdır?

  • Server tətbiqləri: disk «düşünərkən» axınları boş yerə sərf etməmək üçün.
  • Böyük faylların kütləvi emalı: əsas axını bloklamamaq üçün.
  • UI-li tətbiqlər: oxu/yazma zamanı interfeys «donmasın» deyə.

Təsəvvür edin ki, pitsa sifariş etmisiniz. Sinxron dünyada qapıda dayanıb kuryeri gözləyərdiniz. Asinxron dünyada isə öz işinizi görürsünüz, pitsa gələndə sizə zəng edirlər və deyirlər: «Pitsa buradadır!»

2. AsynchronousFileChannel icmalı

Java-da asinxron giriş/çıxış 7-ci versiyadan etibarən java.nio.channels paketində reallaşdırılıb. Əsas qəhrəman — AsynchronousFileChannel sinfidir.

Nələri bacarır?

  • Fayla məlumatları asinxron şəkildə oxumaq və yazmaq.
  • Bufferlərlə işləmək (ByteBuffer).
  • Nəticəni əldə etməyin müxtəlif yanaşmaları: Future və ya CompletionHandler vasitəsilə.
  • Hadisələrin işlənməsi üçün axın hovuzunu (ExecutorService) açıq şəkildə göstərməyə imkan verir.

Əsas metodlar

  • read(ByteBuffer dst, long position): Future<Integer> qaytarır.
  • read(ByteBuffer dst, long position, A attachment, CompletionHandler<Integer, ? super A> handler).
  • write(ByteBuffer src, long position): Future<Integer> qaytarır.
  • write(ByteBuffer src, long position, A attachment, CompletionHandler<Integer, ? super A> handler).
  • static open(Path file, Set<OpenOption> options, ExecutorService executor, FileAttribute<?>... attrs) — kanalı açır.

İş variantları:

  • Future vasitəsilə: əməliyyatı işə salırsınız və sonra onun bitməsini gözləyə bilərsiniz.
  • CompletionHandler vasitəsilə: əməliyyat bitəndə (və ya xətaya düşəndə) çağırılacaq «handler» ötürürsünüz.

Faylın asinxron oxu/yazması üçün açılmasına nümunə

import java.nio.channels.AsynchronousFileChannel;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;

AsynchronousFileChannel channel = AsynchronousFileChannel.open(
    Path.of("data.txt"),
    StandardOpenOption.READ, StandardOpenOption.WRITE
);

Hadisələrin işlənməsi üçün axın hovuzunu açıq şəkildə göstərmək də olar:

import java.util.EnumSet;
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;

ExecutorService executor = Executors.newFixedThreadPool(4);

AsynchronousFileChannel channel = AsynchronousFileChannel.open(
    Path.of("data.txt"),
    EnumSet.of(StandardOpenOption.READ, StandardOpenOption.WRITE),
    executor
);

Maraqlı fakt:
Əgər ExecutorService göstərməsəniz, Java IO hadisələrini xidmət edən daxili axın hovuzu yaradacaq. Sadə tapşırıqlar üçün bu kifayətdir, amma server tətbiqlərində hovuzu özünüz idarə etmək daha yaxşıdır.

3. İcra axınları (ExecutorService) və onların rolu

Asinxron kanalla işləyəndə, səhnə arxasında Java sizin callback-lərinizi icra etməli və ya Future-u tamamlamalıdır. O bunu sehrlə deyil, xüsusi işçi axınları — executor servisi vasitəsilə edir.

Əgər öz axın hovuzunuzu ötürməsəniz, Java sadəcə daxili birini yaradır — adətən hər prosessora bir axın. Rahatdır, amma həmişə təhlükəsiz deyil. Axınların sayına, tapşırıqların prioritetinə və yükün paylanmasına özünüz nəzarət etmək istəyəndə, öz ExecutorService-inizi yaradıb onu open-ə ötürmək daha yaxşıdır.

Server tətbiqlərində bu xüsusilə vacibdir. Öz hovuzunuz olmadan gözlənilməz yük sıçrayışları almaq asandır — hamar iş əvəzinə server «boğula» bilər.

Nümunə:

ExecutorService pool = Executors.newFixedThreadPool(8);

AsynchronousFileChannel channel = AsynchronousFileChannel.open(
    Path.of("huge.log"),
    EnumSet.of(StandardOpenOption.READ),
    pool
);

Hovuz seçiminin təsiri:

  • Çox axın — daha çox paralellik, amma sistemə daha çox yük.
  • Az axın — daha az eyni vaxtlı əməliyyat, amma daha az overhead.
  • Minlərlə asinxron əməliyyat işə salırsınızsa, balans barədə düşünün!

4. Təcrübə: Asinxron fayl oxuma

Sinxron oxu (müqayisə üçün)

import java.nio.file.Files;
import java.nio.file.Path;

byte[] data = Files.readAllBytes(Path.of("input.txt"));
System.out.println("Oxunan bayt sayı: " + data.length);

Problem ondadır ki, axın fayl tam oxunana qədər gözləyir. Fayl böyükdürsə və ya disk ləng işləyirsə, proqram da «ləngiyir» — bu an digər hər şey pauzada qalır.

AsynchronousFileChannel və Future ilə asinxron oxu

import java.nio.channels.AsynchronousFileChannel;
import java.nio.ByteBuffer;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.concurrent.Future;

public class AsyncReadExample {
    public static void main(String[] args) throws Exception {
        Path path = Path.of("input.txt");
        try (AsynchronousFileChannel channel = AsynchronousFileChannel.open(path, StandardOpenOption.READ)) {
            ByteBuffer buffer = ByteBuffer.allocate(1024); // 1 KB oxuyuruq

            Future<Integer> result = channel.read(buffer, 0);

            // Paralel olaraq başqa işlər görmək olar!
            System.out.println("Oxu işə salındı...");

            // ... sonra nəticəni gözləyirik
            int bytesRead = result.get(); // əməliyyat bitənədək axını bloklayır

            System.out.println("Oxunan bayt sayı: " + bytesRead);

            buffer.flip();
            // Baytları sətirə çeviririk (əgər mətnsə)
            byte[] data = new byte[bytesRead];
            buffer.get(data, 0, bytesRead);
            String text = new String(data);
            System.out.println("Məzmun: " + text);
        }
    }
}
  • channel.read(buffer, 0) — 0 mövqeyindən asinxron oxunu işə salır.
  • Future<Integer> qaytarır, onu nəticəni gözləmək üçün istifadə etmək olar.
  • Əməliyyat bitənədək digər işləri icra edə bilərsiniz.
  • result.get() axını bloklayır, amma yalnız nəticə hələ hazır deyilsə.

CompletionHandler ilə asinxron oxu

(Ətraflısına növbəti mühazirədə baxacağıq, amma «dadına baxmaq» üçün...)

import java.nio.channels.AsynchronousFileChannel;
import java.nio.ByteBuffer;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.nio.channels.CompletionHandler;

public class AsyncReadWithHandler {
    public static void main(String[] args) throws Exception {
        Path path = Path.of("input.txt");
        try (AsynchronousFileChannel channel = AsynchronousFileChannel.open(path, StandardOpenOption.READ)) {
            ByteBuffer buffer = ByteBuffer.allocate(1024);

            channel.read(buffer, 0, buffer, new CompletionHandler<Integer, ByteBuffer>() {
                @Override
                public void completed(Integer bytesRead, ByteBuffer buf) {
                    buf.flip();
                    byte[] data = new byte[bytesRead];
                    buf.get(data, 0, bytesRead);
                    String text = new String(data);
                    System.out.println("Asinxron oxundu: " + text);
                }

                @Override
                public void failed(Throwable exc, ByteBuffer buf) {
                    System.err.println("Oxu xətası: " + exc.getMessage());
                }
            });

            // Proqramın dərhal bitməsinə imkan vermirik (əks halda callback icra olunmaya bilər)
            Thread.sleep(100); // Real tətbiqlərdə — daha yaxşısı latch, future və s. ilə sinxronizasiya etməkdir.
        }
    }
}

5. Faydalı nüanslar

Müqayisə: Asinxron vs Sinxron oxu

Xüsusiyyət Sinxron IO ( Files.readAllBytes ) Asinxron IO ( AsynchronousFileChannel )
Axını bloklayır Bəli Xeyr (əgər get() çağırmasanız)
Miqyaslanma Aşağı Yüksək
UI/serverlər üçün uyğundur Xeyr Bəli
Kodun mürəkkəbliyi Sadə Bir az daha mürəkkəb
Resursların idarə edilməsi Sadə Kanalı bağlamağı unutmaq olmaz!

Asinxron IO-nun iş sxemi

sequenceDiagram
    participant Main as Sizin axınınız
    participant OS as Əməliyyat sistemi
    participant Disk as Disk

    Main->>OS: Asinxron oxunu işə salır (read)
    OS->>Disk: Məlumatları oxuyur
    Main->>Main: Başqa tapşırıqları yerinə yetirir
    OS-->>Main: Bitməyi bildirir (Future/CompletionHandler)
    Main->>Main: Nəticəni emal edir

6. AsynchronousFileChannel ilə işləyərkən tipik xətalar

Xəta №1: kanalı bağlamağı unutmaq.
AsynchronousFileChannel — bağlanmalı olan bir resursdur. Kanalı (channel.close() və ya try-with-resources) bağlamağı unutsaq, deskriptor sızıntıları və fayllara çıxış problemləri yarana bilər. Mümkün olan hər yerdə try-with-resources istifadə edin.

Xəta №2: əsas axında bloklayan get().
Əgər Future istifadə edir və əsas axında (məsələn, UI-tətbiqində) get() çağırırsınızsa, asinxron IO-nun mənası itir — axın yenə də gözləyəcək. Nəticəni gözləmək üçün CompletionHandler və ya ayrıca axından istifadə edin.

Xəta №3: ByteBuffer ilə səhv iş.
Buffera yazdıqdan sonra onu oxuya hazırlamaq üçün flip() etməyi unutmayın. Oxuduqdan sonra — təkrar istifadə edəcəksinizsə clear() və ya compact().

Xəta №4: xətaların emalını unutmaq.
Asinxron əməliyyatlar xətayla bitə bilər (məsələn, fayl tapılmadı, icazə yoxdur). Əgər istisnaları CompletionHandler-də emal etməsəniz və ya Future-də xətanı yoxlamasanız, proqram əməliyyatı «səssizcə» yerinə yetirməyəcək.

Xəta №5: nəzərə alınmayan paralellik.
Eyni kanalda bir neçə əməliyyatı eyni vaxtda işə salırsınızsa, kodunuzun axın təhlükəsiz olduğuna və bufferlər və fayl mövqeləri üzərində yarış problemlərinin yaranmadığına əmin olun.

1
Tapşırıq
JAVA 25 SELF, səviyyə, dərs
Bağlanıb
Asinxron məlumat qəbulu üçün hazırlıq 🚀
Asinxron məlumat qəbulu üçün hazırlıq 🚀
1
Tapşırıq
JAVA 25 SELF, səviyyə, dərs
Bağlanıb
Jurnala vacib hadisənin asinxron yazılması ✍️
Jurnala vacib hadisənin asinxron yazılması ✍️
Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION