Cara Memulakan Benang di Jawa

1. Pengenalan

Dalam tutorial ini, kita akan meneroka pelbagai cara untuk memulakan utas dan melaksanakan tugas selari.

Ini sangat berguna, terutama ketika berurusan dengan operasi panjang atau berulang yang tidak dapat berjalan di utas utama , atau di mana interaksi UI tidak dapat ditahan sementara menunggu hasil operasi.

Untuk mengetahui lebih lanjut mengenai butiran utas, baca tutorial kami mengenai Kitaran Hidup Benang di Jawa.

2. Asas Menjalankan Benang

Kita boleh menulis logik dengan mudah dalam urutan selari dengan menggunakan rangka Thread .

Mari cuba contoh asas, dengan memperluas kelas Thread :

public class NewThread extends Thread { public void run() { long startTime = System.currentTimeMillis(); int i = 0; while (true) { System.out.println(this.getName() + ": New Thread is running..." + i++); try { //Wait for one sec so it doesn't print too fast Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } ... } } }

Dan sekarang kami menulis kelas kedua untuk memulakan dan memulakan urutan kami:

public class SingleThreadExample { public static void main(String[] args) { NewThread t = new NewThread(); t.start(); } }

Kita harus memanggil kaedah start () pada utas dalam keadaan BARU (setara dengan tidak dimulakan). Jika tidak, Java akan membuang contoh pengecualian IllegalThreadStateException .

Sekarang anggap kita perlu memulakan beberapa utas:

public class MultipleThreadsExample { public static void main(String[] args) { NewThread t1 = new NewThread(); t1.setName("MyThread-1"); NewThread t2 = new NewThread(); t2.setName("MyThread-2"); t1.start(); t2.start(); } }

Kod kami masih kelihatan sederhana dan sangat serupa dengan contoh yang boleh kami temui dalam talian.

Sudah tentu, ini jauh dari kod siap pengeluaran, di mana sangat penting untuk menguruskan sumber dengan cara yang betul, untuk mengelakkan peralihan konteks terlalu banyak atau terlalu banyak penggunaan memori.

Oleh itu, untuk menyiapkan pengeluaran, kita sekarang perlu menulis plat boiler tambahan untuk menangani:

  • penciptaan benang baru yang konsisten
  • bilangan utas hidup serentak
  • penyingkiran benang: sangat penting untuk benang daemon untuk mengelakkan kebocoran

Sekiranya kita mahu, kita boleh menulis kod kita sendiri untuk semua senario kes ini dan bahkan beberapa lagi, tetapi mengapa kita harus mencipta semula roda?

3. Rangka Kerja Pelayan

The ExecutorService menerapkan corak reka bentuk Thread Pool (juga disebut model pekerja atau kru pekerja yang direplikasi) dan mengurus pengurusan utas yang kami sebutkan di atas, dan ia menambah beberapa ciri yang sangat berguna seperti penggunaan semula benang dan barisan tugas.

Penggunaan semula benang, khususnya, sangat penting: dalam aplikasi berskala besar, memperuntukkan dan menyahpindah banyak objek utas menghasilkan overhead pengurusan memori yang ketara.

Dengan benang pekerja, kami meminimumkan overhead yang disebabkan oleh pembuatan benang.

Untuk memudahkan konfigurasi kumpulan, ExecutorService dilengkapi dengan konstruktor yang mudah dan beberapa pilihan penyesuaian, seperti jenis giliran, bilangan utas minimum dan maksimum dan konvensyen penamaan mereka.

Untuk maklumat lebih lanjut mengenai ExecutorService, sila baca Panduan kami untuk Java ExecutorService.

4. Memulakan Tugas dengan Pelaksana

Berkat kerangka kerja yang kuat ini, kita dapat mengubah pola pikir kita dari memulakan utas hingga menyerahkan tugas.

Mari lihat bagaimana kita dapat menyerahkan tugas tidak segerak kepada pelaksana kita:

ExecutorService executor = Executors.newFixedThreadPool(10); ... executor.submit(() -> { new Task(); });

Ada dua kaedah yang dapat kita gunakan: jalankan , yang tidak mengembalikan apa-apa, dan mengirimkan , yang mengembalikan Masa Depan yang merangkumi hasil pengiraan.

Untuk maklumat lebih lanjut mengenai Niaga Hadapan, sila baca Panduan kami untuk java.util.concurrent.Future.

5. Memulakan Tugas dengan Future yang Dapat Dilengkapkan

Untuk mendapatkan hasil akhir dari objek Future kita dapat menggunakan metode get yang tersedia di objek, tetapi ini akan menyekat thread induk hingga akhir pengiraan.

Sebagai alternatif, kita dapat mengelakkan halangan dengan menambahkan lebih banyak logik untuk tugas kita, tetapi kita harus meningkatkan kerumitan kod kita.

Java 1.8 memperkenalkan kerangka baru di atas konstruk Masa Depan untuk bekerja lebih baik dengan hasil pengiraan: the CompletableFuture .

CompletableFuture mengimplementasikan CompletableStage , yang menambah banyak kaedah untuk melampirkan panggilan balik dan mengelakkan semua paip yang diperlukan untuk menjalankan operasi pada hasilnya setelah siap.

Pelaksanaan untuk menyerahkan tugas jauh lebih mudah:

CompletableFuture.supplyAsync(() -> "Hello");

supplyAsync mengambil Pembekal yang mengandungi kod yang ingin kita laksanakan secara asinkron - dalam kes kita parameter lambda.

Tugas itu sekarang diserahkan secara implisit ke ForkJoinPool.commonPool () , atau kita dapat menentukan Pelaksana yang kita sukai sebagai parameter kedua.

Untuk mengetahui lebih lanjut mengenai CompletableFuture, sila baca Panduan Kami untuk CompletableFuture.

6. Menjalankan Tugas Tertunda atau Berkala

When working with complex web applications, we may need to run tasks at specific times, maybe regularly.

Java has few tools that can help us to run delayed or recurring operations:

