4. Synchronized Methods

Synchronized methods là các methods được sử dụng để điều khiển kết nối tới một đối tượng. Một thread chỉ thực hiện một synchronized method sau khi có được khóa cho methods của object hoặc class. Nếu khóa đã được giữ bởi một thread, các thread khác sẽ phải đợi. Một thread rời bỏ khóa bằng cách return từ synchronized method, cho phép các thread tiếp theo chờ khóa để thực hiện tiến trình. Synchronized methods rất hữu dụng trong những trường hợp mà các method có thể làm hỏng dữ liệu của một đối tượng nếu như hoạt động đồng thời. Lỗi xảy ra khi hai hay nhiều thread đồng thời cập nhật các giá trị vào một đối tượng, dẫn đến giá trị không chính xác hoặc không xác định. Trong khi đó, một thread được tạo ở bên trong một synchronized method, tất cả các thread khác có nhu cầu thực hiện method này phải chờ đến khi nó giành được khóa. Hạn chế này không áp dụng đối với các thread đã có khóa và đang thực hiện một method synchronized cho một đối tượng nào đó. Phương pháp như vậy có thể gọi các synchronized method mà không bị chặn. Các method bất đồng bộ (non-synchronized methods) của object thì được gọi bất cứ lúc nào bởi bất kỳ một thread nào đó.
Dưới đây là một ví dụ sử dụng synchronized method và khóa để truy cập vào một đối tượng chung của nhiều thread. Nếu từ khóa “synchronized” bị lấy ra, message sẽ hiển thị theo cách ngâu nhiên.

Public class SyncMethodsExample extends Thread {
Static String[] msg = { “Beginner”, “java”, “tutorial,”, “.,”, “com”, “is”, “the”, “best” };
Public SyncMethodExample(String id) {
Super(id);
}
Public static void main(String[] args) {
SyncMethodExample thread1 = new SyncMethodExample(“thread1: “);
SyncMethodExample thread2 = new SyncMethodExample(“thread2: “);
Thread1.start();
Thread2.start();
Boolean t1IsAlive = true;
Boolean t2IsAlive = true;
Do {
If (t1IsAlive && !thread1.isAlive()) {
T1IsAlive = false;
System.out.println(“t1 is dead.”);
}
if (t2IsAlive && !thread2.isAlive()) {
t2IsAlive = false;
System.out.println(“t2 is dead.”);
}
} while(t1IsAlive || t2IsAlive);
}
void randomWait() {
Try {
Thread.currentThread().sleep((long)(3000* Math.random()));
} catch (Exception e) {
}

Public synchronized void run() {
SynchronizedOutput.displayList(getName(), msg);
}
}
}
Class SynchronizedOutput {

// if the ‘synchronized’ keyword is removed,
// the message is displayed in random fashion
Public static synchronized void displayList(String name, String list[]) {
For (int i = 0; i < list.length; i++) {
SyncMethodsExample t = (SyncMethodExample) Thread.currentThread();
t.randomWait();
System.out.println(name + list[i]);
}
}
}

Output

thread1: Beginner
thread1: java
thread1: tutorial,
thread1: .,
thread1: com
thread1: is
thread1: the
thread1: best
t1 is dead.
thread2: Beginner
thread2: java
thread2: tutorial,
thread2: .,
thread2: com
thread2: is
thread2: the
thread2: best
t2 is dead

Synchronized Blocks

Đồng bộ các phương thức static trên khóa của class. Thu thập và trả lại một khóa class bằng một thread để thực hiện một synchronized method static, tiến hành tương tự như một khóa đối tượng cho một synchronized method. Một thread thu thập khóa class trước khi nó có thể tiếp tục thực hiện các static synchronized method trong class, chặn các thread khác thực hiện method tương tự như vậy trong cùng một class. Điều này tất nhiên không áp dụng cho phương pháp bất đồng bộ non-synchronized method, cái mà method có thể được gọi bất cứ lúc nào. Đồng bộ hóa các phương thức tĩnh trong một class độc lập với sự đồng bộ method trên một object của class. Một class con sẽ quyết định liệu định nghĩa mới của một method đồng bộ kế thừa (inherited synchronized method) sẽ được đồng bộ hóa trong class con. Synchronized blocks cho phép thực thi các đoạn code đồng bộ khóa của một đối tượng tùy ý. Dạng chung của một khối đồng bộ (synchronized block) như sau :

