Lập trình đa luồng trong Java (Java Multi-threading)

Công Nghệ
Lập trình đa luồng trong Java (Java Multi-threading)
Bài viết được sự cho phép của tác giả Giang Phan Lập trình đa luồng (Multi-threading) là một kỹ thuật trong lập trình mà một ứng dụng có thể thực thi nhiều phần công việc (luồng – thread) một cách đồng thời. Trong Java, đa luồng là một tính năng mạnh mẽ và quan trọng, giúp các ứng dụng có thể tận dụng tối đa tài nguyên CPU và giảm thời gian xử lý bằng cách thực thi nhiều tác vụ cùng lúc. Java cung cấp hỗ trợ sẵn có cho lập trình đa luồng thông qua lớp Thread và giao diện Runnable . Việc sử dụng đa luồng trong Java giúp tăng cường hiệu suất và cải thiện tính đáp ứng (responsiveness) của ứng dụng, đặc biệt trong các ứng dụng yêu cầu xử lý song song hoặc thời gian thực. Khái niệm thread trong Java Thread là gì? Thread (luồng) về cơ bản là một tiến trình con (sub-process). Một đơn vị xử lý nhỏ nhất của máy tính có thể thực hiện một công việc riêng biệt. Trong Java, các luồng được quản lý bởi máy ảo Java (JVM). Multi-thread là gì? Multi-thread (đa luồng) là một tiến trình thực hiện nhiều luồng đồng thời. Một ứng dụng Java ngoài luồng chính có thể có các luồng khác thực thi đồng thời làm ứng dụng chạy nhanh và hiệu quả hơn. VD: Trình duyệt web hay các chương trình chơi nhạc là 1 ví dụ điển hình về đa luồng. + Khi duyệt 1 trang web, có rất nhiều hình ảnh, CSS, javascript… được tải đồng thời bởi các luồng khác nhau. + Khi play nhạc, chúng ta vẫn có thể tương tác được với nút điều khiển như: Play, pause, next, back … vì luồng phát...

Bài viết được sự cho phép của tác giả Giang Phan

Lập trình đa luồng (Multi-threading) là một kỹ thuật trong lập trình mà một ứng dụng có thể thực thi nhiều phần công việc (luồng – thread) một cách đồng thời. Trong Java, đa luồng là một tính năng mạnh mẽ và quan trọng, giúp các ứng dụng có thể tận dụng tối đa tài nguyên CPU và giảm thời gian xử lý bằng cách thực thi nhiều tác vụ cùng lúc.

Java cung cấp hỗ trợ sẵn có cho lập trình đa luồng thông qua lớp Thread và giao diện Runnable. Việc sử dụng đa luồng trong Java giúp tăng cường hiệu suất và cải thiện tính đáp ứng (responsiveness) của ứng dụng, đặc biệt trong các ứng dụng yêu cầu xử lý song song hoặc thời gian thực.

Khái niệm thread trong Java

Thread là gì?

Thread (luồng) về cơ bản là một tiến trình con (sub-process). Một đơn vị xử lý nhỏ nhất của máy tính có thể thực hiện một công việc riêng biệt. Trong Java, các luồng được quản lý bởi máy ảo Java (JVM).

Multi-thread là gì?

Multi-thread (đa luồng) là một tiến trình thực hiện nhiều luồng đồng thời. Một ứng dụng Java ngoài luồng chính có thể có các luồng khác thực thi đồng thời làm ứng dụng chạy nhanh và hiệu quả hơn.

VD: Trình duyệt web hay các chương trình chơi nhạc là 1 ví dụ điển hình về đa luồng.

+ Khi duyệt 1 trang web, có rất nhiều hình ảnh, CSS, javascript… được tải đồng thời bởi các luồng khác nhau.

+ Khi play nhạc, chúng ta vẫn có thể tương tác được với nút điều khiển như: Play, pause, next, back … vì luồng phát nhạc là luồng riêng biệt với luồng tiếp nhận tương tác của người dùng.

Tuyển dụng Java lương cao up to 2000USD

Đa nhiệm (multitasking)

Multitasking: Là khả năng chạy đồng thời một hoặc nhiều chương trình cùng một lúc trên một hệ điều hành. Hệ điều hành quản lý việc này và sắp xếp lịch phù hợp cho các chương trình đó. Ví dụ, trên hệ điều hành Windows chúng ta có làm việc đồng thời với các chương trình khác nhau như: Microsoft Word, Excel, Media Player, …

Chúng ta sử dụng đa nhiệm để tận dụng tính năng của CPU.

Đa nhiệm có thể đạt được bằng hai cách:

  1. Đa nhiệm dựa trên đơn tiến trình (Process) – Đa tiến trình (Multiprocessing).
    • Mỗi tiến trình có địa chỉ riêng trong bộ nhớ, tức là mỗi tiến trình phân bổ vùng nhớ riêng biệt.
    • Tiến trình là nặng.
    • Sự giao tiếp giữa các tiến trình có chi phí cao.
    • Chuyển đổi từ tiến trình này sang tiến trình khác đòi hỏi thời gian để đăng ký việc lưu và tải các bản đồ bộ nhớ, các danh sách cập nhật, …
  2. Đa nhiệm dựa trên luồng (Thread) – Đa luồng (MultiThreading).
    • Các luồng chia sẻ không gian địa chỉ ô nhớ giống nhau.
    • Luồng là nhẹ.
    • Sự giao tiếp giữa các luồng có chi phí thấp.

