ĐA LUỒNG

Multithreading

duytrung.tcu@gmail.com

Nội dung bài học

• Thread

• Multithreading

• Vòng đời của thread

• Xếp lịch chạy cho thread

• Thread safe

• Deadlock

• SwingWorker

duytrung.tcu@gmail.com

• Lock và synchronized

Thread là gì

• Thread /θred/

luồng logic tuần tự các lệnh chương trình, với một điểm bắt đầu và một điểm kết thúc

• Thread là một tiến trình hạng nhẹ (lightweight process), là

• Trong vòng đời của mình, thread chỉ được thực thi một lần duy

nhất

• Bản thân thread không phải là một chương trình, nó không chạy

duytrung.tcu@gmail.com

độc lập mà nằm trong một chương trình hoàn chỉnh

Thread là gì

• Một chương trình có thể là đơn luồng (single-thread) hoặc đa

• Đơn luồng: 1 điểm vào và 1 điểm ra

luồng (multi-thread)

• Đa luồng: 1 điểm bắt đầu ở main(), sau đó là nhiều điểm vào

duytrung.tcu@gmail.com

và nhiều điểm ra chạy song hành với main()

Đa nhiệm (Multitasking / Multi-processing)

• Đa số các HĐH hiện nay là đa nhiệm

nguyên: CPU, bộ nhớ, các kênh vào ra…

• Thực hiện đồng thời nhiều công việc dựa trên chia sẻ tài

• Với CPU đơn nhân: chỉ một tác vụ được thực hiện tại một thời

duytrung.tcu@gmail.com

điểm, xếp lịch trên các khe thời gian (time slice)

Đa nhiệm (Multitasking)

• Với CPU đa nhân: nhiều tác vụ có thể chạy song song trên các

• Về cơ bản, có 2 dạng đa nhiệm:

CPU khác nhau

1. Đa nhiệm hợp tác: mỗi tác vụ sử dụng tài nguyên hệ thống cho đến khi thực hiện xong thì nhường cho tác vụ khác

2. Đa nhiệm ưu tiên: mỗi tác vụ được cấp một khe thời gian,

duytrung.tcu@gmail.com

nhường điều khiển cho tác vụ khác khi dùng hết tài nguyên của mình

Đa luồng (Multithreading)

• Xét trong một tiến trình (process) hay chương trình (program)

• Một tiến trình có bộ nhớ lệnh và các khối điều khiển riêng

• Tiến trình có thể chạy nhiều luồng để nâng cao hiệu quả

tài nguyên được cấp cho tiến trình

• Các luồng chạy trong ngữ cảnh của tiến trình, cùng chia sẻ các

duytrung.tcu@gmail.com

• Các luồng chỉ chạy trong ngữ cảnh cụ thể của tiến trình, khi chạy các luồng sử dụng stack, thanh ghi và bộ đếm chương trình của riêng mình

Tại sao sử dụng thread

 Khi một luồng đang bị đình chỉ, do đang chờ đọc dữ liệu từ một cổng vào/ra, luồng khác có thể sử dụng CPU để tính toán, mang lại kết quả thực hiện tốt hơn

• Tận dụng tối ưu hơn tài nguyên của hệ thống

sử dụng

 Khi soạn thảo văn bản, một thread sẽ đảm nhiệm khi ta ấn lưu file,

trong quá trình lưu thread khác tiếp tục thực hiện cho người dùng nhập văn bản -> giao diện người dùng đáp ứng tốt – Responsive UI

duytrung.tcu@gmail.com

• Giúp nâng cao hiệu quả tương tác của chương trình với người

Minh họa: Unresponsive UI

• Giao diện người dùng đáp ứng kém

• Chương trình đếm:

- Đếm từ 1 đến 100.000.000, hiển thị số đếm lên JTextField

duytrung.tcu@gmail.com

- chạy đếm khi ấn “Start”, dừng đếm khi ấn “Stop”. Các handler cho hai nút xử lý thông qua 1 cờ tên là stopFlag kiểu Boolean: khởi tạo là false, đặt true khi ấn “Stop”, false khi ấn “Start”

Minh họa: Unresponsive UI