public Object method() {
synchronized (this) { // Synchronized block on current object
// method block
}
}

Một thread sẽ chạy vào khối code sau khi lấy được khóa trên đối tượng xác định, không thể có thread nào khác có thể thực hiện khối code này, hoặc phải đợi đến khi có được khóa thì mới được thực hiện. Điều này xảy ra khi thực hiện hết khối code bình thường hoặc được throws exception nào đó.
Việc xác định một đối tượng trong câu lệnh synchronized là bắt buộc. Một class có thể chọn để đồng bộ hóa việc thực hiện một phần của method, bằng cách sử dụng các tham chiếu này và đưa các phần có liên quan của method trong synchronized block. Các dấu ngoặc của khối không thể bỏ được, ngay cả khi khối code chỉ có một câu lệnh.

class SmartClient {
BankAccount account;
// …
public void updateTransaction() {
synchronized (account) { // (1) synchronized block
account.update(); // (2)
}
}
}

Trong ví dụ trước, dòng code (2) nằm trong khối synchronized (1) được đồng bộ trên đối tượng BankAccount. Nếu nhiều thread đồng thời thực hiện phương thức updateTransaction() trên một đối tượng của SmartClient, câu lệnh (2) sẽ được thực hiện bởi một thread tại một thời điểm nhất định, chỉ sau khi đồng bộ hóa trên đối tượng BankAccount liên quan đến trường hợp cụ thể này của SmartClient
Các lớp bên trong có thể truy cập dữ liệu bao quanh nó. Một đối tượng bên trong có thể cần phải đồng bộ hóa trên đối tượng bên ngoài liên quan của nó, để đảm bảo tính toàn vẹn của dữ liệu sau ở sau. Điều này được minh họa trong đoạn code dưới đây, ở đó khối đồng bộ tại (5) sử dụng form đặc biệt của tham chiếu này để đồng bộ hóa trên các đối tượng bên ngoài kết hợp với một đối tượng của lớp bên trong. Thiết lập này đảm bảo rằng một thread đang thực hiện method setPi() ở một đối tượng bên trong chỉ có thể truy cập double myPi tại (2) trong khối đồng bộ ở (5), bằng cách trước hết là lấy khóa trên các đối tượng bên ngoài có liên quan. Nếu thread khác có khóa của đối tượng bên ngoài có liên quan, thread trong đối tượng bên trong phải chờ đến khi nhận được khóa mới có thể tiến hành thực hiện khối lệnh trong khối đồng bộ ở (5). Tuy nhiên, đồng bộ hóa trên một đối tượng bên trong và đối tượng bên ngoài có liên quan là độc lập với nhau, trừ khi được thực thi một cách rõ ràng, như trong đoạn code sau :

class Outer { // (1) Top-level Class
private double myPi; // (2)
protected class Inner { // (3) Non-static member Class
public void setPi() { // (4)
synchronized(Outer.this) { // (5) Synchronized block on outer object
myPi = Math.PI; // (6)
}
}
}
}

Ví dụ dưới đây cho thấy cách synchronized block và khóa đối tượng được sử dụng để phối hợp truy cập đến các đối tượng được chia sẻ bởi nhiều thread.

public class SyncBlockExample extends Thread {

static String[] msg = { “Beginner”, “java”, “tutorial,”, “.,”, “com”,
“is”, “the”, “best” };
public SyncBlockExample(String id) {
super(id);
}
public static void main(String[] args) {
SyncBlockExample thread1 = new SyncBlockExample(“thread1: “);
SyncBlockExample thread2 = new SyncBlockExample(“thread2: “);
thread1.start();
thread2.start();
boolean t1IsAlive = true;
boolean t2IsAlive = true;
do {
if (t1IsAlive && !thread1.isAlive()) {
t1IsAlive = false;
System.out.println(“t1 is dead.”);
}
if (t2IsAlive && !thread2.isAlive()) {
t2IsAlive = false;
System.out.println(“t2 is dead.”);
}
} while (t1IsAlive || t2IsAlive);
}
void randomWait() {
try {
Thread.currentThread().sleep((long) (3000 * Math.random()));
} catch (InterruptedException e) {
System.out.println(“Interrupted!”);
}
}
public void run() {
synchronized (System.out) {
for (int i = 0; i < msg.length; i++) {
randomWait();
System.out.println(getName() + msg[i]);
}
}
}
}

