2. Guarded suspension mode and Balking mode

Guarded suspension mode 1 以及 Balking mode 2 都是是类似于 “附加条件的 synchronized” 这样的模式。

这两者在条件达成的情况下的执行情况与一般的 single threaded execution mode一样; 但是当条件不能满足其继续运行下去时,前者则暂停(执行wait)并等待条件达成(执行notifyAll), 后者则直接中断方法的运行。

2.1. Guarded suspension mode 实例(等待我准备好)

类名

说明

Request

表示一个请求类

RequestQueue

存放请求的类

ClientThread

发送请求的类

ServerThread

接收请求的类

Main

测试类

2.1.1. Request

该类是 immutable 类,是线程安全的。

public class Request {
    private final String name;
    public Request(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
    public String toString() {
        return "[ Request " + name + " ]";
    }
}

2.1.2. RequestQueue

LinkedList对象是非线程安全的。对于wait的唤醒条件notify/notifyAll放置的位置需要考量。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import java.util.Queue;
import java.util.LinkedList;

public class RequestQueue {
    private final Queue<Request> queue = new LinkedList<Request>();

    public synchronized Request getRequest() {
        // 确认队列是否为空
        while (queue.peek() == null) {
            try {
                wait();
            } catch (InterruptedException e) {
            }
        }
        // 将队列中的第一个/头部对象取出
        return queue.remove();
    }

    public synchronized void putRequest(Request request) {
        // 向队列尾部中添加request
        queue.offer(request);
        notifyAll();
    }
}

2.1.3. ClientThread

import java.util.Random;

public class ClientThread extends Thread {
    private final Random random;
    private final RequestQueue requestQueue;
    public ClientThread(RequestQueue requestQueue, String name, long seed) {
        super(name);
        this.requestQueue = requestQueue;
        this.random = new Random(seed);
    }
    public void run() {
        for (int i = 0; i < 10000; i++) {
            Request request = new Request("No." + i);
            System.out.println(Thread.currentThread().getName() + " requests " + request);
            requestQueue.putRequest(request);
            try {
                Thread.sleep(random.nextInt(1000));
            } catch (InterruptedException e) {
            }
        }
    }
}

2.1.4. ServerThread

import java.util.Random;

public class ServerThread extends Thread {
    private final Random random;
    private final RequestQueue requestQueue;
    public ServerThread(RequestQueue requestQueue, String name, long seed) {
        super(name);
        this.requestQueue = requestQueue;
        this.random = new Random(seed);
    }
    public void run() {
        for (int i = 0; i < 10000; i++) {
            Request request = requestQueue.getRequest();
            System.out.println(Thread.currentThread().getName() + " handles  " + request);
            try {
                Thread.sleep(random.nextInt(1000));
            } catch (InterruptedException e) {
            }
        }
    }
}

2.1.5. Main类

public class Main {
    public static void main(String[] args) {
        RequestQueue requestQueue = new RequestQueue();
        new ServerThread(requestQueue, "Bobby", 6535897L).start();
        new ClientThread(requestQueue, "Alice", 3141592L).start();
    }
}

2.1.6. 运行(例子)

通过运行可知,无论服务线程和客户线程的开启先后,其第一个打印的线程必定为客户线程。

Alice requests [ Request No.0 ]
Bobby handles  [ Request No.0 ]
Alice requests [ Request No.1 ]
Alice requests [ Request No.2 ]
Bobby handles  [ Request No.1 ]
Bobby handles  [ Request No.2 ]
Alice requests [ Request No.3 ]
Bobby handles  [ Request No.3 ]
Alice requests [ Request No.4 ]
Bobby handles  [ Request No.4 ]
Alice requests [ Request No.5 ]
Alice requests [ Request No.6 ]
Bobby handles  [ Request No.5 ]
Bobby handles  [ Request No.6 ]
Alice requests [ Request No.7 ]
Bobby handles  [ Request No.7 ]
Alice requests [ Request No.8 ]
Bobby handles  [ Request No.8 ]

小技巧

使用 Guarded suspension 模式需要注意程序的生存性。考虑到这种模式的实现通过 wait 以及 notifyAll 来实现,需要意识到会有执行了 wait 但是却没执行 notifyAll,以及执行了 notifyAll 但是从来没有执行 wait 的极端情况。

2.2. Balking mode 实例(不需要就算了)

类名

说明

Data

可修改并保存的数据的类

SaverThread

定期保存数据的类

ChangerThread

修改并保存数据内容的类

Main

测试类

2.2.1. Data

import java.io.IOException;
import java.io.FileWriter;
import java.io.Writer;

public class Data {
    private final String filename;  // 保存的文件名称
    private String content;         // 数据内容
    private boolean changed;        // 修改后的内容若未保存,则为true