Đa tiến trình (multiprocessing) và đa luồng (multithreading) cả hai được sử dụng để tạo ra hệ thống đa nhiệm (multitasking). Nhưng chúng ta sử dụng đa luồng nhiều hơn đa tiến trình bởi vì các luồng chia sẻ một vùng bộ nhớ chung. Chúng không phân bổ vùng bộ nhớ riêng biệt để tiết kiệm bộ nhớ, và chuyển đổi ngữ cảnh giữa các luồng mất ít thời gian hơn tiến trình.

Ưu điểm của Multi-thread Java

Đầu tiên, đa luồng giúp tăng hiệu suất của ứng dụng bằng cách chạy song song nhiều tác vụ. Thay vì thực hiện tuần tự, các tác vụ có thể xử lý đồng thời, tận dụng tối đa CPU, đặc biệt trong môi trường đa lõi (multi-core). Mỗi luồng có thể dùng chung và chia sẻ nguồn tài nguyên trong quá trình chạy, nhưng có thể thực hiện một cách độc lập.

Đa luồng còn giúp cải thiện tính đáp ứng của ứng dụng. Trong các ứng dụng giao diện người dùng (UI), việc sử dụng đa luồng tránh tình trạng ứng dụng bị “treo” khi thực hiện các tác vụ dài, từ đó cải thiện trải nghiệm người dùng. Điều này rất quan trọng khi cần thực hiện các tác vụ nặng như tải dữ liệu hoặc xử lý I/O.

Ngoài ra, đa luồng hỗ trợ xử lý đồng thời nhiều yêu cầu. Đối với các hệ thống máy chủ hoặc ứng dụng web, đa luồng cho phép xử lý nhiều yêu cầu của người dùng cùng lúc, giảm thời gian chờ đợi và tăng khả năng phục vụ. Các luồng cũng chia sẻ tài nguyên bộ nhớ, giúp tiết kiệm tài nguyên so với việc tạo nhiều tiến trình riêng biệt.

Nhược điểm của đa luồng trong Java

Tuy nhiên, lập trình đa luồng có một số nhược điểm lớn. Độ phức tạp của nó là một trong những thách thức lớn nhất. Khi làm việc với nhiều luồng, lập trình viên phải xử lý đồng bộ hóa, phối hợp luồng và tránh các lỗi như deadlock (tình trạng chết) hoặc race condition (điều kiện cạnh tranh). Điều này đòi hỏi kỹ năng cao và kinh nghiệm.

Đồng bộ hóa dữ liệu giữa các luồng cũng là một vấn đề quan trọng. Khi nhiều luồng truy cập chung một tài nguyên, nếu không đồng bộ đúng cách, dữ liệu có thể bị thay đổi ngoài ý muốn, dẫn đến lỗi. Đồng bộ hóa giúp ngăn chặn tình trạng này, nhưng nó làm giảm hiệu suất vì các luồng phải chờ nhau. Đồng thời, nếu đồng bộ không chính xác, có thể gây ra tình trạng deadlock, khi các luồng bị khóa lẫn nhau và không thể tiếp tục thực thi.

Deadlock (Khoá chết) là gì? Deadlock xảy ra khi 2 tiến trình đợi nhau hoàn thành, trước khi chạy. Kết quả của quá trình là cả 2 tiến trình không bao giờ kết thúc

Một vấn đề khác là race condition, khi nhiều luồng cùng thao tác với một tài nguyên mà không kiểm soát đúng cách. Điều này có thể dẫn đến dữ liệu không chính xác hoặc bị hỏng.

Cuối cùng, lập trình đa luồng còn dẫn đến tăng sử dụng tài nguyên. Mỗi luồng yêu cầu bộ nhớ và CPU riêng, và khi có quá nhiều luồng, việc chuyển đổi giữa các luồng (context switching) làm giảm hiệu suất tổng thể của hệ thống.

Vòng đời (các trạng thái) của một Thread trong Java

Lập trình đa luồng trong Java (Java Multi-threading)Lập trình đa luồng trong Java (Java Multi-threading)

