本文内容
何时该使用线程锁.
线程锁的写法.
以线程锁的例子来理解线程的调度。
使用线程锁的场合
程序中经常采用多线程处理,这可以充分利用系统资源,缩短程序响应时间,改善用户体验;如果程序中只使用单线程,那么程序的速度和响应无疑会大打折扣。
但是,程序采用了多线程后,你就必须认真考虑线程调度的问题,如果调度不当,要么造成程序出错,要么造成荒谬的结果。
一个讽刺僵化体制的笑话
前苏联某官员去视察植树造林的情况,现场他看到一个人在远处挖坑,其后不远另一个人在把刚挖出的坑逐个填上,官员很费解于是询问陪同人员,当地管理人员说“负责种树的人今天病了”。
上面这个笑话如果发生在程序中就是线程调度的问题,种树这个任务有三个线程:挖坑线程,种树线程和填坑线程,后面的线程必须等前一个线程完成才能进行,而不是按时间顺序来进行,否则一旦一个线程出错就会出现上面荒谬的结果。
用线程锁来处理两个线程先后执行的情况
在程序中,和种树一样,很多任务也必须以确定的先后秩序执行,对于两个线程必须以先后秩序执行的情况,我们可以用线程锁来处理。
线程锁的大致思想是:如果线程A和线程B会执行实例的两个函数a和b,如果A必须在B之前运行,那么可以在B进入b函数时让B进入wait set,直到A执行完a函数再把B从wait set中激活。这样就保证了B必定在A之后运行,无论在之前它们的时间先后顺序是怎样的。
线程锁的代码
如右,SwingComponentLock的实例就是一个线程锁,lock函数用于锁定线程,当完成状态isCompleted为false时进入的线程会进入SwingComponentLock的实例的wait set,已完成则不会;要激活SwingComponentLock的实例的wait set中等待的线程需要执行unlock函数。
public class SwingComponentLock {
// 是否初始化完毕
boolean isCompleted = false;
/**
* 锁定线程
*/
public synchronized void lock() {
while (!isCompleted) {
try {
wait();
} catch (Exception e) {
e.printStackTrace();
logger.error(e.getMessage());
}
}
}
/**
* 解锁线程
*
*/
public synchronized void unlock() {
isCompleted = true;
notifyAll();
}
}
线程锁的使用
public class TreeViewPanel extends BasePanel {
// 表空间和表树
private JTree tree;
// 这个是防树还未初始化好就被刷新用的
private SwingComponentLock treeLock;
protected void setupComponents() {
// 初始化锁
treeLock = new SwingComponentLock();
// 创建根节点
DefaultMutableTreeNode root = new DefaultMutableTreeNode("DB");
tree = new JTree(root);
// 设置布局并装入树
setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
add(new JScrollPane(tree));
// 设置树节点的图标
setupTreeNodeIcons();
// 解除对树的锁定
treeLock.unlock();
}
/**
* 刷新树视图
*
* @param schemas
*/
public synchronized void refreshTree(List<SchemaTable> schemas) {
treeLock.lock();
DefaultTreeModel model = (DefaultTreeModel) tree.getModel();
DefaultMutableTreeNode root = (DefaultMutableTreeNode) model.getRoot();
root.removeAllChildren();
for (SchemaTable schemaTable : schemas) {
DefaultMutableTreeNode schemaNode = new DefaultMutableTreeNode(
schemaTable.getSchema());
for (String table : schemaTable.getTables()) {
schemaNode.add(new DefaultMutableTreeNode(table));
}
root.add(schemaNode);
}
model.reload();
}
讲解
上页中,setupComponents函数是Swing主线程执行的,而refreshTree函数是另外的线程执行(初始化时程序开辟一个线程执行,其后执行由用户操作决定)。 refreshTree函数必须要等setupComponents函数把tree初始化完毕后才能执行,而tree初始化的时间较长,可能在初始化的过程中执行refreshTree的线程就进入了,这就会造成问题。
程序使用了一个SwingComponentLock来解决这个问题,setupComponents一开始就创建SwingComponentLock的实例treeLock,然后执行refreshTree的线程以来就会进入treeLock的wait set,变成等待状态,不会往下执行,这是不管tree是否初始化完毕都不会出错;而setupComponents执行到底部会激活treeLock的wait set中等待的线程,这时再执行refreshTree剩下的代码就不会有任何问题,因为setupComponents执行完毕tree已经初始化好了。
让线程等待和激活线程的代码都在SwingComponentLock类中,这样的封装对复用很有好处,如果其它复杂组件如table也要依此办理直接创建SwingComponentLock类的实例就可以了。如果把wait和notifyAll写在TreeViewPanel类中就不会这样方便了。
总结
线程锁用于必须以固定顺序执行的多个线程的调度。
线程锁的思想是先锁定后序线程,然后让线序线程完成任务再接触对后序线程的锁定。
线程锁的写法和使用一定要理解记忆下来。