Output

thread1: Beginner
thread1: java
thread1: tutorial,
thread1: .,
thread1: com
thread1: is
thread1: the
thread1: best
t1 is dead.
thread2: Beginner
thread2: java
thread2: tutorial,
thread2: .,
thread2: com
thread2: is
thread2: the
thread2: best
t2 is dead.

Synchronized block cũng có thể được xác định trên một class:

synchronized (.class) { }

Khối đồng bộ trên khóa của đối tượng ký hiệu là.class . Một static synchronized method classAction() trong class A tương đương với khai báo như sau :

static void classAction() {

synchronized (A.class) { // Synchronized block on class A
// …
}
}

Tóm lại, một thread có thể giữ một khóa trên một đối tượng bằng cách
• thực hiện một synchronized method
• thực hiện khối lệnh của synchronized block trên đối tượng đó
• thực hiện một đồng bộ method static của một class
Các trạng thái của Thread

Một java thread luôn luôn ở một trong các trạng thái sau đây :
• Trạng thái New Thread (trạng thái sẵn sàng để chạy)
• Trạng thái Runnable (trạng thái chạy)
• Trạng thái Not Runnable
• Trạng thái Dead

New Thread

Một thread đang trong trạng thái này khi thread mới được ra nhưng không được cho chạy. Một thread bắt đầu vòng đời của nó trong trạng thái Ready-to-run. Bạn có thể gọi trạng thái này bằng phương thức start() hay stop(). Gọi bất kỳ method nào bên cạnh việc gọi start() hay stop() là nguyên nhân gây ra IllegalThreadStateException

Runnable

Khi một thread mới gọi method start() có nghĩa là thread đã sẵn sàng để chạy (trạng thái Runnable). Một Runnable Thread thực sự có thể chạy, hoặc có thể đang chờ đến lượt của mình để chạy

Not Runnable

Một thread trở thành Not Runnable khi một trong 4 sự kiện sau xảy ra :
• Method sleep() được gọi, thread sẽ sleep trong một khoảng thời gian xác định
• Method suspend() được gọi
• Method wait() được gọi, chờ đợi tài nguyên nào đó, hoặc chờ đợi thread khác thực hiện xong để lấy được khóa của đối tượng
• Thread bị chặn I/O

   Ví dụ : Thread.currentThread().sleep(1000);

Chú ý : Thread.currentThread() có thể trả về một output giống như : Thread[threadA,5,main]
– ThreadA : tên của thread
– 5 : mức ưu tiên của thread
– Main : tên group mà nó thuộc về
Ở đây, method run() tự đặt mình vào trạng thái Not Runnable trong một giây. Một thread có thể đánh thức đột ngột bằng cách gọi interrupt() method trên đối tượng sleeping thread hoặc vào cuối khoảng thời gian sleep kết thúc.
Các tình huống dưới đây mô tả cách chuyển thread từ trạng thái Non Runnable sang Runnable :
• thread sleep trong một khoảng thời gian xác định, hết khoảng thời gian đó thread chuyển từ Non Runnable sang Runnable
• thread bị suspended, sau đó method resume() được gọi
• thread đang chờ đợi điều kiện nào đó ở một biến điều kiện, bất cứ đối tượng sở hữu biến này phải trả lại nó bằng cách gọi notify() hoặc notifyAll()
• thread bị chặn I/O, sau đó I/O hoàn tất

Trạng thái Dead

Một thread đi vào trạng thái này khi method run() đã hoàn tất thực hiện hoặc khi method stop() được gọi. Khi đã ở trạng thái này thì thread không bao giờ có thể chạy lại một lần nào nữa

Thread Priority