Vòng đời của thread trong java được kiểm soát bởi JVM. Java định nghĩa các trạng thái của luồng trong các thuộc tính static của lớp Thread.State:

  • NEW : Đây là trạng thái khi luồng vừa được khởi tạo bằng phương thức khởi tạo của lớp Thread nhưng chưa được start(). Ở trạng thái này, luồng được tạo ra nhưng chưa được cấp phát tài nguyên và cũng chưa chạy. Nếu luồng đang ở trạng thái này mà ta gọi các phương thức ép buộc stop,resume,suspend … sẽ là nguyên nhân sảy ra ngoại lệ IllegalThreadStateException .
  • RUNNABLE : Sau khi gọi phương thức start() thì luồng test đã được cấp phát tài nguyên và các lịch điều phối CPU cho luồng test cũng bắt đầu có hiệu lực. Ở đây, chúng ta dùng trạng thái là Runnable chứ không phải Running, vì luồng không thực sự luôn chạy mà tùy vào hệ thống mà có sự điều phối CPU khác nhau.
  • WAITING : Thread chờ không giới hạn cho đến khi một luồng khác đánh thức nó.
  • TIMED_WAITING : Thread chờ trong một thời gian nhất định, hoặc là có một luồng khác đánh thức nó.
  • BLOCKED: Đây là 1 dạng của trạng thái “Not Runnable”, là trạng thái khi Thread vẫn còn sống, nhưng hiện tại không được chọn để chạy. Thread chờ một monitor để unlock một đối tượng mà nó cần.
  • TERMINATED : Một thread ở trong trạng thái terminated hoặc dead khi phương thức run() của nó bị thoát.

Cách tạo thread trong Java

Trong java ta có thể tạo ra một luồng bằng một trong hai cách sau: tạo 1 đối tượng của lớp được extend từ class Thread hoặc implements từ interface Runnable.

Tạo luồng bằng cách extend từ lớp Thread

Để tạo luồng bằng cách tạo lớp kế thừa từ lớp Thread, ta phải làm các công việc sau :

  1. Khai báo 1 lớp mới kế thừa từ lớp Thread
  2. Override lại phương thức run ở lớp này, những gì trong phương thức run sẽ được thực thi khi luồng bắt đầu chạy. Sau khi luồng chạy xong tất cả các câu lệnh trong phương thức run thì luồng cũng tự hủy.
  3. Tạo 1 thể hiện (hay 1 đối tượng) của lớp ta vừa khai báo.
  4. Sau đó gọi phương thức start() của đối tượng này để bắt đầu thực thi luồng.
package com.gpcoder.simple;

public class TheadSimple extends Thread {
    public void run() {
        System.out.println("thread is running...");
    }

    public static void main(String args[]) {
        TheadSimple t1 = new TheadSimple();
        t1.start();
    }
}

Lưu ý :

  • Tuy ta khai báo những công việc cần làm của luồng trong phương thức run() nhưng khi thực thi luồng ta phải gọi phương thức start(). Vì đây là phương thức đặc biệt mà java xây dựng sẵn trong lớp Thread, phương thức này sẽ cấp phát tài nguyên cho luồng mới rồi chạy phương thức run() ở luồng này. Vì vậy, nếu ta gọi phương thức run() mà không gọi start() thì cũng tương đương với việc gọi 1 phương thức của 1 đối tượng bình thường và phương thức vẫn chạy trên luồng mà gọi phương thức chứ không chạy ở luồng mới tạo ra, nên vẫn chỉ có 1 luồng chính làm việc chứ ứng dụng vẫn không phải là đa luồng.
  • Sau khi start một thread, nó không bao giờ có thể được start lại. Nếu bạn làm như vậy, một ngoại lệ IllegalThreadStateException sẽ xảy ra.

Tạo luồng bằng cách implement từ Interface Runnable

Để tạo luồng bằng cách hiện thực từ Interface Runnable, ta phải làm các công việc sau :

  1. Khai báo 1 lớp mới implements từ Interface Runnable
  2. Hiện thực phương thức run() ở lớp này, những gì trong phương thức run() sẽ được thực thi khi luồng bắt đầu chạy. Sau khi luồng chạy xong tất cả các câu lệnh trong phương thức run thì luồng cũng tự hủy.
  3. Tạo 1 thể hiện (hay 1 đối tượng) của lớp ta vừa khai báo. (VD : Tên đối tượng là r1)
  4. Tạo 1 thể hiện của lớp Thread bằng phương thức khởi tạo : Thread(Runnable target)
    • Runnable target: Là 1 đối tượng thuốc lớp được implements từ giao diện Runnable.
    • Ví dụ: Thread t1 = new Thread(r1);
  5. Gọi phương thức start() của đối tượng t1.
package com.gpcoder.simple;

public class RunnableSimple implements Runnable {
    public void run() {
        System.out.println("thread is running...");
    }

    public static void main(String args[]) {
        RunnableSimple runable = new RunnableSimple();
        Thread t1 = new Thread(runable);
        t1.start();
    }
}

Khi nào implements từ interface Runnable?

+ Cách hay được sử dụng và được yêu thích là dùng interface Runnable, bởi vì nó không yêu cầu phải tạo một lớp kế thừa từ lớp Thread. Trong trường hợp ứng dụng thiết kế yêu cầu sử dụng đa kế thừa, chỉ có interface mới có thể giúp giải quyết vấn đề. Ngoài ra, Thread Pool rất hiểu quả và có thể được cài đặt, sử dụng rất hơn giản.
+ Trong trường hợp còn lại ta có thể kế thừa từ lớp Thread.

Ví dụ minh họa sử dụng đa luồng

Ví dụ Tạo luồng bằng cách extend từ class Thread

Tạo luồng extend từ class Thead

package com.gpcoder.flow;

