1 có bao nhiêu cách để tạo thread c năm 2024

Trong Java, chúng ta có 2 cách để khởi tạo và chạy một thread, đó là sử dụng class Thread hoặc là interface Runnable. Cụ thể như thế nào, trong bài viết này, chúng ta sẽ cùng nhau tìm hiểu về chúng các bạn nhé!

Class Thread

Các bạn có thể dễ dàng tạo mới một thread bằng cách extend class của các bạn từ class Thread rồi sau đó override phương thức run() của class Thread này. Code bên trong run() method sẽ là tác vụ mà chúng ta cần execute trong thread mới.

1 có bao nhiêu cách để tạo thread c năm 2024

Ví dụ mình muốn tạo mới một thread để tính tổng của 2 số bằng cách extend class Thread thì mình sẽ tạo mới một class extend class Thread và override lại phương thức run() như sau:

package com.huongdanjava.javaexample; public class Calculator extends Thread {     @Override     public void run() {         int a = 2;         int b = 7;         int sum = a + b;         System.out.println(sum);     } }

Để chạy thread trong trường hợp này, chúng ta sẽ tạo mới instance của class Calculator và gọi phương thức start() của đối tượng này để chạy các bạn nhé. Ví dụ:

package com.huongdanjava.javaexample; public class Example { public static void main(String[] args) { Calculator calculator = new Calculator(); calculator.start(); } }

Kết quả:

1 có bao nhiêu cách để tạo thread c năm 2024

Các bạn có thể thấy rõ việc code của chúng ta được execute trong main thread hay là một thread khác bằng cách add thêm một số dòng code như sau:

package com.huongdanjava.javaexample; public class Calculator extends Thread {     @Override     public void run() {         System.out.println("Calculator : " + Thread.currentThread().getName());         int a = 2;         int b = 7;         int sum = a + b;         System.out.println(sum);     } }

package com.huongdanjava.javaexample; public class Example { public static void main(String[] args) {     System.out.println("Main : " + Thread.currentThread().getName()); Calculator calculator = new Calculator(); calculator.start(); } }

Kết quả:

1 có bao nhiêu cách để tạo thread c năm 2024

Nếu các bạn để ý thì sẽ có thắc mắc là nếu chúng ta gọi phương thức run() thay vì phương thức start() của class Calculator thì có sự khác biệt gì? Câu trả lời là nếu các bạn gọi phương thức run() thì code bên trong nó sẽ được execute trong cùng thread của chương trình thay vì một thread mới.

Ví dụ nếu bây giờ mình đổi qua gọi phương thức run() trong hàm main của ứng dụng:

package com.huongdanjava.javaexample; public class Example { public static void main(String[] args) {     System.out.println("Main : " + Thread.currentThread().getName()); Calculator calculator = new Calculator(); calculator.run(); } }

các bạn sẽ thấy kết quả như sau:

1 có bao nhiêu cách để tạo thread c năm 2024

Interface Runnable

Với interface Runnable thì class của các bạn cần implement interface này và hiện thực phương thức run() được định nghĩa trong nó.

Trong ví dụ trên, thay vì extend từ class Thread mình có thể implement interface Runnable như sau:

package com.huongdanjava.javaexample; public class Calculator implements Runnable {     public void run() {         System.out.println("Calculator : " + Thread.currentThread().getName());         int a = 2;         int b = 7;         int sum = a + b;         System.out.println(sum);     } }

Để tạo mới thread và chạy ứng dụng của chúng ta trong trường hợp này, các bạn cần tạo mới class Thread với tham số là class implement interface Runnable của chúng ta. Ví dụ như sau:

package com.huongdanjava.javaexample; public class Example { public static void main(String[] args) {     System.out.println("Main : " + Thread.currentThread().getName());     Thread thread = new Thread(new Calculator());     thread.start(); } }

Kết quả cũng như vậy:

1 có bao nhiêu cách để tạo thread c năm 2024

Tổng kết

Việc sử dụng extend class Thread hay implement interface Runnable là tuỳ vào nhu cầu của các bạn nhưng mình prefer việc implement interfacve Runnable hơn vì trong Java, chúng ta có thể implement nhiều interface khác nhau nhưng chỉ có thể extend từ một class nào đó.

Chào các bạn. Bài viết này ra đời trong bối cảnh có rất nhiều bạn quan tâm đến các bài viết về Thread và đồng bộ hóa của mình, bắt đầu từ Bài 41.