Trong java chúng ta có thể xác định mức độ ưu tiên của một thread nào đó so với các thread khác. Những thread có mức độ ưu tiên cao được truy cập đến tài nguyên cao hơn so với thread khác. Một thread kế thừa mức độ ưu tiên từ thread tạo ra nó.
Bạn có thể thay đổi mức ưu tiên của thread bất cứ lúc nào bằng cách sử dụng method setPriority() và lấy giá trị ưu tiên bằng method getPriority()
Các hằng số sau đây được định nghĩa trong Thread class :
• MIN_PRIORITY (0) : ưu tiên thấp nhất
• NORM_PRIORITY (5) : ưu tiên mặc định
• MAX_PRIORITY (10) : ưu tiên cao nhất
Mức ưu tiên của một thread có thể được thiết lập bằng giá trị integer nằm giữa giá trị MIN_PRIOTIRY và MAX_PRIORITY ở trên.
Khi hai hoặc nhiều thread đã sẵn sàng để được thực thi và tài nguyên hệ thống là đang có sẵn, sẵn sàng cho thread chạy., hệ thống thời gian thực (runtime system) sẽ chọn Runnable thread với mức độ ưu tiên cao nhất để thực hiện.
Nếu có hai thread có cùng mức độ ưu tiên đang chờ CPU, thread scheduler sẽ chọn một trong số chúng để chạy. Thread được chọn sẽ chạy cho đến khi một trong các điều kiện sau là đúng :
• Một thread có mức ưu tiên cao hơn sẽ trở thành Runnable
• Kết thúc method run()
• on systems that support time-slicing, its time allotment has expired

Thread Scheduler

Lập lịch trong JVM thường sử dụng một trong 2 chiến lược sau :

Lập lịch ưu tiên (Preemptive scheduling)

Nếu một thread với một mức ưu tiên cao hơn so với tất cả thread khác nó sẽ trở thành Runnable thread, scheduler sẽ chặn trước các thread đang chạy (được chuyển sang trạng thái runnable) và chọn mới thread có mức ưu tiên cao hơn để thực hiện

Time-Slicing or Round-Robin scheduling

Một thread đang chạy được phép thực hiện cho một khoảng thời gian cố định, sau đó chuyển tới trạng thái Read-to-run (Runnable) để đợi chạy lại.
Một thread scheduler là triển khai và phụ thuộc vào nền tảng, do đó, tùy từng nền tảng khác nhau mà thread sẽ sắp xếp và lập lịch khác nhau.

 Yielding

Method yield() được định nghĩa trong class Thread, gọi method yield() sẽ khiến cho thread đang ở trạng thái Running chuyển về trạng thái Runnable, từ đó trả lại CPU cho thread khác sử dụng. Thread sau đó mặc cho thread scheduler lập lịch, nếu không có thread nào đang đợi và ở trạng thái Ready-to-run, thread này sẽ tiếp tục chạy lại. Nếu có nhiều thread khác trong trạng thái Ready-to-run, mức độ ưu tiên của chúng sẽ quyết định thread nào được thực hiện. Method yield() cho các thread khác có cùng mức ưu tiên một cơ hội để chạy. Nếu không có thread nào có mức độ ưu tiên như nhau thì yield() sẽ được bỏ qua.

Sleeping and Waking Up

Thread class chứa một phương thức tĩnh có tên là sleep(), mục đích là chuyển trạng thái của thread về trạng thái sleep trong một khoảng thời gian nào đó. Method này không trả lại khóa của thread nào cả, thread sẽ sleep ít nhất trong thời gian quy định (đối số sleep), trước khi chuyển trạng thái Runnable và chạy lại. Nếu một thread bị lỗi khi sleep, nó sẽ throw ra InterruptedException khi nó tỉnh dậy. Class Thread có rất nhiều overloaded của method sleep()

Waiting and Notifying

Waiting và Notifying cung cấp phương tiện để truyền thông đồng bộ hóa trên cùng một đối tượng. Các thread thực hiện method wait() và notify() (hoặc notifyAll()) trên các đối tượng chia sẻ vì mục đích này. NotifyAll(), notify() và wait() là các method của class Object. Những method này có thể được gọi từ synchronized context (synchronized method hoặc synchronized block), nếu không, sẽ gây ra IllegalMonitorStateException. Method notifyAll() đánh thức tất cả thread đang đợi tài nguyên. Trong trường hợp này, các thread được đánh thức sẽ cạnh tranh quyền truy cập tài nguyên. Một thread sẽ giành được tài nguyên và các thread khác quay trở lại chờ đợi.

