Thread in Java
Người viết: Ngô Công Tuyền - Học viên lớp Java 09
Email: tuyenemotion@gmail.com

1.Thế nào là một Thread trong Java.

Một Thread (luồng) là một hướng đi hay một đường dẫn đã được thực hiện, khi chương trình được thực hiện khi mà chương trình được thực thi. Nói chung, tất cả các chương trình có ít nhất một luồng, được biết như là luồng chính, nó được cung cấp bởi JVM (Java Vitural Machine) vào lúc bất đầu thực hiện chương trình. Vào thời điểm đó, khi luồng chính được cung cấp, phương thức main() được gọi bởi luồng chính.
Đơn luồng khi một luồng được thực thi trong chương trình. Đa luồng thực thi có thể được chạy đồng thời bởi một ứng trên máy ảo java (JVM). Mức độ ưu tiên của mỗi luồng là khác nhau. Một luồng được ưu tiên hơn sẽ thực thi trước các luồng khác.
Thread là rất quan trọng trong một chương trình bởi vì nó cho phép nhiều hoạt động diễn ra trên một phương thức duy nhất. Mỗi luồng sẽ thường có bộ đếm chương trình (program counter), ngăn xếp và biến cục bộ của riêng nó.

2.Cách tạo một luồng trong Java.

Một luồng trong Java có thể được tạo ra theo 2 cách dưới đây:

2.1. Kế thừa lớp java.lang.Thread.

Trong trường hợp này, một thread sẽ được tạo bởi mộ lớp mới kế thừa lớp Thread, tạo ra một thực thể cho lớp này. Phương thức run() bao gồm những chức năng sẽ được thực hiện bởi luồng này.
Dưới đây là một ví dụ tạo một luồng từ việc kế thừa lớp java.lang.Thread:

public class MyThread extends Thread {
    public void run(){
        System.out.println("Thread is running...");
    }
    public static void main(String[] args) {
        MyThread thread = new MyThread();
        thread.start();
    }
}

Output:
Thread is running...
Ở đây, phương thức start() đã tạo một luồng mới và làm cho nó có khả năng chạy được. Luồng mới được bắt đầu bên trong phương thức run().

2.2. Thực hiện Runnable interface.

Đây là phương pháp dễ dàng hơn trong 2 cách tạo ra luồng. Trong trường hợp này, một lớp sẽ phải thực hiện runable interface và sau đó là phương thức run().
Các mã code chúng ta muốn thực hiện trong luồng này phải được để trong phương thức run().
Dưới đây là code ví dụ:

public class MyThreadAndRunnable implements Runnable{
    @Override
    public void run() {
        System.out.println("My thread implements runnable is running...");
    }

    public static void main(String[] args) {
        Thread thread = new Thread(new MyThreadAndRunnable());
        thread.start();
    }
}

Output:
My thread implements runnable is running...
Khi phương thức start được dùng nó sẽ gọi đến phương thức run(). Khi start() được gọi, một stack mới sẽ được cung cấp cho luồng này và khi run() được gọi nó sẽ tạo ra một luồng mới trong chương trình.

3.Vòng đời của một luồng trong Java.

Vòng đời của một luồng trong Java đề cập tới quá trình biến đổi trang thái của luồng, bắt đâu khi nó được tạo ra và kết thúc khi nó chết. Một luồng được tạo ra và thực thi khi gọi phương thức start() của lớp Thread đó, một luồng sẽ được đưa vào trạng thái có thể chạy. Khi chúng ta gọi phương thức sleep() hoặc wait() của nó, nó sẽ vào chế độ không thể chạy được.
Luồng có thể chuyển đổi từ trạng thái không chạy được sang trạng thái chạy được. Một luồng chết khi nó thoát khỏi quá trình chạy. Trong Java, việc chuyển đổi trạng thái của luồng được biết đến như vòng đời của luồng.
Có 5 trạng thái cơ bản trong một vòng đời của luồng:
1. New
2. Runable.
3. Running
4. Blocked (non-runnable)
5. Dead.
Lifecycle of a Thread in Java

3.1 Trạng thái ban đầu (new state).

