Đ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
nó
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