btnStart.addActionListener(new ActionListener() {

btnStop.addActionListener(new ActionListener() {

@Override

@Override

public void actionPerformed(ActionEvent e) {

public void actionPerformed(ActionEvent evt) {

stopFlag = false;

stop = true; // set the stop flag

for(int i=0; i<10000000;++i)

}

{

});

if(stopFlag) break;

tfCount.setText(count + "");

++count;

}

không thay đổi giá trị??

}

});

duytrung.tcu@gmail.com

Tạo thread

• Trong Java, thread là đối tượng

• Tạo thread mới có thể sử dụng 2 cơ chế sau:

1

2

class

interface

extends

implements

duytrung.tcu@gmail.com

Runnable (java.lang.Runnable)

• Là interface mà các lớp có đặc tính thực thi được bởi các luồng

• Tạo ra một khuôn mẫu chung cho các đối tượng chạy đồng thời

cần kế nhiệm (implements)

AsyncBoxView.ChildState, ForkJoinWorkerThread, FutureTask,

RenderableImageProducer, SwingWorker, Thread, TimerTask

• Các lớp kế nhiệm từ Runnable:

• Phương thức kế nhiệm duy nhất: public void run();

duytrung.tcu@gmail.com

logic thực hiện của đối tượng kế nhiệm sẽ đặt trong phương thức này

Lớp Thread (java.lang.Thread)

• Kế nhiệm từ Runnable

public Thread(String threadName);

public Thread(Runnable target);

public Thread(Runnable target, String threadName);

 Hai hàm đầu được sử dụng khi tạo thread theo cơ chế kế thừa lớp Thread

 Hai hàm sau được sử dụng khi tạo thread bằng một đối tượng kế nhiệm Runnable

duytrung.tcu@gmail.com

• Các hàm khởi tạo chính: public Thread();

Tạo thead: với Thread

class MyThread extends Thread{

public void run(){

1. Tạo một lớp kế thừa Thread và override phương thức run()

// logic thực thi thread tại đây

...

}

}

2. Tạo một đối tượng từ lớp vừa tạo

MyThread thr1 = new MyThread()

thr1.start();

duytrung.tcu@gmail.com

3. Chạy thread khi cần bằng phương thức start()

Tạo thread: với Runnable

public interface Runnable {

void run();

}

• Runnable là một interface có phương thức duy nhất là run()

public class MyRunnable implements Runnable {

public void run() {

1. Tạo một lớp kế nhiệm Runnable và cung cấp thân hàm cho phương thức run()

// logic thực thi thread tại đây

. . .

}

}

duytrung.tcu@gmail.com

Tạo thread: với Runnable

Runnable r = new MyRunnable();

2. Tạo ra một đối tượng từ lớp vừa tạo

Thread t = new Thread(r);

3. Khởi tạo một đối tượng Thread và truyền vào đối tượng kiểu Runnable ở trên

t.start();

duytrung.tcu@gmail.com

4. Gọi đến phương thức start để bắt đầu thực hiện thread

Demo 1

class MyThread extends Thread

{

private String name;

public MyThread(String name) {

this.name = name;

}

@Override

public void run()

{

for(int i = 1; i <= 5; i++)

{

System.out.println(name+ ":" + i);

yield();

}

}

}

duytrung.tcu@gmail.com

Demo 1

run:

public class Demo1 {

Thread 1:1

public static void main(String[] args) {

Thread 2:1

Thread[] threads = {

Thread 3:1

new MyThread("Thread 1"),

Thread 2:2

new MyThread("Thread 2"),

Thread 1:2

new MyThread("Thread 3")

Thread 2:3

};

Thread 3:2

for(Thread t : threads)

Thread 2:4

{

Thread 1:3

Thread 2:5

t.start();

Thread 3:3

}

Thread 1:4

}

Thread 3:4

}

Thread 1:5

Thread 3:5

BUILD SUCCESSFUL (total time: 0 seconds)

* Không đoán trước được kết quả hiển thị, ta không kiểm soát được hoàn toàn trình tự thực hiện các thread

duytrung.tcu@gmail.com

Lớp Thread: Các phương thức

Tên phương thức

Giải thích

public void start()

Bắt đầu chạy thread, JRE sẽ gọi đến phương thức run() của lớp, các thread đang chạy vẫn tiếp tục

public void run()

Chuỗi các lệnh thực thi khi chạy thread, khi run() hoàn thành, thread chấm dứt

public static sleep(long milisecs)

public static sleep(long millisecs, int nanosecs)

Đình chỉ thread hiện tại và nhường điều khiển cho thread khác trong khoảng thời gian tính bằng mili-giây (cộng nano-giây). Cho phép thread hoạt động trở lại sớm hơn khoảng thời gian “ngủ” dự kiến thông qua phương thức interrupt.

public void interrupt()

public static void yield()

Gợi ý cho trình xếp lịch (scheduler) rằng thread hiện tại có thể nhường quyền điều khiển cho thread khác trong lịch chạy, tuy vậy trình xếp lịch có thể bỏ qua gợi ý này

public boolean isAlive()

Trả về false nếu thread chưa chạy, hoặc đã “chết” và ngược lại

public void setPriority(int p)

Đặt mức ưu tiên cho thread

duytrung.tcu@gmail.com

Vòng đời của một thread

• Khi khởi tạo, thread ở trạng thái “NEW”

• Ở trạng thái này , thread mới chỉ là một đối tượng trong bộ nhớ heap,

chưa được cấp tài nguyên gì

duytrung.tcu@gmail.com

Vòng đời của một thread

• Phương thức start() được gọi, thread chuyển sang trạng thái

“RUNNABLE”

• Thread được cấp tài nguyên cần thiết, được xếp lịch chạy, và gọi

phương thức run()

duytrung.tcu@gmail.com

Vòng đời của một thread

Thread rơi vào trạng thái “not RUNNABLE” trong các tình huống sau:

• Gọi sleep()

• Gọi wait() để chờ một số điều kiện được đáp ứng rồi mới thực hiện tiếp

• Bị “blocked” để chờ một thao tác vào /ra thực hiện xong

duytrung.tcu@gmail.com

Vòng đời của một thread

Từ “not RUNNABLE”, thread trở lại “RUNNABLE” trong các trường hợp:

• Thread “ngủ” được báo thức khi hết thời gian hoặc gọi interrupt()

• Thread đang chờ do gọi wait() thì notify() hoặc notifyAll() được gọi thông

báo các điều kiện cần thiết đã xong và có thể chạy tiếp

• Thao tác vào/ra được thực hiện xong, thread được “unblocked”

duytrung.tcu@gmail.com

Vòng đời của một thread

• Thread kết thúc hay ở trạng thái “TERMINATED” khi phương thức run()

chạy xong và kết thúc

• isAlive() kiểm tra xem thread còn sống hay không

isAlive() = true

isAlive() = false

duytrung.tcu@gmail.com

Vòng đời của một thread

 NEW

 RUNNABLE

 WAITING

 BLOCKED

 TIMED_WAITING

 TERMINATED

duytrung.tcu@gmail.com

Phương thức getState() trả về trạng thái hiện tại của thread, có kiểu enum trong Thread.State, nhận một trong các giá trị sau

Mức ưu tiên và xếp lịch cho thread

• JVM xếp lịch chạy cho các thread dựa trên mức ưu tiên

• Mỗi thread được gán một mức ưu tiên là một số nguyên trong khoảng từ Thread.MIN_PRIORITY đến Thread.MAX_PRIORITY, Thread.NORM_PRIORITY là mức ưu tiên mặc định bằng 5

• Khi thread được tạo, nó kế thừa mức ưu tiên từ thread cha

trong đó, int priority phụ thuộc vào JVM, có thể trong khoảng từ 1 đến 10

duytrung.tcu@gmail.com

• Đặt mức ưu tiên cho thread bằng phương thức public void setPriority(int priority);

Mức ưu tiên và xếp lịch cho thread

• Mức ưu tiên càng cao, càng được JVM ưu tiên chạy trước

chúng theo thuật toán Round Robin

• Với các thread cùng mức ưu tiên cao nhất, JVM xếp lịch cho

• Xét ưu tiên, nếu một thread có mức ưu tiên cao hơn trở nên

RUNNABLE, thread đang chạy có mức ưu tiên thấp hơn ngay lập tức phải nhường điều khiển cho nó

duytrung.tcu@gmail.com

• Nếu nhiều thread đang RUNNABLE và có cùng mức ưu tiên, một thread có thể chiếm điều khiển cho đến khi chạy xong (tham ăn - starvation). Có thể sử dụng sleep() hoặc yield() để phân bổ điều khiển cho các thread khác nữa

Mức ưu tiên và xếp lịch cho thread