Nếu các bạn có theo dõi các bài viết này, sẽ thấy mình đã bỏ hẳn một bài viết giúp tổng hợp các phương thức hữu ích của Thread. Thật ra nếu bỏ qua việc tìm hiểu các phương thức này, cũng không gây khó khăn cho việc chúng ta đi nhanh qua các bài học sau. Nhưng như vậy thì thật là tiếc vì bản thân Thread có nhiều phương thức khá hay, nếu lúc nào đó bỗng nhiên bạn cần đến, thì việc tìm hiểu thêm về chúng cũng mất kha khá thời gian.

Và cũng nhân tiện có nhiều bạn cũng đặt các câu hỏi xoay quanh một vài phương thức được dùng nhiều, hôm nay mình viết hẳn một bài để tổng hợp lại các phương thức đó của Thread lại. Mình sẽ tập trung giải nghĩa cụ thể vào từng phương thức, có ví dụ rõ ràng, để các bạn nắm rõ hơn và để mình nhanh chóng cho ra những bài viết còn lại của chuỗi kiến thức Thread khá là đồ sộ này nhé.

Lưu ý rằng bài viết hôm nay sẽ chưa có đủ mặt các phương thức, nhưng mình sẽ cập nhật thêm sau này và sẽ để lại đường link đến bài này từ các bài viết liên quan khác.

Thread.sleep()

Một phương thức đơn giản nhưng khá nhiều bạn thắc mắc.

Giải Nghĩa

sleep() là một phương thức static của Thread. Do đó chúng ta gọi kèm với tên của lớp: Thread.sleep().

Thread.sleep() làm cho Thread hiện tại (chính là Thread đang gọi đến lệnh Thread.sleep()) phải tạm hoãn lại việc thực thi trong một khoảng thời gian được chỉ định. Nói cho đầy đủ thì là vậy, nhưng các lập trình viên hay dùng theo tên của phương thức, đó là “Ngủ”.

Tuy Thread.sleep() được nạp chồng cho phép bạn chỉ định thời gian ngủ cho Thread hiện tại tính đến nano giây. Nhưng bạn đừng có nghĩ rằng Thread.sleep() sẽ thực sự ngủ với chính xác khoảng thời gian mà bạn định nghĩa nhé. Vì việc ngủ và thức này khiến cho hệ thống phải xử lý và cho ra các độ trễ khác nhau tùy vào thiết bị phần cứng nữa. Do đó Thread.sleep() cũng chỉ tương đối, và chúng ta không bao giờ được dùng Thread.sleep() để xây dựng một chức năng hẹn giờ hay đếm ngược thời gian, điều mà đòi hỏi tính chính xác về mặt thời gian rất cao.

Sử Dụng

Thread.sleep() được nạp chồng bởi 2 phương thức.

  • Thread.sleep(long millis): xác định thời gian ngủ cho Thread, tính bằng mili giây.
  • Thread.sleep(long millis, int nanos): như trên, nhưng bạn có thể thêm vào tham số thứ hai, giúp cộng thêm thời gian ngủ tính bằng nano giây.

Khi sử dụng Thread.sleep(), bạn phải try catch phương thức này bằng một có tên là InterruptedException. Exception này sẽ được tung ra nếu như có một Thread nào đó khác interrupt Thread này (dừng Thread lại) khi Thread.sleep() đang hoạt động.

Ví Dụ

Một ví dụ đơn giản, ví dụ này sẽ tin ra console các con số từ 0 đến 5. Mỗi lần in cách nhau nửa giây (500 mili giây). Và vì chúng ta không start() bất cứ Thread nào trong đoạn code dưới, điều đó không có nghĩa rằng là hiện tại ứng dụng đang rỗng không có Thread nào đấy nhé. Bản thân phương thức main() tự nó đã được đưa vào một Thread rồi, gọi là main thread, nên bạn có thể thoải mái gọi đến Thread.sleep() ở đây.

