本文主要是从代码层面解读上一篇博文(Titan线程隔离的验证)中Titan的实现机制,即如何用一个TitanGraph对象来透明地支持多线程间的事务隔离。(注:源码使用titan-0.5.4版本)
TitanGraph是一个接口,当我们使用TitanFactory.open(…)获得一个TitanGraph时,返回的都是一个StandardTitanGraph对象。 但StandardTitanGraph类并没有实现TitanGraph的所有接口,StandardTitanGraph继承自抽象类TitanBlueprintsGraph。实际上所有对TitanGraph的基本操作(如 addVertex、addEdge、commit、rollback等),也即Blueprints定义的接口,都是在这个类中实现的。
具体的类继承图就是这样的:
再细看TitanBlueprintsGraph对Blueprints接口的实现,发现都是先调用了getAutoStartTx方法,然后再进行操作:
public abstract class TitanBlueprintsGraph implements TitanGraph {
……
public TitanVertex addVertex(Object id) {
return getAutoStartTx().addVertex(id);
}
@Override
public TitanVertex addVertex() {
return getAutoStartTx().addVertex();
}
@Override
public TitanVertex getVertex(long id) {
return getAutoStartTx().getVertex(id);
}
……
@Override
public EdgeLabel getOrCreateEdgeLabel(String name) {
return getAutoStartTx().getOrCreateEdgeLabel(name);
}
@Override
public EdgeLabel getEdgeLabel(String name) {
return getAutoStartTx().getEdgeLabel(name);
}
}
getAutoStartTx方法返回的是一个TitanBlueprintsTransaction对象,因此对图的操作就是在一个Transaction里进行操作。
再看getAutoStartTx到底是如何返回一个transaction的:
private TitanBlueprintsTransaction getAutoStartTx() {
if (txs == null) ExceptionFactory.graphShutdown();
TitanBlueprintsTransaction tx = txs.get(); // 获取当前线程对应的事务
if (tx == null) { // 如果当前线程没有对应的事务
tx = (TitanBlueprintsTransaction) newThreadBoundTransaction(); // 新建一个事务
txs.set(tx); // 保存到txs中
openTx.put(tx, Boolean.TRUE);
log.debug("Created new thread-bound transaction {}", tx);
}
return tx;
}
变量 txs 很关键,是一个全局变量:
private ThreadLocal<TitanBlueprintsTransaction> txs = new ThreadLocal<TitanBlueprintsTransaction>() {
protected TitanBlueprintsTransaction initialValue() {
return null;
}
};
ThreadLocal是java自带的类,一个变量被定义为ThreadLocal<T>的,则每个线程都拥有一个独立的 T 类型的变量,通过get/set函数可以读写这个值。这就相当于定义了一个全局的Map<Thread, T>,每个线程可以get/set到自己的变量,大家拥有的变量值是相互独立的。更具体的关于ThreadLocal的介绍可以看这篇文章:[Java并发包学习七]解密ThreadLocal
既然使用了ThreadLocal,代码就非常简单了,不需要任何synchronized的同步语句,因为各线程操作的都是自己的那份变量,各个线程都有自己的一个transaction对象。因此,当不同线程对同一个TitanGraph对象进行操作的时候,实际上是化归为对它们自己的事务对象的操作,这也就做到了线程间的事务隔离了。
最后再看一下 TitanGraph 的 commit 和 rollback 函数,具体的实现在 TitanBlueprintsGraph 中:
@Override
public void commit() {
TitanTransaction tx = txs.get(); // 获取当前线程对应的事务
if (tx != null && tx.isOpen()) {
try {
tx.commit(); // 提交事务
} finally {
txs.remove(); // 将事务从txs中删除
openTx.remove(tx);
log.debug("Committed thread-bound transaction {}", tx);
}
}
}
@Override
public void rollback() {
TitanTransaction tx = txs.get(); // 获取当前线程对应的事务
if (tx != null && tx.isOpen()) {
try {
tx.rollback(); // 回滚事务
} finally {
txs.remove(); // 将事务从txs中删除
openTx.remove(tx);
log.debug("Rolled back thread-bound transaction {}", tx);
}
}
}
这两个函数都是一样,对具体的 transaction 对象执行操作之后,将它从 ThreadLocal 变量 txs 中删除。这样当该线程下次再操作 TitanGraph 时,就会(在 getAutoStartTx 函数中)生成一个新的 transaction 记录到 txs 中,即对应地又打开一个事务。
最后再看一下 TitanGraph 的 shutdown 函数:
@Override
public synchronized void shutdown() {
for (TitanTransaction tx : openTx.keySet()) {
tx.commit();
}
openTx.clear();
txs = null;
}
openTx 是一个 Map<TitanBlueprintsTransaction, Boolean> 类型,记录哪些 transaction 是打开的。该函数对所有打开的 transaction 执行 commit 操作。并最后把 txs 置空,这样这个 TitanGraph 对象就不能再被操作了(见getAutoStartTx函数的第一行代码)。
综上所述,我们从源码中可以看出 TitanGraph 对象不仅仅是线程安全的,而且透明地为每个线程打开一个事务,使得每个线程里的操作都是事务隔离的。