  • java.util.Timer
  • java.util.concurrent.ScheduledThreadPoolExecutor

6.1. Timer

Timer is a facility to schedule tasks for future execution in a background thread.

Tasks may be scheduled for one-time execution, or for repeated execution at regular intervals.

Let's see what the code looks if we want to run a task after one second of delay:

TimerTask task = new TimerTask() { public void run() { System.out.println("Task performed on: " + new Date() + "n" + "Thread's name: " + Thread.currentThread().getName()); } }; Timer timer = new Timer("Timer"); long delay = 1000L; timer.schedule(task, delay);

Now let's add a recurring schedule:

timer.scheduleAtFixedRate(repeatedTask, delay, period);

This time, the task will run after the delay specified and it'll be recurrent after the period of time passed.

For more information, please read our guide to Java Timer.

6.2. ScheduledThreadPoolExecutor

ScheduledThreadPoolExecutor has methods similar to the Timer class:

ScheduledExecutorService executorService = Executors.newScheduledThreadPool(2); ScheduledFuture resultFuture = executorService.schedule(callableTask, 1, TimeUnit.SECONDS);

To end our example, we use scheduleAtFixedRate() for recurring tasks:

ScheduledFuture resultFuture = executorService.scheduleAtFixedRate(runnableTask, 100, 450, TimeUnit.MILLISECONDS);

The code above will execute a task after an initial delay of 100 milliseconds, and after that, it'll execute the same task every 450 milliseconds.

If the processor can't finish processing the task in time before the next occurrence, the ScheduledExecutorService will wait until the current task is completed, before starting the next.

To avoid this waiting time, we can use scheduleWithFixedDelay(), which, as described by its name, guarantees a fixed length delay between iterations of the task.

Untuk keterangan lebih lanjut mengenai SchedchedExecutorService, sila baca Panduan kami untuk Java ExecutorService.

6.3. Alat Mana Yang Lebih Baik?

Sekiranya kita menjalankan contoh di atas, hasil pengiraannya sama.

Jadi, bagaimana kita memilih alat yang betul ?

Apabila kerangka menawarkan pelbagai pilihan, penting untuk memahami teknologi yang mendasari untuk membuat keputusan yang tepat.

Mari cuba menyelam sedikit lebih dalam di bawah tudung.

Pemasa :

  • tidak menawarkan jaminan masa nyata: ia schedules tugas menggunakan Object.wait (long) kaedah
  • terdapat satu thread latar belakang, jadi tugas berjalan secara berurutan dan tugas yang lama dapat menunda yang lain
  • runtime exceptions thrown in a TimerTask would kill the only thread available, thus killing Timer

ScheduledThreadPoolExecutor:

  • can be configured with any number of threads
  • can take advantage of all available CPU cores
  • catches runtime exceptions and lets us handle them if we want to (by overriding afterExecute method from ThreadPoolExecutor)
  • cancels the task that threw the exception, while letting others continue to run
  • relies on the OS scheduling system to keep track of time zones, delays, solar time, etc.
  • provides collaborative API if we need coordination between multiple tasks, like waiting for the completion of all tasks submitted
  • provides better API for management of the thread life cycle

The choice now is obvious, right?

7. Difference Between Future and ScheduledFuture

In our code examples, we can observe that ScheduledThreadPoolExecutor returns a specific type of Future: ScheduledFuture.

ScheduledFuture extends both Future and Delayed interfaces, thus inheriting the additional method getDelay that returns the remaining delay associated with the current task. It's extended by RunnableScheduledFuture that adds a method to check if the task is periodic.

ScheduledThreadPoolExecutor melaksanakan semua konstruk ini melalui kelas dalaman ScheduledFutureTask dan kegunaan mereka untuk mengawal kitaran hidup tugas.

8. Kesimpulan

Dalam tutorial ini, kami bereksperimen dengan pelbagai kerangka yang tersedia untuk memulakan utas dan menjalankan tugas secara selari.

Kemudian, kami membahas perbezaan antara Timer dan SchedchedThreadPoolExecutor .

Kod sumber untuk artikel tersebut terdapat di GitHub.