public class ThreadDemo extends Thread {
    private Thread t;
    private String threadName;

    ThreadDemo(String name) {
        threadName = name;
        System.out.println("Creating " + threadName);
    }

    @Override
    public void run() {
        System.out.println("Running " + threadName);
        try {
            for (int i = 4; i > 0; i--) {
                System.out.println("Thread: " + threadName + ", " + i);
                // Let the thread sleep for a while.
                Thread.sleep(50);
            }
        } catch (InterruptedException e) {
            System.out.println("Thread " + threadName + " interrupted.");
        }
        System.out.println("Thread " + threadName + " exiting.");
    }

    public void start() {
        System.out.println("Starting " + threadName);
        if (t == null) {
            t = new Thread(this, threadName);
            t.start();
        }
    }

}

Chương trình sử dụng đa luồng:

package com.gpcoder.flow;

public class ThreadDemoTest {
    public static void main(String args[]) {
        System.out.println("Main thread running... ");

        ThreadDemo T1 = new ThreadDemo("Thread-1-HR-Database");
        T1.start();

        ThreadDemo T2 = new ThreadDemo("Thread-2-Send-Email");
        T2.start();

        System.out.println("==> Main thread stopped!!! ");
    }
}

Kết quả thực thi chương trình trên:

Main thread running...
Creating Thread-1-HR-Database
Starting Thread-1-HR-Database
Creating Thread-2-Send-Email
Starting Thread-2-Send-Email
==> Main thread stopped!!!
Running Thread-1-HR-Database
Running Thread-2-Send-Email
Thread: Thread-2-Send-Email, 4
Thread: Thread-1-HR-Database, 4
Thread: Thread-1-HR-Database, 3
Thread: Thread-2-Send-Email, 3
Thread: Thread-2-Send-Email, 2
Thread: Thread-1-HR-Database, 2
Thread: Thread-2-Send-Email, 1
Thread: Thread-1-HR-Database, 1
Thread Thread-2-Send-Email exiting.
Thread Thread-1-HR-Database exiting.

Kết quả chương trình trên được giải thích thông qua hình bên dưới:

Lập trình đa luồng trong Java (Java Multi-threading)Lập trình đa luồng trong Java (Java Multi-threading)

Ví dụ Tạo luồng bằng cách implement từ Interface Runnable

Tạo luồng implement từ Interface Runnable

package com.gpcoder.flow;

class RunnableDemo implements Runnable {
    private Thread t;
    private String threadName;

    RunnableDemo(String name) {
        threadName = name;
        System.out.println("Creating " + threadName);
    }

    @Override
    public void run() {
        System.out.println("Running " + threadName);
        try {
            for (int i = 4; i > 0; i--) {
                System.out.println("Thread: " + threadName + ", " + i);
                // Let the thread sleep for a while.
                Thread.sleep(50);
            }
        } catch (InterruptedException e) {
            System.out.println("Thread " + threadName + " interrupted.");
        }
        System.out.println("Thread " + threadName + " exiting.");
    }

    public void start() {
        System.out.println("Starting " + threadName);
        if (t == null) {
            t = new Thread(this, threadName);
            t.start();
        }
    }

}

Chương trình sử dụng đa luồng:

package com.gpcoder.flow;

public class RunnableDemoTest {
    public static void main(String args[]) {
        System.out.println("Main thread running... ");

        RunnableDemo R1 = new RunnableDemo("Thread-1-HR-Database");
        R1.start();

        RunnableDemo R2 = new RunnableDemo("Thread-2-Send-Email");
        R2.start();

        System.out.println("==> Main thread stopped!!! ");
    }
}

Kết quả thực thi chương trình trên:

Main thread running...
Creating Thread-1-HR-Database
Starting Thread-1-HR-Database
Creating Thread-2-Send-Email
Starting Thread-2-Send-Email
==> Main thread stopped!!!
Running Thread-1-HR-Database
Running Thread-2-Send-Email
Thread: Thread-1-HR-Database, 4
Thread: Thread-2-Send-Email, 4
Thread: Thread-1-HR-Database, 3
Thread: Thread-2-Send-Email, 3
Thread: Thread-1-HR-Database, 2
Thread: Thread-2-Send-Email, 2
Thread: Thread-1-HR-Database, 1
Thread: Thread-2-Send-Email, 1
Thread Thread-1-HR-Database exiting.
Thread Thread-2-Send-Email exiting.

Kết quả chương trình trên được giải thích thông qua hình bên dưới:

Lập trình đa luồng trong Java (Java Multi-threading)Lập trình đa luồng trong Java (Java Multi-threading)