    public Data(String filename, String content) {
        this.filename = filename;
        this.content = content;
        this.changed = true;
    }

    // 修改数据内容
    public synchronized void change(String newContent) {
        content = newContent;
        changed = true;
    }

    // 若数据内容修改过,则保存到文件中
    public synchronized void save() throws IOException {
        if (!changed) {
            return;
        }
        doSave();
        changed = false;
    }

    // 将数据内容实际保存到文件中
    private void doSave() throws IOException {
        System.out.println(Thread.currentThread().getName() + " calls doSave, content = " + content);
        Writer writer = new FileWriter(filename);
        writer.write(content);
        writer.close();
    }
}

2.2.2. ChangerThread

import java.io.IOException;
import java.util.Random;

public class ChangerThread extends Thread {
    private final Data data;
    private final Random random = new Random();
    public ChangerThread(String name, Data data) {
        super(name);
        this.data = data;
    }
    public void run() {
        try {
            for (int i = 0; true; i++) {
                data.change("No." + i);             // 修改数据
                Thread.sleep(random.nextInt(1000)); // 执行其他操作
                data.save();                        // 显式地保存
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

2.2.3. SaverThread

import java.io.IOException;

public class SaverThread extends Thread {
    private final Data data;
    public SaverThread(String name, Data data) {
        super(name);
        this.data = data;
    }
    public void run() {
        try {
            while (true) {
                data.save();            // 要求保存数据
                Thread.sleep(1000);     // 休眠约1秒
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

2.2.4. Main

public class Main {
    public static void main(String[] args) {
        Data data = new Data("data.txt", "(empty)");
        new ChangerThread("ChangerThread", data).start();
        new SaverThread("SaverThread", data).start();
    }
}

2.2.5. 运行(实例)

SaverThread calls doSave, content = No.0
SaverThread calls doSave, content = No.1
ChangerThread calls doSave, content = No.2
SaverThread calls doSave, content = No.3
ChangerThread calls doSave, content = No.4
SaverThread calls doSave, content = No.5
SaverThread calls doSave, content = No.6
ChangerThread calls doSave, content = No.7
SaverThread calls doSave, content = No.8
ChangerThread calls doSave, content = No.9
ChangerThread calls doSave, content = No.10
ChangerThread calls doSave, content = No.11
SaverThread calls doSave, content = No.12
ChangerThread calls doSave, content = No.13
ChangerThread calls doSave, content = No.14
SaverThread calls doSave, content = No.15

小技巧

可使用 Balking 模式的情况————实现闭锁。所谓闭锁,一般针对“状态仅变化一次的变量”,如下Something类中的 initialized 变量一样,一般针对的是初始化以及终止处理这类“不会执行两次及以上的处理”。

public class Something {
    private boolean initialized = false;
    public synchronized void init() {
        if (initialized) {
            return;
        }
        doInit();
        initialized = true;
    }
    private void doInit() {
        // 实际的初始化处理
    }
}

2.3. time out模式

通过以上两种模式的例子可知,其实际执行的语句都是比较极端的,比如 Guarded suspension模式中如果条件永远不满足那么就可能永远地等待下去;而 Balking 模式中如果条件不满足就立马停止操作在实际使用中可能也会受到限制。那么作为这两者地折中方案,Guarded timed 模式就可以极好地解决。


1

Guarded suspension : 防护着的暂停

2

Balking : 阻停