STDIO
Tìm kiếm gần đây
    • Nội dung
    • QR Code
    • 0
    • 0
    • Sao chép

    Lập Trình Multithreading trong Ngôn Ngữ Lập Trình Java

    Để đáp ứng được yêu cầu thực hiện được nhiều tác vụ cùng một lúc, Java cung cấp multithreading, mỗi tác vụ riêng được hiểu như 1 thread và thread này thực hiện một công việc được chỉ định.
    19/11/2015
    02/10/2020
    9 phút đọc
    Lập Trình Multithreading trong Ngôn Ngữ Lập Trình Java

    Giới thiệu

    Phần cứng ngày càng phát triển, từ đơn nhân thành đa nhân, từ máy tính chỉ chạy được 1 chương trình tại 1 thời điểm (đơn nhiệm) đã có thể chạy nhiều chương trình 1 lúc (đa nhiệm), cùng lướt web, nghe nhạc, chơi game - multi-process.

    Trong phạm vi nhỏ hơn của 1 chương trình cũng có những thay đổi tương tự để chương trình vừa có thể vẽ, phát âm thanh và tải dữ liệu đồng thời, đó chính là multi-thread.

    Process và Thread

    Process

    Process (tiểu trình) được hiểu là 1 chương trình đang chạy, ví dụ đọc bài viết này cần trình duyệt web - đây là 1 process và process này sẽ có ID (Process IDentifier) để phân biệt với các process khác .

    Tất cả các process đều được quản lý bởi hệ điều hành và mỗi process có 1 vùng nhớ làm việc riêng mà các process khác không được can thiệp vào.

    Các process này có thể chạy song song với nhau. Về bản chất thì khái niệm song song được hiểu bởi con người, đối với máy tính tại 1 thời điểm CPU chỉ đáp ứng được 1 process. Những process được hệ điều hành lập lịch Scheduling (phân phối phần cứng cho mỗi process) sao cho các process sử dụng CPU hiệu quả. Thời gian này quá nhanh đối với con người nên người dùng cảm thấy các process được chạy đồng thời.

    Tìm hiểu các giải thuật định thời với các giải thuật như First Come First Served (FCFS) Scheduling, Shortest-Job-First (SJF) Scheduling,  Priority Scheduling, Round Robin(RR) Scheduling.

    Xem các process đang chạy trên Windows

    Mở chương trình cmd và gõ tasklist để hiển thị danh sách các process đang chạy.

    tasklist

    Và dưới đây là hình ảnh các process đang chạy.

    tasklist trên Windows.

    Hoặc có thể mở Task Manager để xem các process đang chạy

    Task Manager trên Windows.

    Thread

    Thread thường được nhắc tới với các tên là tiểu trình, luồng, tuyến.

    Trong 1 process thường có nhiều thread chạy song song với nhau, các thread sử dụng chung vùng nhớ của Process. Khi 1 chương trình được start hay là process start thì luôn luôn có 1 thread được tạo và thread này được gọi là Main Thread. Từ Main Thread có thể tạo ra các thread khác để xử lý những công việc riêng.

    Hình ảnh mô tả Process và Thread

    Process và Thread
    Process

    Tại sao phải cần đến Thread?

    Trong ứng dụng có những công việc tốn khá nhiều thời gian. Ví dụ:

    • Giao tiếp network: nếu download file hay reques server chờ trả về kết quả thì tốn khá nhiều thời gian.
    • Đọc ghi file: chi phí đọc ghi file là khá lớn.

    Nếu thực hiện những công việc này trên Main Thread thì những công việc khác phải chờ, sau khi hoàn thành công việc này mới tiếp tục thực việc công việc khác. Việc chờ như vậy đôi khi sẽ khiến ứng dụng bị "đơ" 1 thời gian, chẳng hạn như UI bị đóng băng.

    Giải pháp cho vấn đề trên là tạo ra Thread khác để thực hiện những công việc khác nhau, chạy song song với Main Thread, để ứng dụng của chúng ta đạt hiệu quả cao về xử lý lẫn trải nghiệm người dùng.

    Multilthreading trong Java

    Để tạo Thread trong Java có hai cách:

    1. Tạo class kế thừa từ class Thread.
    2. Implements interface Runnable.

    Override lại phương thức run() trong class Threadinterface Runnable, các công việc cần chạy trong thread sẽ viết trong phương thức run() này.

    Cách 1: kế thừa lớp Thread

    Đầu tiên tạo 1 class kế thừa lớp Thread và override lại phương thức run()

    package com.nguyennghia.demothreading;
    
    public class MyThread extends Thread {
    	@Override
    	public void run() {
    		for(int i = 0; i < 50; i++){
    			System.out.print("X");
    		}
    	}
    }
    

    Khi cần chạy thread này, tiến hành tạo đối tượng MyThread và gọi phương thức start().

    package com.nguyennghia.demothreading;
    
    public class Program {
    	public static void main(String[] args) {
    		MyThread myThread = new MyThread();
    		myThread.start(); // call start() method to run thread
    		
    		for(int i = 0; i < 50; i++){
    			System.out.print("Y");
    		}
    	}
    
    }
    

    Chạy chương trình sẽ thấy XY được in ra không theo trật tự nào vì myThread chạy song song với Main Thread, mỗi lần chạy sẽ thấy các kết quả khác nhau:

    YYYYYYYYYYYYYYXXXXXYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY

    Cách 2: Implements interface Runnable

    Đầu tiên tạo 1 class implements interface Runable

    package com.nguyennghia.demothreading;
    
    public class MyRunnale implements Runnable {
    	@Override
    	public void run() {
    		for(int i = 0; i < 50; i++){
    			System.out.print("X");
    		}
    	}
    }
    

    Khi cần chạy thread này, tiến hành tạo đối tượng MyRunnable và truyền vào constructor của Thread, sau đó gọi phương thức start() của Thread.

    package com.nguyennghia.demothreading;
    
    public class Program {
    	public static void main(String[] args) {
    		Thread myThread = new Thread(new MyRunnale());
    		myThread.start();
    		
    		for(int i = 0; i < 50; i++){
    			System.out.print("Y");
    		}
    	}
    }
    

    Kết quả sẽ xuất ra tương tự như cách 1.

    Ngoài ra có thể implement trực tiếp Runnable tại thời điểm truyền vào constructor của Thread như bên dưới nếu code xử lý không quá phức tạp.

    package com.nguyennghia.demothreading;
    
    public class Program {
    	public static void main(String[] args) {
    		Thread myThread = new Thread(new Runnable() {
    			@Override
    			public void run() {
    				for(int i = 0; i < 50; i++){
    					System.out.print("X");
    				}
    			}
    		});
    		myThread.start();
    	
    		for(int i = 0; i < 50; i++){
    			System.out.print("Y");
    		}
    	}
    
    }
    

    Hình ảnh mô phỏng Main Thread và Worker Thread (thread con) trong cả hai cách trên.

    Main Thread và Worker Thread

    Các thông tin của Thread

    ThreadID

    Khi 1 Thread được tạo ra sẽ có 1 ID do máy ảo Java cung cấp, không thể thay đổi giá trị ID này.

    Phương thức getID() cho phép lấy ID của thread

    myThread.getId(); //get id of myThread

    ThreadName

    Thread cũng sẽ có 1 tên đại diện cho thread đó, có thể gán tên cho thread sử dụng phương thức setName().

    myThread.setName("ThreadName");

    Và phương thức getName() để lấy tên của thread

    myThread.getName();
    

    ThreadPriority

    Các thread khi tạo ra có độ ưu tiên, độ ưu tiên này sẽ được CPU sử dụng để lập lịch cho Thread. Priority có giá trị từ 0 đến 10. Các giá trị phổ biến được định nghĩa sẵn trong lớp Thread là:

    public final static int MAX_PRIORITY = 10; // The maximum priority value allowed for a thread.
    public final static int MIN_PRIORITY = 1; // The minimum priority value allowed for a thread.
    public final static int NORM_PRIORITY = 5; // The normal (default) priority value assigned to threads.

    Sử dụng phương thức setPriority() để cấu hình priority cho thread và getPriority() lấy giá trị priority của thread.

    myThread.setPriority(Thread.MAX_PRIORITY);
    myThread.getPriority();

    Mặc định khi thread được tạo sẽ có giá trị priority là NORM_PRIORITY.

    ThreadState

    1 thread có các state, trạng thái dưới đây:

    • NEW: thread đã được khởi tạo nhưng chưa chạy.
    • RUNNABLE: thread đang chạy.
    • BLOCKED: thread bị chặn, trạng thái này xảy ra khi thread tiến hành truy cập vào vùng dữ liệu dùng chung nhưng tại thời điểm đó có một thread khác đang trong vùng này.
    • WAITING: thread trong trạng thái chờ tín hiệu từ thread khác, xảy ra khi gọi Object.wati() hoặc Thread.join(). Trạng thái này kết thúc khi một thread khác gọi phương thức Object.notify().
    • TIMED_WAITING: tương tự như trạng thái WAITING, nhưng trong một khoảng thời gian xác định.
    • TERMINATED: thread đã kết thúc.

    Đụng độ giữa các Thread và cách giải quyết

    Ccác thread tạo ra sẽ dùng chung 1 vùng nhớ, nếu các process này cùng truy cập vào 1 vùng nhớ tại cùng 1 thời điểm thì dẫn đến sai, mất dữ liệu. 

    Để khắc phục điều này, Java cung cấp synchronized để đồng bộ các thread khi sử dụng chung vùng nhớ chia sẻ (shared memory).

    Mội khối synchronized đánh dấu 1 phương thức hay 1 khối mã được đồng bộ tránh xung đột giữa các thread.

    Khi 1 thread can thiệt vào phương thức hay khối mã được đánh dấu là synchronized thì thread này sẽ khóa (lock) không cho các thread khác can thiệp vào cho đến khi thread này thực hiện xong thì mới đánh thức các thread khác. Và như vậy tại 1 thời điểm chỉ có 1 thread truy cập vào vùng nhớ được chia sẻ.

    Tạo class ShareMemory với phương thức là printData() đại diện cho dữ liệu dùng chung cho nhiều thread.

    package com.nguyennghia.demothreading;
    
    public class ShareMemory {
        public void printData(String threadName) {
            for(int i = 0; i < 50; i++) {
                System.out.println(threadName + ": " + i);
            }
        }
    }
    
    

    Tiến hành tạo 3 thread để cùng truy cập vào phương thức printData của đối tượng ShareMemory.

    package com.nguyennghia.demothreading;
    
    public class MyThread extends Thread {
    	private ShareMemory mShareMemory;
    	private String mThreadName;
    	
    	public MyThread(ShareMemory sm, String threadName) {
    		this.mShareMemory = sm;
    		this.mThreadName = threadName;
    	}
    	
    	@Override
    	public void run() {
    		mShareMemory.printData(mThreadName);
    	}
    }
    

    Hàm Main:

    public class Program {
    
    	public static void main(String[] args) {
    		ShareMemory sm = new ShareMemory();
    		MyThread thread1 = new MyThread(sm, "Thread1");
    		MyThread thread2 = new MyThread(sm, "Thread2");
    		MyThread thread3 = new MyThread(sm, "Thread3");
    
    		thread1.start();
    		thread2.start();
    		thread3.start();
    	}
    }

    Chạy và xem kết quả

    Thread1: 0
    Thread3: 0
    Thread2: 0
    Thread3: 1
    Thread1: 1
    Thread3: 2
    Thread3: 3
    Thread2: 1
    Thread3: 4
    Thread1: 2
    Thread3: 5
    Thread2: 2
    Thread3: 6
    Thread1: 3
    Thread3: 7
    Thread2: 3
    Thread3: 8
    Thread1: 4
    Thread3: 9
    Thread2: 4
    Thread3: 10
    .
    .
    .
    

    Cả 3 thread đều truy cập vào 1 tài nguyên trong khi thread này vẫn nắm giữ.

    Để đồng bộ, thêm từ khóa synchronized vào trước phương thức printData()

    package com.nguyennghia.demothreading;
    
    public class ShareMemory {
    	public synchronized void printData(String threadName){
    		for(int i = 0; i < 50; i++){
    			System.out.println(threadName + ": " + i);
    		}
    	}
    }
    

    Xem lại kết quả

    Thread1: 0
    Thread1: 1
    Thread1: 2
    Thread1: 3
    Thread1: 4
    Thread1: 5
    Thread1: 6
    Thread1: 7
    Thread1: 8
    Thread1: 9
    Thread1: 10
    .
    .
    .
    Thread3: 0
    Thread3: 1
    Thread3: 2
    Thread3: 3
    Thread3: 4
    Thread3: 5
    Thread3: 6
    Thread3: 7
    Thread3: 8
    Thread3: 9
    Thread3: 10
    .
    .
    .
    Thread2: 0
    Thread2: 1
    Thread2: 2
    Thread2: 3
    Thread2: 4
    Thread2: 5
    Thread2: 6
    Thread2: 7
    Thread2: 8
    Thread2: 9
    Thread2: 10
    

    thread1 sẽ được giữ tài nguyên và khóa không cho thread2thread3 truy cập. Sau khi thread1 thực hiện xong sẽ đánh thức thread2thread3, lúc này thread3 sẽ được giữ tài nguyên và khóa không cho thread2 vào. Sau khi thực hiện xong sẽ đánh thức thread3 thực hiện. Như vậy tại 1 thời điểm chỉ có 1 thread được can thiệp vào vùng nhớ chia sẻ.

    Với từ khóa synchronized mà ngôn ngữ Java cung cấp có thể đồng bộ hóa giữa các thread.

    0 Bình luận
    Java

    Java

    Tổng hợp các bài viết Java hữu ích

    Đề xuất

    Tổng Quan về Ngôn Ngữ Lập Trình Java
    Java là 1 trong những ngôn ngữ lập trình mạnh mẽ, được sử dụng rộng rãi ...
    Các Ngôn Ngữ Lập Trình Game
    Bài viết giới thiệu các ngôn ngữ lập trình game cho lập trình viên như ...
    03/07/2020

    Khám phá

    Thế nào là Ngôn ngữ Lập trình?
    Tìm hiểu cách máy tính làm việc của máy tính thông qua ngôn ngữ lập ...
    25/08/2015
    Những Ngôn Ngữ Lập Trình Phổ Biến
    Giới thiệu các ngôn ngữ lập trình đáng để học và phục vụ công việc, xây ...
    String trong Java
    Giới thiệu về kiểu chuỗi và kỹ thuật thao tác với chuỗi trong ngôn ngữ ...
    06/05/2015
    Tổng Quan Về Ngôn Ngữ Lập Trình JavaScript
    Tìm hiểu tổng quan ngôn ngữ lập trình JavaScript và tầm quan trọng của ...
    14/11/2015
    Cyber Security - Khám Phá Thế Giới An Ninh Mạng và Ngôn Ngữ Lập Trình
    Tìm hiểu những ngôn ngữ lập trình hữu ích nhất dành cho mạng máy tính và ...
    Biến Trong Ngôn Ngữ Lập Trình JavaScript
    Biến là một khái niệm khá quen thuộc trong mọi ngôn ngữ lập trình. Nhắc ...
    15/11/2015
    Lập Trình Hướng Đối Tượng Trong Python  - Phần 1: Cơ Bản
    Đặc điểm và cách hiện thực lập trình hướng đối tượng trong Python.
    29/03/2015
    C# - Vì Sao Lại Chọn C Sharp
    Ngôn ngữ lập trình C# được xây dựng bởi Microsoft có thiết kế mạnh mẽ có ...
    Khi bạn nhấn vào liên kết sản phẩm do STDIO đề xuất và mua hàng, STDIO có thể nhận được hoa hồng. Điều này hỗ trợ STDIO tạo thêm nhiều nội dung hữu ích. Tìm hiểu thêm.
    STDIO
    Trang chính
    Công ty TNHH STDIO

    30, Trịnh Đình Thảo, Hòa Thạnh, Tân Phú, Hồ Chí Minh
    +84 28.36205514 - +84 942.111912
    developer@stdio.vn

    383/1 Quang Trung, Phường 10, Quận Gò Vấp, Hồ Chí Minh
    Số giấy phép ĐKKD: 0311563559 do sở Kế hoạch và Đầu Tư TPHCM cấp ngày 23/02/2012

    ©STDIO, 2013 - 2020