Các phương thức của lớp Thread thường hay sử dụng

  • suspend() : Đây là phương thức làm tạm dừng hoạt động của 1 luồng nào đó bằng các ngưng cung cấp CPU cho luồng này. Để cung cấp lại CPU cho luồng ta sử dụng phương thức resume(). Cần lưu ý 1 điều là ta không thể dừng ngay hoạt động của luồng bằng phương thức này. Phương thức suspend() không dừng ngay tức thì hoạt động của luồng mà sau khi luồng này trả CPU về cho hệ điều hành thì không cấp CPU cho luồng nữa.
  • resume() : Đây là phương thức làm cho luồng chạy lại khi luồng bị dừng do phương thức suspend() bên trên. Phương thức này sẽ đưa luồng vào lại lịch điều phối CPU để luồng được cấp CPU chạy lại bình thường.
  • stop() : Luồng này sẽ kết thúc phương thức run() bằng cách ném ra 1 ngoại lệ ThreadDeath, điều này cũng sẽ làm luồng kết thúc 1 cách ép buộc. Nếu giả sử, trước khi gọi stop() mà luồng đang nắm giữa 1 đối tượng nào đó hoặc 1 tài nguyên nào đó mà luồng khác đang chờ thì có thể dẫn tới việc sảy ra deadlock.
  • destroy() : dừng hẳn luồng.
  • isAlive() : Phương thức này kiểm tra xem luồng còn active hay không. Phương thức sẽ trả về true nếu luồng đã được start() và chưa rơi vào trạng thái dead. Nếu phương thức trả về false thì luồng đang ở trạng thái “New Thread” hoặc là đang ở trạng thái “Dead”
  • yeild() : Hệ điều hành đa nhiệm sẽ phân phối CPU cho các tiến trình, các luồng theo vòng xoay. Mỗi luồng sẽ được cấp CPU trong 1 khoảng thời gian nhất định, sau đó trả lại CPU cho hệ điều hành (HĐH), HĐH sẽ cấp CPU cho luồng khác. Các luồng sẽ nằm chờ trong hàng đợi Ready để nhận CPU theo thứ tự. Java có cung cấp cho chúng ta 1 phương thức khá đặc biệt là yeild(), khi gọi phương thức này luồng sẽ bị ngừng cấp CPU và nhường cho luồng tiếp theo trong hàng chờ Ready. Luồng không phải ngưng cấp CPU như suspend mà chỉ ngưng cấp trong lần nhận CPU đó mà thôi.
  • sleep(long) : tạm dừng luồng trong một khoảng thời gian millisecond.
  • join() : thông báo rằng hãy chờ thread này hoàn thành rồi thread cha mới được tiếp tục chạy.
  • join(long) : Thread cha cần phải đợi millisecond mới được tiếp tục chạy, kể từ lúc gọi join(long). Nếu tham số millis = 0 nghĩa là đợi cho tới khi luồng này kết thúc.
  • getName() : Trả về tên của thread.
  • setName(String name) : Thay đổi tên của thread.
  • getId() : Trả về id của thread.
  • getState(): trả về trạng thái của thread.
  • currentThread() : Trả về tham chiếu của thread đang được thi hành.
  • getPriority() : Trả về mức độ ưu tiên của thread.
  • setPriority(int) : Thay đổi mức độ ưu tiên của thread.
  • isDaemon() : Kiểm tra nếu thread là một luồng Daemon.
  • setDaemon(boolean): xác định thread là một luồng Daemon hay không.
  • interrupt() : làm gián đoạn một luồng trong java. Nếu thread nằm trong trạng thái sleep hoặc wait, nghĩa là sleep() hoặc wait() được gọi ra. Việc gọi phương thức interrupt() trên thread đó sẽ phá vỡ trạng thái sleep hoặc wait và ném ra ngoại lệ InterruptedException. Nếu thread không ở trong trạng thái sleep hoặc wait, việc gọi phương thức interrupt() thực hiện hành vi bình thường và không làm gián đoạn thread nhưng đặt cờ interrupt thành true.
  • isInterrupted() : kiểm tra nếu thread đã bị ngắt.
  • interrupted() : kiểm tra nếu thread hiện tại đã bị ngắt.

Một số thông tin liên quan đến luồng

Định danh của luồng (ThreadId)

ThreadId là định danh của luồng, nó dùng để phân biệt với các luồng khác cùng tiến trình hoặc cùng tập luồng. Đây là thông số mà máy ảo java tự tạo ra khi ta tạo luồng nên ta không thể sửa đổi cũng như áp đặt thông số này khi tạo luồng. Nhưng ta có thể lấy được nó thông qua phương thức getId() của lớp Thread

Tên của luồng (ThreadName)

ThreadName là tên của luồng, đây là thuộc tính mà ta có thể đặt hoặc không đặt cho luồng. Nếu ta không đặt cho luồng thì máy ảo java sẽ tự đặt với quy tắc sau: “Thread-” + Thứ tự luồng được tạo ra, bắt đầu từ 0.

Độ ưu tiên của luồng (Priority)

Như đã nói ở phần trước, mỗi luồng có 1 độ ưu tiên nhất định. Đây sẽ là thông số quyết định mức ưu tiên khi cấp phát CPU cho các luồng.

Trong java, đế đặt độ ưu tiên cho 1 luồng ta dùng phương thức: void setPriority(int newPriority)

  • int newPriority : Là giá trị từ 1 đến 10.

Java có định nghĩa sẵn 3 mức ưu tiên chuẩn như sau:

  • Thread.MIN_PRIORITY (giá trị 01)
  • hread.NORM_PRIORITY (giá trị 05)
  • Thread.MAX_PRIORITY (giá trị 10)