public static void main(String[] args) {

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

join()

Giải Nghĩa

Phương thức join() cho phép một Thread phải chờ cho đến khi các Thread khác kết thúc. Vậy Thread nào phải chờ Thread nào? Vấn đề này khiến nhiều bạn bỡ ngỡ ban đầu, do chỉ có một phương thức join() thì thể hiện thế nào về việc Thread nào chờ Thread nào. Bạn chỉ cần nhớ, Thread nào sử dụng (hay khai báo) các Thread khác, mà một trong các Thread được khai báo này gọi đến phương thức join(), Thread sử dụng sẽ phải đợi Thread khai báo đó kết thúc mới làm tiếp công việc của mình.

join() cũng có các phương thức nạp chồng cho phép Thread hiện tại chỉ cần phải chờ trong một khoảng thời gian tính bằng mili giây hoặc thêm đến nano giây. Và cũng như Thread.sleep(), join() không thực sự đếm thời gian chính xác, nên bạn cũng đừng dùng join() cho một số tình huống đòi hỏi mặt khắt khe về thời gian nhé.

Sử Dụng

join() được nạp chồng bởi 3 phương thức.

  • join(): chờ đến khi Thread này kết thúc.
  • join(final long millis): chờ trong khoảng thời gian tính bằng mili giây. Nếu truyền vào đây giá trị 0, sẽ trở thành join() trên kia.
  • join(long millis, int nanos): như trên nhưng có thể cộng thêm thời gian tính bằng nano giây vào tham số thứ hai. Nếu truyền vào 2 tham số đều là 0 thì sẽ trở thành join().

Cũng giống như Thread.sleep(), sử dụng join() cũng cần phải try catch với InterruptedException. Cả hai phương thức này sẽ tung ra Exception này khi được gọi bởi lệnh interrupt.

Ví Dụ

Chúng ta hãy xây dựng một Thread đơn giản sau đây. Thread này khi chạy sẽ in ra các con số từ 0 đến 5 ở mỗi 0,5 giây.

public class MyThread extends Thread {

public MyThread(String threadName) {
    super(threadName);
}
@Override
public void run() {
    for (int i = 0; i < 5; i++) {
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(getName() + " " + i);
    }
}
}

Còn ở phương thức main() chúng ta sẽ khai báo 3 đối tượng Thread từ lớp MyThread này. Sau khi start thread1 xong thì gọi thread1.join(), sau đó chúng ta cũng sẽ start các Thread còn lại.

public static void main(String[] args) {

MyThread thread1 = new MyThread("Thread1");
MyThread thread2 = new MyThread("Thread2");
MyThread thread3 = new MyThread("Thread3");
thread1.start();
try {
    thread1.join();
} catch (InterruptedException e) {
    e.printStackTrace();
}
thread2.start();
thread3.start();
}

Kết quả in ra console như sau.

Thread1 0 Thread1 1 Thread1 2 Thread1 3 Thread1 4 Thread2 0 Thread3 0 Thread2 1 Thread3 1 Thread2 2 Thread3 2 Thread2 3 Thread3 3 Thread2 4 Thread3 4

Bạn có thể thấy, sau khi thread1.start(), thread1 này sẽ bắt đầu in các con số ra console. Tuy nhiên lời gọi thread1.join() sau đó khiến Thread đang sử dụng thread1 này (chính là main thread, thread đang chứa phương thức main() của chúng ta) rơi vào trạng thái đợi cho thread1 hoàn thành, trong quá trình đợi đó, các thread2 và thread3 khi này vẫn chưa được thực thi với hàm start(). Sau khi thread1 đếm xong và hoàn thành nhiệm vụ, các Thread còn lại mới được chạy và bắt đầu đếm là vậy.

interrupt()/isInterrupted()/Thread.interrupted()

Giải Nghĩa

Đôi khi chúng ta cần phải ngưng một Thread nào đó đang hoạt động. Cách hợp lý là gọi đến phương thức interrupt() của Thread. Lời gọi interrupt() thực chất cũng không làm cho Thread được gọi đến ngưng tác vụ ngay đâu, điều đó khá là nguy hiểm khi mà hệ thống không biết chắc Thread này đang làm việc gì, biết đâu nó đang ghi một file quan trọng nào đó. Việc một Thread khác interrupt() thoải mái một Thread nào đó có thể gây nên hậu quả khôn lường. Do đó, interrupt() không được xây dựng sẵn các lệnh giúp dừng Thread từ hệ thống, nó chỉ được xem như là một chỉ thị đưa ra cho Thread đang hoạt động biết, tốt hơn hết chính Thread đang hoạt động đó phải biết được chỉ thị này và tự biết cách ngưng tác cụ của mình lại. Một lát ở ví dụ bên dưới chúng ta sẽ nắm được điều này. Hai phương thức còn lại isInterrupted() và Thread.interrupted() dùng để kiểm tra Thread này có bị interrupt hay chưa.

Sử Dụng

  • interrupt(): đưa ra chỉ thị cho Thread rằng nó đang được gọi để chấm dứt việc thực thi.
  • isInterrupted(): kiểm tra xem Thread đó có bị chấm dứt hay chưa.
  • Thread.interrupted(): cũng như isInterrupted(), nhưng việc gọi kiểm ta với phương thức này cũng kèm với việc trả lại trạng thái ban đầu cho Thread sau đó. Nên việc gọi kiểm tra một Thread có bị chấm dứt hay chưa bởi hai lần gọi phương thức này có thể cho ra hai kết quả khác nhau.

Ví Dụ

Ví dụ lần này chúng ta hãy đi từ phương thức main(), để xem với lời gọi interrupt() như thế này thì MyThread phải làm sao là đúng nhé.

public static void main(String[] args) {

MyThread thread = new MyThread();
System.out.println("We start this Thread");
thread.start();
try {
    thread.join(2000);
} catch (InterruptedException e) {
    e.printStackTrace();
}
System.out.println("We stop this Thread");
thread.interrupt();
}

Nếu như ở MyThread, chúng ta “thờ ơ” với InterruptedException, tức là không có chuẩn bị gì cho sự interrupt cả, như sau.

public class MyThread extends Thread {

@Override
public void run() {
    for (int i = 0; i < 5; i++){
        System.out.println(i);
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
        }
    }
}
}

Kết quả in ra console sẽ như sau. Bạn thấy ở phương thức main() gọi interrupt() trong “vô vọng”, vòng lặp vẫn lặp và đếm như thường, nó chỉ kết thúc khi hoàn thành tác vụ mà thôi.

We start this Thread 0 1 We stop this Thread 2 3 4

Tuy nhiên, nếu chúng ta sửa MyThread một chút, có “trách nhiệm” hơn vớ InterruptedException, Thread này sẽ kết thúc dễ dàng do có sự chuẩn bị trước.

public class MyThread extends Thread {

@Override
public void run() {
    for (int i = 0; i < 5; i++){
        System.out.println(i);
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            System.out.println("OK, I'm stop.");
            return;
        }
    }
}
}

Console sẽ in ra như sau.

We start this Thread 0 1 We stop this Thread OK, I'm stop.

Hoặc try catch như thế này cũng cho ra kết quả viên mãn như trên.

public class MyThread extends Thread {

@Override
public void run() {
    try {
        for (int i = 0; i < 5; i++) {
            System.out.println(i);
            Thread.sleep(1000);
        }
    } catch (InterruptedException e) {
        System.out.println("OK, I'm stop.");
    }
}
}

Thread.currentThread()

Giải Nghĩa

Lời gọi Thread.currentThread() sẽ nhận về một tham chiếu đến đối tượng Thread hiện tại. Phương thức này cũng không quá phức tạp nên mình cũng không giải thích gì nhiều.

Ví Dụ

Bạn hãy nhìn vào MyRunnable sau. Trong trường hợp này thì để có thể lấy được tên của Thread đang chạy, chỉ có cách gọi đến Thread.currentThread() mà thôi.

public class MyThread extends Thread {

public MyThread(String threadName) {
    super(threadName);
}
@Override
public void run() {
    for (int i = 0; i < 5; i++) {
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(getName() + " " + i);
    }
}
}

0

Phương thức main() như sau.

public class MyThread extends Thread {

public MyThread(String threadName) {
    super(threadName);
}
@Override
public void run() {
    for (int i = 0; i < 5; i++) {
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(getName() + " " + i);
    }
}
}

1

Kết quả in ra console.

public class MyThread extends Thread {

public MyThread(String threadName) {
    super(threadName);
}
@Override
public void run() {
    for (int i = 0; i < 5; i++) {
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(getName() + " " + i);
    }
}
}

2

getName()/setName()

Giải Nghĩa

Mặc định thì các Thread khi khởi chạy trong ứng dụng, sẽ được hệ thống đặt cho một cái tên, tuần tự, như sau: Thread-0, Thread-1,….

Nếu bạn thấy các tên này rất khó để gợi nhớ, hãy đặt cho chúng một cái tên khác theo ý bạn.

Sử Dụng

  • getName(): trả về tên của Thread.
  • setName(String name): đặt một tên mới cho Thread.

Ví Dụ

Như bao ví dụ khác, chúng ta cần một Thread khá đơn giản như sau. Thread này sẽ in ra các con số từ 0 đến 5 ở mỗi 0,5 giây. Tuy nhiên Thread có kèm theo phương thức getName() để hiển thị tên của nó ra console.

public class MyThread extends Thread {

public MyThread(String threadName) {
    super(threadName);
}
@Override
public void run() {
    for (int i = 0; i < 5; i++) {
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(getName() + " " + i);
    }
}
}

3

Ở main() chúng ta sẽ start Thread với cái tên mặc định trước. Đợi Thread này chạy 1 giây sau rồi đổi tên cho nó.