Tóm lại, một thread sẽ duy trì chạy cho tới khi:

 Một thread mức ưu tiên cao hơn trở nên RUNNABLE  Chủ động nhường điều khiển cho thread khác thông qua các phương thức

wait(), yield() và sleep()

 Thread kết thúc, cụ thể là run() kết thúc  Trên các hệ điều hành có chia khe thời gian (time slicing), thread sẽ dừng khi

dùng hết định mức CPU dành cho nó

• Lưu ý quan trọng: xếp lịch ưu tiên cho thread phụ vào JVM, JVM thông qua hệ điều hành để thực hiện đa luồng, không phải lúc nào JVM cũng đảm bảo thread có mức ưu tiên cao nhất được chạy trước, chẳng hạn thread có ưu tiên thấp hơn có thể được chạy để ngăn starvation. Do đó, đừng quá tin tưởng vào mức ưu tiên khi thiết kế thuật toán

duytrung.tcu@gmail.com

Nhóm thread – ThreadGroup

• Một số trường hợp đòi hỏi gom các thread có chức năng tương

 Lấy ví dụ với một trình duyệt web, nếu nhiều thread đều đang thực hiện lấy một bức ảnh trên internet về thì người dùng bấm Stop để ngừng quá trình tải trang web lại; lúc này sẽ rất tiện lợi và an toàn nếu như có thể dừng tất cả các thread đang tải ảnh về cùng một lúc

• Xây dựng một ThreadGroup cho phép làm việc với một nhóm

tự nhau thành nhóm để tiện sử dụng

String groupName = “Loading Image Group”;

// Tên nhóm phải là duy nhất

ThreadGroup g = new ThreadGroup(groupName);

duytrung.tcu@gmail.com

các thread

Nhóm thread – ThreadGroup

Thread t = new Thread(g, threadName);

• Phương thức activeCount() trả về số thread còn có thể chạy

• Đưa thread vào threadGroup thông qua hàm khởi tạo:

if(g.activeCount == 0){

// Tất cả các thread đều đã kết thúc}

được

• Để ngắt tất cả các thread trong một nhóm, gọi đến interrupt()

g.interrupt();

duytrung.tcu@gmail.com

cho cả nhóm

Đồng bộ thread (Synchronization)

• Trong các ứng dụng đa luồng, một tình huống phổ biến là hai

• Điều gì xảy ra nếu các thread này đều thay đổi đối tượng theo

thread trở lên cần tương tác với cùng đối tượng

những chiều hướng khác nhau?

• Đồng bộ thread rất quan trọng, ngăn chặn tình trạng các thread

duytrung.tcu@gmail.com

“giẫm chân lên nhau”

Minh họa khi không đồng bộ thread

Giả thiết ta xây dựng một ngân hàng, quản lý 100 tài khoản, mỗi tài khoản ban đầu có 1000$. Các tài khoản chuyển một số tiền ngẫu nhiên đến một tài khoản ngẫu nhiên trong nội bộ ngân hàng.

100.000

duytrung.tcu@gmail.com

Minh họa khi không đồng bộ thread

 double[] accounts: mảng chứa số tiền của các tài khoản

• Xây dựng lớp Bank:

 public Bank(int n, double initialBalance) : n tài khoản, và số tiền ban

đầu trong mỗi tài khoản

 public void transfer(int from, int to, double amount): phương

thức chuyển số tiền amount từ tài khoản from, đến tài khoản to

 public double getTotalBalance(): phương thức trả về số tiền đang có trong

toàn ngân hàng, bằng tổng số tiền của tất cả các tài khoản

duytrung.tcu@gmail.com

• Hàm khởi tạo và các phương thức:

Minh họa khi không đồng bộ thread

• Xây dựng mỗi tài khoản là một thread, mỗi thread liên tục chuyển tiền từ tài khoản hiện tại sang tài khoản khác bằng phương thức transfer()

• In ra thông tin mỗi giao dịch và tính tổng số tiền trong toàn ngân hàng

bằng phương thức getTotalBalance(). Kết quả in ra có dạng như sau:

duytrung.tcu@gmail.com

Minh họa khi không đồng bộ thread

accounts[to] += amount

1. Tải accounts[to] vào thành ghi

+500$

2. Cộng với amount

3. Trả kết quả về với accounts[to]

+900$

đúng phải là 6400, mất 900$!

duytrung.tcu@gmail.com

Thread safe

• Một đoạn chương trình được gọi là thread safe – an toàn với đa luồng – nếu nó hoạt động đúng khi được thực thi đồng thời bởi nhiều thread

• Đáp ứng được nhiều thread sử dụng một dữ liệu chia sẻ: mảng

account trong ví dụ ngân hàng chẳng hạn

• “Đồng thời” xét trên khía cạnh hiệu quả thu được, còn mỗi thời

điểm chỉ có một thread sử dụng dữ liệu chia sẻ

duytrung.tcu@gmail.com

• Rõ ràng phương thức transfer() không thread safe!

Đồng bộ thread (Synchronization)

• Vấn đề: phương thức transfer() có thể bị cắt ngang bởi một thread khác, hoặc thread hiện tại chưa thực hiện xong thì đã hết tài nguyên (khe thời gian chẳng hạn)

• Giải quyết: phải đảm bảo hoàn thành phương thức rồi mới nhường điều khiển cho thread khác

duytrung.tcu@gmail.com

Đồng bộ thread (Synchronization)

1. Sử dụng Lock, cụ thể là lớp ReentrantLock, xuất hiện từ

2 cơ chế chính để bảo vệ một khu vực lệnh khỏi các truy cập đồng thời:

JDK 5.0

duytrung.tcu@gmail.com

2. Sử dụng từ khóa synchronized trước JDK 5.0

Đồng bộ thread (Synchronization)

1. Sử dụng Lock, cụ thể là lớp ReentrantLock, xuất hiện từ

2 cơ chế chính để bảo vệ một khu vực lệnh khỏi các truy cập đồng thời:

JDK 5.0

duytrung.tcu@gmail.com

2. Sử dụng từ khóa synchronized trước JDK 5.0

Lớp ReentrantLock (java.util.concurrent.locks)

• Dàn cảnh bảo vệ đoạn lệnh sử dụng ReentrantLock như sau:

myLock.lock(); // a ReentrantLock object

try{

bank.transfer(from, to, amount);

}

finally

{

myLock.unlock();

// make sure the lock is unlocked even if // an exception is thrown

}

• Khi có một thread “bấm khóa” đối tượng myLock, không thread nào

khác có thể xâm nhập vào đoạn lệnh chính

• Khi thread khác gọi đến phương thức lock(), thread này sẽ rơi vào trạng thái BLOCKED, cho đến khi thread kia “mở khóa” myLock

duytrung.tcu@gmail.com

Vấn đề với kiểm tra điều kiện

• Trong ví dụ ngân hàng, ta mong muốn số dư của tài khoản nguồn phải

không nhỏ hơn lượng tiền cần chuyển:

if(bank.getBalance(from) >= amount)

bank.transfer(from, to, amount);

• Kịch bản sau hoàn toàn có thể xảy ra: thread mới kiểm tra điều kiện xong,

kết quả là hợp lệ, chưa chuyển tiền thì mất quyền điều khiển:

if(bank.getBalance(from) >= amount)

// Mới kiểm tra điều kiện xong thì đến phiên thread khác

bank.transfer(from, to, amount);

• Đến khi thread chạy lại, số dư tài khoản này lại nhỏ hơn số tiền muốn

chuyển, do đó ta cần đảm bảo rằng thread phải không bị gián đoạn trong quá trình trong quá trình kiểm tra điều kiện và chuyển tiền

duytrung.tcu@gmail.com

Vấn đề với kiểm tra điều kiện

• Cách giải quyết: bảo vệ toàn bộ đoạn lệnh trong transfer() như sau:

public void transfer(int from, int to, double amount){

bankLock.lock();

try{

while(accounts[from] < amount){

// chờ tài khoản khác chuyển tiền vào cho đến khi

// số dư là hợp lệ

...

}

// chuyển tiền ...

}

finally{

bankLock.unlock();

}

Không khả thi do thread hiện tại đã chiếm quyền điều khiển cho đến khi đối tượng bankLock được unlock!

}

duytrung.tcu@gmail.com

Biến điều kiện – đối tượng Condition

• Condition (java.util.concurrent.locks) là một interface, đóng vai trò như một phương tiện để dừng thread lại, cho đến khi được một thread khác thông báo điều kiện đã đảm bảo để chạy tiếp

• Một đối tượng Condition luôn sinh từ một “khóa” cụ thể. Để sinh một Condition, gọi phương thức newCondition() từ đối tượng “khóa”

• Một “khóa” do đó có thể gắn với một hoặc nhiều đối tượng

Condition

duytrung.tcu@gmail.com

• Đặt tên Condition sao cho mô tả được điều kiện mà nó đại diện

Biến điều kiện – đối tượng Condition

Tên phương thức

Mô tả

void await()

Đưa thread hiện tại về tình trạng chờ, cho đến khi được thread khác báo hiệu (signal) hoặc bị ngắt (interrupt)

boolean await(long time, TimeUnit unit)

Tương tự như await(), thêm khoảng thời gian tối đa cho thread ở tình trạng chờ

long awaitNanos(long nanosTimeout)

Tương tự như await(), thêm khoảng thời gian tối đa cho thread ở tình trạng chờ tính bằng nano-giây

void awaitUninterruptibly()

Đưa thread về tình trạng chờ cho đến khi được thread khác báo hiệu

boolean awaitUntil(Date deadline)

Đưa thread về tình trạng chờ đến hết thời hạn deadline

void signal()

Đánh thức một thread đang chờ

void signalAll()

Đánh thức tất cả các thread đang chờ

duytrung.tcu@gmail.com

Biến điều kiện – đối tượng Condition

• Trở lại ví dụ ngân hàng, ta sinh ra một đối tượng Condition để

private Condition sufficientFunds;

đặc trưng cho điều kiện “số dư tài khoản hợp lệ”

sufficientFunds = bankLock.newCondition();

• Khi khởi tạo đối tượng Bank, khởi tạo đối tượng Condition:

• Khi kiểm tra thấy số dư tài khoản không hợp lệ, ta dừng thread

while(accounts[from] < amount){

sufficientFunds.await();

...

}

duytrung.tcu@gmail.com

để chờ đến khi đủ số dư bằng phương thức await()

Biến điều kiện – đối tượng Condition

• Thread hiện tại sẽ rơi vào trạng thái BLOCKED, đưa vào trong danh

sách đợi (waitset), và nhả “khóa” cho thread khác

• Trạng thái BLOCKED này khác với ngữ cảnh khi chờ khóa: thread tiếp tục BLOCKED kể cả khi “khóa” sẵn sàng trở lại, nó chỉ UNBLOCKED khi một thread khác gọi đến signalAll() của cùng đối tượng Condition

sufficientFunds.signalAll();

• Lúc này thread trở lại RUNNABLE, được xếp lịch chạy, và sẵn sàng

nhận khóa khi có thể

duytrung.tcu@gmail.com

Biến điều kiện – đối tượng Condition

• Khi nhận được “khóa”, thread tiếp tục tại vị trí cuối cùng khi nó

• Tại thời điểm này, rõ ràng thread nên kiểm tra lại điều kiện, bởi dễ hiểu điều kiện được đánh giá là đúng chỉ tại thời điểm gọi signalAll(), còn hiện tại thì chưa biết

rời đi: trở lại từ await()

• Do vậy, một kinh nghiệm là nên gọi await() từ đối tượng điều

while(accounts[from] < amount){

sufficientFunds.await();

...

}

duytrung.tcu@gmail.com

kiện trong một vòng lặp

Deadlock

• Deadlock là hiện tượng hai hoặc nhiều thread chờ nhau nhả

duytrung.tcu@gmail.com

khóa và do đó bị “tắc” mãi mãi

Deadlock

• Khi thread gọi await(), “số phận” của nó phụ thuộc vào các thread còn lại: nếu không thread nào gọi signalAll(), nó sẽ vĩnh viễn không chạy trở lại nữa → deadlock

• Khi các thread đều đang block, còn lại thread cuối cùng gọi await() mà không unblock bất kỳ thread nào, do đó tất cả đều BLOCKED, chương trình “treo”!! → deadlock

duytrung.tcu@gmail.com

• Kinh nghiệm: gọi signalAll() mỗi khi trạng thái của đối tượng thay đổi theo chiều hướng có lợi cho các thread đang chờ

Deadlock

• Ở ví dụ ngân hàng, mỗi khi số dư tài khoản được cập nhật, các

public void transfer(int from, int to, double amount){

bankLock.lock();

try{

while(accounts[from] < amount){

sufficientFunds.await();

thread đang chờ cần được trao cơ hội để kiểm tra xem số dư tài khoản của mình đã hợp lệ chưa, bằng cách gọi signalAll()

// chuyển tiền sufficientFunds.signalAll();

}

finally{

bankLock.unlock();

}

}

duytrung.tcu@gmail.com

Deadlock

• Phương thức signal() chỉ đánh thức một thread duy nhất,

• signal() rõ ràng có hiệu năng tốt hơn signalAll(), tuy vậy tiềm ẩn một rủi ro: nếu như thread được đánh thức chạy tiếp song vẫn chưa đạt điều kiện, nó sẽ về trạng thái BLOCKED để chờ tiếp. Nếu như không có thread nào khác gọi đến signal() nữa, chương trình “treo” → deadlock

duytrung.tcu@gmail.com

được chọn ngẫu nhiên, trong danh sách đợi

Tổng kết về Lock và Condition

• Một khóa bảo vệ một khu vực code, chỉ cho phép một thread

• Một khóa chịu trách nhiệm điều hành các thread đang muốn

được thực thi code tại mỗi thời điểm

thực thi khu vực code được bảo vệ

• Một khóa có thể liên kết với một hoặc nhiều biến điều kiện

Condition

duytrung.tcu@gmail.com

• Mỗi biến điều kiện chịu trách nhiệm điều hành các thread đã xâm nhập vào khu vực code bảo vệ song không thể chạy tiếp do điều kiện chưa đáp ứng

Đồng bộ thread (Synchronization)

1. Sử dụng Lock, cụ thể là lớp ReentrantLock, xuất hiện từ

2 cơ chế chính để bảo vệ một khu vực lệnh khỏi các truy cập đồng thời:

JDK 5.0

duytrung.tcu@gmail.com

2. Sử dụng từ khóa synchronized trước JDK 5.0

Từ khóa synchronized

• Trước Lock và Condition, Java sử dụng một cơ chế tương tranh

• Mỗi đối tượng Java đều có một “khóa ẩn” (implicit lock)

khác

• Nếu một phương thức được khai bao với

từ khóa synchronized, khóa của đối tượng sẽ bảo vệ cho toàn bộ phương thức

• Do vậy, để gọi được phương thức, thread phải giành được khóa

duytrung.tcu@gmail.com

của đối tượng chứa phương thức

Cơ chế Lock và cơ chế synchronized

public void method()

{

implicitLock.lock();

public synchronized void method()

try{

{

// method body

// method body

}

}

finally{

implicitLock.unlock();

}

}

wait()

implicitCondition.await()

notify()

implicitCondition.signal()

implicitCondition.signalAll()

↔ ↔ notifyAll() ↔

duytrung.tcu@gmail.com

• “Khóa ẩn” của đối tượng chỉ có một “điều kiện ẩn” gắn kèm

Sử dụng synchronized cho một khối lệnh

• Khai báo một đối tượng Object chỉ để tận dụng “khóa ẩn” của

class Bank{

private Object lock = new Object();

private double accounts[];

public void transfer(int from, int to, double amount){

synchronized(lock){

accounts[from] -= amount;

accounts[to] += amount;

}

...

}

...

duytrung.tcu@gmail.com

Từ khóa volatile

• Đôi khi, việc đồng bộ thread sử dụng lock hay synchronized sẽ

• Từ khóa volatile cung cấp một cơ chế không cần khóa để

lãng phí khi ta chỉ đọc ghi đến một, hai thuộc tính của đối tượng

Ví dụ một đối tượng có một cờ boolean tên done có thể được được set bằng thread này, get bằng thread khác:

private boolean done;

private volatile boolean done;

public synchronized boolean isDone()

public boolean isDone()

{

{

return done;

return done;

}

}

duytrung.tcu@gmail.com

đồng bộ việc truy cập đến một thuộc tính của đối tượng

Tổng kết về truy cập đồng thời

• Thuộc tính là volatile

Truy cập đồng thời đến một thuộc tính là an toàn trong các trường hợp sau:

• Thuộc tính là final, và truy cập diễn ra sau khi nó đã được

khởi tạo

duytrung.tcu@gmail.com

• Truy cập đến thuộc tính được bảo vệ bởi một khóa

Thread trên Swing

Một ứng dụng Swing chạy trên nhiều thread, chia làm 3 loại:

kết thúc GUI

1. Main thread, là thread chạy phương thức main(), bắt đầu và

3. Một số thread nền

duytrung.tcu@gmail.com

2. Event-dispatching thread (EDT)

Thread trên Swing

• Mọi thao tác xử lý sự kiện, vẽ và hiển thị đều thực hiện ở EDT,

 Các sự kiện được xử lý lần lượt, kết thúc cái này mới đến cái khác  Quá trình vẽ không bị ngắt bởi các sự kiện

nhằm đảm bảo:

toàn lớn, thì giao diện sẽ “đóng băng”!

• Nếu EDT bị chiếm bởi bởi một tác vụ đòi hỏi khối lượng tính

• Kinh nghiệm cho thấy những tác vụ nào yêu cầu thời gian thực

• Code tương tác với các component nên chạy trên EDT, vì nhiều

hiện từ 30 mili-giây trở lên thì không nên chạy trên EDT

duytrung.tcu@gmail.com

component không thread safe!

Thread trên Swing

Tóm lại:

EDT, nếu không sẽ ảnh hưởng đến tính đáp ứng của giao diện

• Các tác vụ tốn thời gian hoặc tác vụ IO không nên chạy trên

• Các component của Swing chỉ nên được sử dụng trong EDT để

duytrung.tcu@gmail.com

đạt được thread safe

invokeLater() và invokeAndWait()

• javax.swing.SwingUtilities

đặt một tác vụ Runnable vào chạy trong EDT

• invokeLater(Runnable) và invokeAndWait(Runnable)

• Để ngăn chặn các vấn đề xảy ra giữa main thread và EDT, sử

dụng invokeLater(Runnable) để tạo các component trong EDT, thay vì main thread

• Gọi invokeLater() trong bất cứ thread nào để yêu EDT chạy đoạn code trong phương thức run() của đối tượng Runnable

duytrung.tcu@gmail.com

• Giống với java.awt.EventQueue.invokeLater()

invokeLater() và invokeAndWait()

/** The entry main() method */ public static void main(String args[]) {

// Run the GUI codes on the event-dispatching thread for thread-safety SwingUtilities.invokeLater(new Runnable() {

@Override public void run() {

JFrame frame = new JFrame("My Swing Application"); frame.setContentPane(new MyMainPanel()); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.pack(); frame.setLocationRelativeTo(null); // center on screen frame.setVisible(true); // show it

}

});

}

• invokeAndWait() chờ EDT chạy xong đoạn code cụ thể mới

duytrung.tcu@gmail.com

trả về, hay được sử dụng trong init() của JApplet

SwingWorker

• Là lớp trừu tượng, tạo kết nối giữa EDT với một số thread chạy

• Sử dụng để xếp các tác vụ tính toán lớn vào thread chạy nền và

nền

trả kết quả trung gian hoặc kết quả cuối cùng về cho EDT

• Khai báo lớp của SwingWorker như sau:

public abstract class SwingWorker implements RunnableFuture

trong đó: T chỉ kiểu kết quả trả về của phương thức doInBackground() và get()

V chỉ kiểu kết quả trả về của các phương thức publish() và process()

RunnableFuture là kết hợp của 2 interface: Runnable và Future.

Runnable có run(), Future có get(), cancel(), isDone() và isCancelled()

duytrung.tcu@gmail.com

SwingWorker

Tên phương thức

Mô tả

protected T doInBackground() throws Exception Thực thi tác vụ nào đó trong thread chạy nền

protected void done()

Làm gì trên EDT sau khi doInBackground() chạy xong

public final T get() throws InterruptedException, ExecutionException

Chờ doInBackground() chạy xong và lấy kết quả Gọi get() trong EDT sẽ dừng mọi sự kiện lại, kể cả vẽ lại, cho đến khi SwingWorker chạy xong

public final void execute()

Bắt đầu thực hiện tác vụ trong SwingWorker

Cố gắng hủy bỏ tác vụ đang được SwingWorker thực hiện

public final boolean cancel(boolean mayInterruptIfRunning)

public final boolean isDone()

Trả về true nếu tác vụ đã được hoàn thành

public final boolean isCancelled()

Trả về true nếu tác vụ đã bị hủy trước khi hoàn thành

duytrung.tcu@gmail.com