Khi chúng ta dùng lớp Thread để tạo ra một đối tượng luồng, một luồng được sinh ra và nó được định nghĩa ở trạng thái New. Thực tế, khi một luồng được tạo nó được đưa vào trạng thái New, nhưng khi phương thức start() vẫn chưa được gọi.

3.2 Trạng thái có thể chạy (runnable).

Một luồng trong trạng thái runnale được chuyển bị để thực thi code. Khi phương thức start() của một luồng mới được gọi, nó sẽ được đưa và trạng thái runnable.
Trong môi trường runnable, luồng này sẵn sàng để thực thi và chỉ đợi bộ xử lý sẵn sàng (CPU time). Có nghĩa là luồng này đã vào hàng đợi cho việc thực thi.

3.3 Trạng thái chạy (running state)

Trạng thái này ngụ ý rằng bộ xử lý (CPU) chỉ định phiên cho luồng để thực thi. Sau khi một luồng ở trạng thái chuẩn bị được chọn để được thực thi bởi thread scheduler, nó tiến đến trạng thái chạy.
Trong trạng thái chạy, bộ xử lý phân bổ thời gian để luồng thực thi và chạy các tác vụ của nó. Đây là trạng thái mà luồng trực tiếp thực thi các hoạt động của nó. Chỉ khi luồng vào trang thái runnable thì nó mới có thể tiến đến trạng thái running.

3.4. Trạng thái đóng (block state).

Khi đó, luồng vẫn còn sống, đối tượng thread vẫn tồn tại, nhưng nó không thể được chọn để thựu thi bởi scheduler. Nó đang trong trạng thái không hoạt động.

3.5. Trạng thái chết (dead state)

Khi hàm run() kết thúc việc thực thi các câu lệnh, nó tự động chết hoặc được đưa vào trạng thái chết. Điều này có nghĩa là khi một luồng thực hiện xong hàm run(), nó sẽ bị chấm dứt hoặc bị hủy. Hoặc khi gọi hàm stop(), luồng cũng sẽ kết thúc.

4.Các mức độ ưu tiên của Thread trong Java.

Số lượng dich vụ được gán cho luồng được biết đến như mức độ ưu tiên của luồng đó. Các luồng thông thường trong JVM đều được gán cho một mức độ ưu tiên riêng. Thang điểm độ ưu tiên từ 1 đến 10.
* 1 là mức độ ưu tiên thấp nhất.
* 5 là mức độ ưu tiên bình thường.
* 10 là mức độ ưu tiên cao nhất.
Luồng chính được thiết lập với mức độ ưu tiên bằng 5. Mỗi luồng con có cùng mức độ ưu tiên với luồng cha. Chúng ta có thể điều chỉnh mức độ ưu tiên cho bất kỹ luồng nào cho dù đó là luồng chính hay luồng do người dùng tự định nghĩa. Chúng ta nên điều chỉnh mức độ ưu tiên bằng cách sử dụng các hằng số của lớp Thread như sau:
1. Thread.MIN_PRIORITY;
2. Thread.NORM_PRIORITY;
3. Thread.MAX_PRIORITY;
Ví dụ sau đây sẽ giúp chúng ta hiểu thêm về mức độ ưu tiên của luồng:

public class ThreadPriority extends Thread{

    public void run() {
        System.out.println("running thread priority is: "+ Thread.currentThread().getPriority());
    }