Wait() method signatures
Final void wait(long timeout) throws InterruptedException
Final void wait(long timeout, int nanos) throws InterruptedException
Final void wait() throws InterruptedException

Method wait() được gọi có thể xác định thời gian thread nên chờ đợi trước khi time out. Một thread khác có thể gọi method interrupt() trên một thread đang chờ kết quả trong InterruptedException. Đây là một ngoại lệ kiểm tra và do đó code với method wait() phải được nằm trong try catch block

notify() method signatures

final void notify()
final void notifyAll()

Một thread thường gọi method wait() trên một đối tượng đang giữ khóa vì một điều kiện để thực hiện tiếp tục của nó đã không được đáp ứng. Thread rời khỏi trạng thái Running chuyển sang trạng thái Waiting-for-notification. Thread trả lại khóa đối tượng. Việc giải phóng khóa của shared object cho phép các thread khác chạy và thực hiện synchronized code trên cùng một đối tượng sau khi có được khóa của nó.
Method wait() làm thread hiện tại dừng lại và chờ đợi thread khác thông báo nó có sự thay đổi điều kiện.
Một thread trong trạng thái Waiting-for-notification có thể được đánh thức bằng một trong ba sự cố sau :
1. Một thread khác gọi method notify() trên đối tượng của thread đang đợi, và thread đang đợi là thread được chọn để đánh thức
2. Waiting thread times out
3. Một thread khác làm gián đoạn waiting thread

Chương trình dưới đây có 3 thread, thao tác trên cùng một stack. Hai trong 3 thread đang push phần tử vào stack, trong khi một thread pop các phần tử ra khỏi stack. Ví dụ này minh họa cho việc gọi method wait() để một thread chuyển về trạng thái waiting-for-notification.

class StackClass {

private Object[] stackArray;
private volatile int topOfStack;
StackClass(int capacity) {
stackArray = new Object[capacity];
topOfStack = -1;
}
public synchronized Object pop() {
System.out.println(Thread.currentThread() + “: popping”);
while (isEmpty()) {
try {
System.out.println(Thread.currentThread()
+ “: waiting to pop”);
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Object obj = stackArray[topOfStack];
stackArray[topOfStack–] = null;
System.out.println(Thread.currentThread()
+ “: notifying after pop”);
notify();
return obj;
}
public synchronized void push(Object element) {
System.out.println(Thread.currentThread() + “: pushing”);
while (isFull()) {
try {
System.out.println(Thread.currentThread()
+ “: waiting to push”);
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
stackArray[++topOfStack] = element;
System.out.println(Thread.currentThread()
+ “: notifying after push”);
notify();
}
public boolean isFull() {
return topOfStack >= stackArray.length – 1;
}
public boolean isEmpty() {
return topOfStack < 0;
}
}

abstract class StackUser extends Thread {

protected StackClass stack;
StackUser(String threadName, StackClass stack) {
super(threadName);
this.stack = stack;
System.out.println(this);
setDaemon(true);
start();
}
}

class StackPopper extends StackUser { // Stack Popper

StackPopper(String threadName, StackClass stack) {
super(threadName, stack);
}
public void run() {
while (true) {
stack.pop();
}
}
}

class StackPusher extends StackUser { // Stack Pusher

StackPusher(String threadName, StackClass stack) {
super(threadName, stack);
}
public void run() {
while (true) {
stack.push(new Integer(1));
}
}
}

public class WaitAndNotifyExample {

public static void main(String[] args) {
StackClass stack = new StackClass(5);
new StackPusher(“One”, stack);
new StackPusher(“Two”, stack);
new StackPopper(“Three”, stack);
System.out.println(“Main Thread sleeping.”);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(“Exit from Main Thread.”);
}
}

Trường topOfStatck trong class StackClass được khai báo là volatile, do đó có thể đọc và ghi trên biến này

http://www.javabeginner.com/learn-java/java-threads-tutorial

Leave a Reply

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

You may use these HTML tags and attributes:

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>