Để lấy độ ưu tiên của 1 luồng, ta dùng phương thức: int getPriority()

Ví dụ minh họa

WorkingThread.java

package com.gpcoder.info;

public class WorkingThread extends Thread {
    public WorkingThread(String name) {
        super(name);
    }

    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.printf("Luồng: %s có độ ưu tiên là %d n", getName(), getPriority());
        }
    }
}

ThreadInfoExample.java

package com.gpcoder.info;

public class ThreadInfoExample {

    public static void main(String[] args) {
        Thread t1 = new WorkingThread("Luồng 1");
        Thread t2 = new WorkingThread("Luồng 2");
        Thread t3 = new WorkingThread("Luồng 3");

        System.out.println("ID luồng 1: " + t1.getId());
        System.out.println("ID luồng 2: " + t2.getId());
        System.out.println("ID luồng 3: " + t3.getId());

        t1.setPriority(1);
        t2.setPriority(5);
        t3.setPriority(10);

        t1.start();
        t2.start();
        t3.start();
    }

}

Kết quả thực thi chương trình trên:

ID luồng 1: 10
ID luồng 2: 11
ID luồng 3: 12
Luồng: Luồng 2 có độ ưu tiên là 5
Luồng: Luồng 2 có độ ưu tiên là 5
Luồng: Luồng 2 có độ ưu tiên là 5
Luồng: Luồng 2 có độ ưu tiên là 5
Luồng: Luồng 2 có độ ưu tiên là 5
Luồng: Luồng 1 có độ ưu tiên là 1
Luồng: Luồng 3 có độ ưu tiên là 10
Luồng: Luồng 3 có độ ưu tiên là 10
Luồng: Luồng 3 có độ ưu tiên là 10
Luồng: Luồng 3 có độ ưu tiên là 10
Luồng: Luồng 3 có độ ưu tiên là 10
Luồng: Luồng 1 có độ ưu tiên là 1
Luồng: Luồng 1 có độ ưu tiên là 1
Luồng: Luồng 1 có độ ưu tiên là 1
Luồng: Luồng 1 có độ ưu tiên là 1

Sử dụng phương thức sleep()

Phương thức sleep() của lớp Thread được sử dụng để tạm ngừng một thread cho một khoảng thời gian nhất định.

Ví dụ chương trình in ra số từ 1 – 5, tạm ngừng 500 ms trước khi in chữ số tiếp theo.

package com.gpcoder.sleep;

public class SleepMethodExample extends Thread {

    public void run() {
        for (int i = 1; i <= 5; i++) {
            System.out.println(i);
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String args[]) {
        SleepMethodExample t1 = new SleepMethodExample();
        t1.start();
    }

}

Sử dụng join() và join(long)

join() : thông báo rằng hãy chờ thread này hoàn thành rồi thread cha mới được tiếp tục chạy.

Ví dụ:

package com.gpcoder.join;

public class UsingJoinMethod extends Thread {

    public UsingJoinMethod(String name) {
        super(name);
    }

    @Override
    public void run() {
        System.out.println(getName());
        for (int i = 1; i <= 5; i++) {
            try {
                System.out.print(i + " ");
                Thread.sleep(300);
            } catch (InterruptedException ie) {
                System.out.println(ie.toString());
            }
        }
        System.out.println();
    }

    public static void main(String[] args) throws InterruptedException {
        UsingJoinMethod t1 = new UsingJoinMethod("Thread 1");
        UsingJoinMethod t2 = new UsingJoinMethod("Thread 2");
        t1.start();
        t1.join();
        t2.start();
        System.out.println("Main Thread Finished");
    }
}

Thực thi chương trình trên:

Thread 1
1 2 3 4 5
Main Thread Finished
Thread 2
1 2 3 4 5

join(long) : Thread cha cần phải đợi millisecond mới được tiếp tục chạy, kể từ lúc gọi join(long). Nếu tham số millis = 0 nghĩa là đợi cho tới khi luồng này kết thúc.

package com.gpcoder.join;

public class UsingJoinMethod2 extends Thread {

    public UsingJoinMethod2(String name) {
        super(name);
    }

    @Override
    public void run() {
        System.out.println(getName());
        for (int i = 1; i <= 5; i++) {
            try {
                System.out.print(i + " ");
                Thread.sleep(300);
            } catch (InterruptedException ie) {
                System.out.println(ie.toString());
            }
        }
        System.out.println();
    }

    public static void main(String[] args) throws InterruptedException {
        UsingJoinMethod2 t1 = new UsingJoinMethod2("Thread 1");
        UsingJoinMethod2 t2 = new UsingJoinMethod2("Thread 2");
        t1.start();

        // Main Thread phải chờ 450ms mới được tiếp tục chạy.
        // Không nhất thiết phải chờ Thread t1 kết thúc
        t1.join(450);

        t2.start();
        System.out.println("Main Thread Finished");
    }
}

Thực thi chương trình trên:

Thread 1
1 2 Main Thread Finished
Thread 2
1 3 2 4 3 5 4
5

Xử lý ngoại lệ cho Thread

Phương thức Thread.setDefaultUncaughtExceptionHandler() thiết lập mặc định xử lý khi luồng đột ngột chấm dứt do một ngoại lệ xảy ra mà không có xử lý khác đã được xác định cho luồng đó.

Ví dụ:

package com.gpcoder.exception;

import java.util.Random;

public class WorkingThread implements Runnable {

    @Override
    public void run() {
        while (true) {
            processSomething();
        }
    }

    private void processSomething() {
        try {
            System.out.println("Processing working thread");
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        Random r = new Random();
        int i = r.nextInt(100);
        if (i > 70) {
            throw new RuntimeException("Simulate an exception was not handled in the thread");
        }
    }

}

Chương trình minh họa xử lý Thread Exception

package com.gpcoder.exception;

public class ThreadExceptionDemo {

    public static void main(String[] args) {
        System.out.println("==> Main thread running...");

        Thread thread = new Thread(new WorkingThread());
        Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
            @Override
            public void uncaughtException(Thread t, Throwable e) {
                System.out.println("#Thread: " + t);
                System.out.println("#Thread exception message: " + e.getMessage());
            }
        });

        thread.start();
        System.out.println("==> Main thread end!!!");
    }
}

Thực thi chương trình trên:

==> Main thread running...
==> Main thread end!!!
Processing working thread
Processing working thread
Processing working thread
Processing working thread
Processing working thread
Processing working thread
#Thread: Thread[Thread-0,5,main]
#Thread exception message: Have a problem...

Trên đây là những kiến thức cơ bản về luồng (thread) và đa luồng (Multi-thread) trong Java. Chúng ta sẽ tiếp tục tìm hiểu về các vấn đề khác của đa luồng trong Java ở các bài viết tiếp theo. Cám ơn các bạn đã quan tâm và theo dõi bài viết.

Tài liệu tham khảo:

  • https://docs.oracle.com/javase/tutorial/essential/concurrency/
  • https://www.javatpoint.com/multithreading-in-java
  • https://o7planning.org/vi/10269/huong-dan-lap-trinh-da-luong-trong-java

Bài viết gốc được đăng tải tại gpcoder.com

Có thể bạn quan tâm:

Xem thêm các việc làm Java hấp dẫn tại Station D

Bài viết liên quan

Bộ cài đặt Laravel Installer đã hỗ trợ tích hợp Jetstream

Bộ cài đặt Laravel Installer đã hỗ trợ tích hợp Jetstream

Bài viết được sự cho phép của tác giả Chung Nguyễn Hôm nay, nhóm Laravel đã phát hành một phiên bản chính mới của “ laravel/installer ” bao gồm hỗ trợ khởi động nhanh các dự án Jetstream. Với phiên bản mới này khi bạn chạy laravel new project-name , bạn sẽ nhận được các tùy chọn Jetstream. Ví dụ: API Authentication trong Laravel-Vue SPA sử dụng Jwt-auth Cách sử dụng Laravel với Socket.IO laravel new foo --jet --dev Sau đó, nó sẽ hỏi bạn thích stack Jetstream nào hơn: Which Jetstream stack do you prefer? [0] Livewire [1] inertia > livewire Will your application use teams? (yes/no) [no]: ... Nếu bạn đã cài bộ Laravel Installer, để nâng cấp lên phiên bản mới bạn chạy lệnh: composer global update Một số trường hợp cập nhật bị thất bại, bạn hãy thử, gỡ đi và cài đặt lại nha composer global remove laravel/installer composer global require laravel/installer Bài viết gốc được đăng tải tại chungnguyen.xyz Có thể bạn quan tâm: Cài đặt Laravel Làm thế nào để chạy Sql Server Installation Center sau khi đã cài đặt xong Sql Server? Quản lý các Laravel route gọn hơn và dễ dàng hơn Xem thêm Tuyển dụng lập trình Laravel hấp dẫn trên Station D

By stationd
Principle thiết kế của các sản phẩm nổi tiếng

Principle thiết kế của các sản phẩm nổi tiếng

Tác giả: Lưu Bình An Phù hợp cho các bạn thiết kế nào ko muốn làm code dạo, design dạo nữa, bạn muốn cái gì đó cao hơn ở tầng khái niệm Nếu lập trình chúng ta có các nguyên tắc chung khi viết code như KISS , DRY , thì trong thiết kế cũng có những nguyên tắc chính khi làm việc. Những nguyên tắc này sẽ là kim chỉ nam, nếu có tranh cãi giữa các member trong team, thì cứ đè nguyên tắc này ra mà giải quyết (nghe hơi có mùi cứng nhắc, mình thì thích tùy cơ ứng biến hơn) Tìm các vị trí tuyển dụng designer lương cao cho bạn Nguyên tắc thiết kế của GOV.UK Đây là danh sách của trang GOV.UK Bắt đầu với thứ user cần Làm ít hơn Thiết kế với dữ liệu Làm mọi thứ thật dễ dàng Lặp. Rồi lặp lại lần nữa Dành cho tất cả mọi người Hiểu ngữ cảnh hiện tại Làm dịch vụ digital, không phải làm website Nhất quán, nhưng không hòa tan (phải có chất riêng với thằng khác) Cởi mở, mọi thứ tốt hơn Bao trừu tượng luôn các bạn, trang Gov.uk này cũng có câu tổng quát rất hay Thiết kế tốt là thiết kế có thể sử dụng. Phục vụ cho nhiều đối tượng sử dụng, dễ đọc nhất nhất có thể. Nếu phải từ bỏ đẹp tinh tế – thì cứ bỏ luôn . Chúng ta tạo sản phẩm cho nhu cầu sử dụng, không phải cho người hâm mộ . Chúng ta thiết kế để cả nước sử dụng, không phải những người đã từng sử dụng web. Những người cần dịch vụ của chúng ta nhất là những người đang cảm thấy khó sử dụng dịch...

By stationd
Hiểu về trình duyệt – How browsers work

Hiểu về trình duyệt – How browsers work

Bài viết được sự cho phép của vntesters.com Khi nhìn từ bên ngoài, trình duyệt web giống như một ứng dụng hiển thị những thông tin và tài nguyên từ server lên màn hình người sử dụng, nhưng để làm được công việc hiển thị đó đòi hỏi trình duyệt phải xử lý rất nhiều thông tin và nhiều tầng phía bên dưới. Việc chúng ta (Developers, Testers) tìm hiểu càng sâu tầng bên dưới để nắm được nguyên tắc hoạt động và xử lý của trình duyệt sẽ rất hữu ích trong công việc viết code, sử dụng các tài nguyên cũng như kiểm thử ứng dụng của mình. Cách để npm packages chạy trong browser Câu hỏi phỏng vấn mẹo về React: Component hay element được render trong browser? Khi hiểu được cách thức hoạt động của trình duyệt chúng ta có thể trả lời được rất nhiều câu hỏi như: Tại sao cùng một trang web lại hiển thị khác nhau trên hai trình duyệt? Tại sao chức năng này đang chạy tốt trên trình duyệt Firefox nhưng qua trình duyệt khác lại bị lỗi? Làm sao để trang web hiển thị nội dung nhanh và tối ưu hơn một chút?… Hy vọng sau bài này sẽ giúp các bạn có một cái nhìn rõ hơn cũng như giúp ích được trong công việc hiện tại. 1. Cấu trúc của một trình duyệt Trước tiên chúng ta đi qua cấu trúc, thành phần chung và cơ bản nhất của một trình duyệt web hiện đại, nó sẽ gồm các thành phần (tầng) như sau: Thành phần nằm phía trên là những thành phần gần với tương tác của người dùng, càng phía dưới thì càng sâu và nặng về xử lý dữ liệu hơn tương tác. Nhiệm...

By stationd
Thị trường EdTech Vietnam- Nhiều tiềm năng nhưng còn bị bỏ ngỏ tại Việt Nam

Thị trường EdTech Vietnam- Nhiều tiềm năng nhưng còn bị bỏ ngỏ tại Việt Nam

Lĩnh vực EdTech (ứng dụng công nghệ vào các sản phẩm giáo dục) trên toàn cầu hiện nay đã tương đối phong phú với nhiều tên tuổi lớn phân phối đều trên các hạng mục như Broad Online Learning Platforms (nền tảng cung cấp khóa học online đại chúng – tiêu biểu như Coursera, Udemy, KhanAcademy,…) Learning Management Systems (hệ thống quản lý lớp học – tiêu biểu như Schoology, Edmodo, ClassDojo,…) Next-Gen Study Tools (công cụ hỗ trợ học tập – tiểu biểu như Kahoot!, Lumosity, Curriculet,…) Tech Learning (đào tạo công nghệ – tiêu biểu như Udacity, Codecademy, PluralSight,…), Enterprise Learning (đào tạo trong doanh nghiệp – tiêu biểu như Edcast, ExecOnline, Grovo,..),… Hiện nay thị trường EdTech tại Việt Nam đã đón nhận khoảng đầu tư khoảng 55 triệu đô cho lĩnh vực này nhiều đơn vị nước ngoài đang quan tâm mạnh đến thị trường này ngày càng nhiều hơn. Là một trong những xu hướng phát triển tốt, và có doanh nghiệp đã hoạt động khá lâu trong ngành nêu tại infographic như Topica, nhưng EdTech vẫn chỉ đang trong giai đoạn sơ khai tại Việt Nam. Tại Việt Nam, hệ sinh thái EdTech trong nước vẫn còn rất non trẻ và thiếu vắng nhiều tên tuổi trong các hạng mục như Enterprise Learning (mới chỉ có MANA), School Administration (hệ thống quản lý trường học) hay Search (tìm kiếm, so sánh trường và khóa học),… Với chỉ dưới 5% số dân công sở có sử dụng một trong các dịch vụ giáo dục online, EdTech cho thấy vẫn còn một thị trường rộng lớn đang chờ được khai phá. *** Vừa qua Station D đã công bố Báo cáo Vietnam IT Landscape 2019 đem đến cái nhìn toàn cảnh về các ứng dụng công...

By stationd