    public static void main(String[] args) {
        Thread thread1 = new ThreadPriority();
        Thread thread2 = new ThreadPriority();
        Thread thread3 = new ThreadPriority();
        thread1.setPriority(Thread.MIN_PRIORITY);
        thread2.setPriority(Thread.MAX_PRIORITY);
        thread3.setPriority(ThreadPriority.NORM_PRIORITY);
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

Output:

running thread priority is: 10
running thread priority is: 5
running thread priority is: 1

5.Các contructors thường gặp trong lớp thread.

Chúng ta hãy xem xét đến việc tạo các luồng theo các cách khác nhau thông qua constructor, kế thừa lớp Thread hoặc Runnable interface.

  • Thread().
    Gọi constructor mặc định Thread() để tạo đối tượng từ lớp Thread.
package constructor;

public class ThreadNoArgs {
    public static void main(String[] args) {
        Thread thread = new Thread();
        thread.start();
        System.out.println("Thread has been created with name: "+ thread.getName());
    }
}

Output:
Thread has been created with name: Thread-0

  • Thread(String str)
    Tạo một đối tượng Thread với tên là str.
package constructor;

public class ThreadString extends Thread{

    public static void main(String[] args) {
        Thread thread = new Thread("My thread name");
        thread.start();
        System.out.println("Thread has been created with name: "+ thread.getName());
    }
}

Output:
Thread has been created with name: My thread name

  • Thread(Runnable r)
    Với loại constructor này, một tham chiếu Runnable được truyền vào khi tạo đối tượng Thread mới:
package constructor;

public class ThreadRunnable implements Runnable{
    @Override
    public void run() {
        System.out.println("Do something");
    }

    public static void main(String[] args) {
        Thread thread = new Thread(new ThreadRunnable());
        System.out.println("Thread has been created with name: "+ thread.getName());
        thread.start();
    }
}

Output:

Thread has been created with name: Thread-0
Do something
  • Thread(Runnable r, String name)
    Tạo đối tượng được truyền tham trị r kiểu Runnable và có tên là name:
package constructor;

public class ThreadRunnableString implements Runnable{
    @Override
    public void run() {
        System.out.println("Do something new");
    }
    public static void main(String[] args) {
        Thread thread = new Thread(new ThreadRunnableString(),"New Thread Name");
        System.out.println("Thread has been created with name: "+ thread.getName());
        thread.start();
    }
}

Output:

Thread has been created with name: New Thread Name
Do something new

6.Đa luồng trong Java.

Trong Java, đa luồng là phương pháp chạy 2 hay nhiều luồng cùng lúc nhằm tối ưu hóa việc sử dụng bộ vi xử lý (CPU). Các luồng chạy song song với nhau. Vì một số luồng có thể dùng chung một vùng nhớ vì thế nó tiết kiệm bộ nhớ hơn. Hơn nữa, việc chuyển đổi giữa các luồng tốn ít thời gian hơn.
Trong Java, đa luồng làm tăng cường cấu trúc của chương trình bằng việc làm nó đơn giản và dễ dàng hơn để điều hướng. Các luồng tổng quát này có thể được sử dụng trong các ứng dụng trên máy chủ bậc cao để dễ dàng thay đổi hoặc tăng cường cấu hình của các cấu trúc phức tạp này
Multithreading

7.Cách xử lý Thread Deadlock.

Một deadlock là trường hợp hai hay nhiều luồng chờ nhau vô hạn. Một số luồng cần đồng thời bị khóa, nhưng thời điểm kết thúc của chúng được sắp xếp khác nhau, điều đó dẫn tới một vòng lặp vô tận. Trong một chương trình Java đa luồng, một deadlock có thể xảy ra, bởi vì từ khóa dẫn tới luồng đang thực thi bị đóng trong khi chờ đối tượng liên kết được chỉ định hoàn thành.
Để tránh deadlock, chúng ta phải đảm bảo tất cả các lock phải có mặt đúng thứ tự trong hàng chờ.
Một ví dụ về deadlock:

package deadlock;

import constructor.ThreadRunnable;

public class Person {
    private final String name;

    public Person(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public synchronized void call(Person listener) {
        System.out.println(this.name+" call to " + listener.getName());
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        listener.listen(this);
    }
    public synchronized void listen(Person caller){
        System.out.println(this.name+ " listen "+ caller.getName());
    }

    public static void main(String[] args) {
        Person person1 = new Person("Person 1");
        Person person2 = new Person("Person 2");
        new Thread(new Runnable() {
            @Override
            public void run() {
                person1.call(person2);
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                person2.call(person1);
            }
        }).start();

    }
}

Non-stop application output:

Person 1 call to Person 2
Person 2 call to Person 1

Deadlock in Java
Ở đây, luồng 1: person1 -> call(person2) — person2.listen(person1);
luồng 2: person2 -> call(person1) — person1.listen(person2);
Hiểu đơn giản là cả hai người đều gọi thì không có ai nghe cả, và cả luồng 1 và luồng 2 đều trong quá trình đợi người nghe.
Link tham khảo: