Clojure的transient集合

  下班后记一些有趣的东西。

    这个话题是阿宝同学问我为什么clojure的PersistentVector的节点Node为什么要有个原子引用指向一个线程:

static class Node implements Serializable {
     //记录的线程
    transient final AtomicReference<Thread> edit;
    final Object[] array;

    Node(AtomicReference<Thread> edit, Object[] array){
        this.edit = edit;
        this.array = array;
    }

    Node(AtomicReference<Thread> edit){
        this.edit = edit;
        this.array = new Object[32];
    }
}

    我还真不懂,没有细看过这部分代码,早上花点时间学习了下。
    PersistentVector的实现是另一个话题,这里不提。我们都知道clojure的数据结构是immutable的,修改任意一个数据结构都将生成一个新的数据结构,原来的不变。为了减少复制的开销,clojure的数据结构同时是persistent,所谓持久数据结构是将数据组织为树形的层次结构,修改的时候只是root改变,指向不同的节点,原有的节点仍然复用,从而避免了大量的数据复制,具体可以搜索下ideal hash trees这篇paper, paper难懂,可以看看这篇blog

    但是在创建PersistentVector的时候,从一堆现有的元素或者集合创建一个PersistentVector,如果每次都重新生成一个PersistentVector,未免太浪费,创建过程的性能会受到影响。我们完全可以假设创建PersistentVector这个过程肯定是线程安全的,没有必要每添加一个元素就重新生成一个PersistentVector,完全可以在同一个PersistentVector上修改。这就是TransientVector的意义所在。
    TransientVector就是一个可修改的Vector,调用它添加一个元素,删除一个元素,都是在同一个对象上进行,而不是生成新的对象。查看PersistentVector的创建:

static public PersistentVector create(ISeq items){
    TransientVector ret = EMPTY.asTransient();
    for(; items != null; items = items.next())
        ret = ret.conj(items.first());
    return ret.persistent();
}

static public PersistentVector create(List items){
    TransientVector ret = EMPTY.asTransient();
    for(Object item : items)
        ret = ret.conj(item);
    return ret.persistent();
}

static public PersistentVector create(Object items){
    TransientVector ret = EMPTY.asTransient();
    for(Object item : items)
        ret = ret.conj(item);
    return ret.persistent();
}

    看到三个方法的第一步都是将EMPTY集合transient化,生成一个可修改的TransientVector:

TransientVector(PersistentVector v){
        this(v.cnt, v.shift, editableRoot(v.root), editableTail(v.tail));
    }

static Node editableRoot(Node node){
        return new Node(new AtomicReference<Thread>(Thread.currentThread()), node.array.clone());
    }

    生成的时候记录了当前的线程在root节点。然后添加元素的时候直接调用TransientVector的conj方法,查看conj可以看到每次返回的都是this:

public TransientVector conj(Object val){
                //确保修改过程合法
        ensureEditable();
         //忽略逻辑
        return this;
    }

    查看ensureEditable方法:

void ensureEditable(){
        Thread owner = root.edit.get();
        if(owner == Thread.currentThread())
            return;
        if(owner != null)
            throw new IllegalAccessError("Transient used by non-owner thread");
        throw new IllegalAccessError("Transient used after persistent! call");
    }

    终于看到Node中的edit引用的线程被使用了,判断当前修改的线程是否是使得集合transient化的线程,如果不是则抛出异常,这是为了保证对TransientVector的编辑是在同一个线程里,防止因为意外发布TransientVector引用引起的线程安全隐患。

    知道了transient集合的用途,我们能在clojure中使用吗?完全没问题,clojure.core有个transient方法,可以将一个集合transient化:

(defn transient 
  [^clojure.lang.IEditableCollection coll] 
  (.asTransient coll))

  
    前提是这个集合是可编辑的,clojure的map、vector和set都是可编辑的。让我们确认下transient修改后的集合还是不是自身:

user=> (def v1 [1 2 3])
#'user/v1
user=> (def v2 (transient v1))
#'user/v2
user=> v2
#<TransientVector clojure.lang.PersistentVector$TransientVector@7eb366>

    定义了集合v1,v2是调用了transient之后的集合,查看v2,果然是一个TransientVector。查看v2的元素个数是不是3个:

user=> (.count v2)
3

    没问题,注意,我们不能直接调用count函数,因为v2是个普通的java对象,我们必须使用dot操作符来调用java对象的方法。添加一个元素看看:

user=> (def v3 (.conj v2 4))
#'user/v3
user=> v3
#<TransientVector clojure.lang.PersistentVector$TransientVector@7eb366>

    添加一个元素后形成集合v3,查看v3,跟v2是同一个对象#<TransientVector clojure.lang.PersistentVector$TransientVector@7eb366>
。证明了transient集合修改的是自身,而不是生成一个新集合。确认下4有加入v2和v3:

user=> (.nth v3 3)
4
user=> (.count v2)
4
user=> (.count v3)
4
user=> (.nth v2 3)
4

    果然没有问题。transient集合的使用应当慎重,除非能确认没有其他线程会去修改集合,并且对线程的可见性要求不高的时候,也许可以尝试下这个技巧。
   文章转自庄周梦蝶  ,原文发布时间2010-08-18

时间: 2024-10-02 23:37:13

Clojure的transient集合的相关文章

Scheme interpreter in clojure

   昨天晚上用clojure搞了个scheme解释器,基本上是sicp里的解释器的clojure翻译版本,可能唯一值的一提的是对transient集合的使用,实现副作用的set!.总共代码包含注释才366行,支持的feature包括 Feature Supported Comment define yes lambda yes variable lookup yes primitive procedure evaluation yes compound procedure evaluation

【好书试读】前端函数式攻城指南

开始试读:https://yqfile.alicdn.com/5b9a3f747101a427f4c501288f8cd20e.pdf 天猫购买链接:前端函数式攻城指南 函数式编程可以说是非常古老的编程方式,但是近年来函数式编程越来越受到人们的关注.不管是 Google 力推的 Go.学术派的 Scala 与 Haskell,还是 Lisp 的新 方言 Clojure,这些新的函数式编程语言都越来越受到人们的关注. 当然不仅是后端函数式编程语言层出不穷,前端也不甘示弱.虽然前端浏览器只支持一门语

Java集合源码剖析:Hashtable源码剖析

Hashtable简介 Hashtable同样是基于哈希表实现的,同样每个元素是一个key-value对,其内部也是通过单链表解决冲突问题,容量不足(超过了阀值)时,同样会自动增长. Hashtable也是JDK1.0引入的类,是线程安全的,能用于多线程环境中. Hashtable同样实现了Serializable接口,它支持序列化,实现了Cloneable接口,能被克隆. HashTable源码剖析 Hashtable的源码的很多实现都与HashMap差不多,源码如下(加入了比较详细的注释):

Java集合源码剖析:HashMap源码剖析

HashMap简介 HashMap是基于哈希表实现的,每一个元素是一个key-value对,其内部通过单链表解决冲突问题,容量不足(超过了阀值)时,同样会自动增长. HashMap是非线程安全的,只是用于单线程环境下,多线程环境下可以采用concurrent并发包下的concurrentHashMap. HashMap 实现了Serializable接口,因此它支持序列化,实现了Cloneable接口,能被克隆. HashMap源码剖析 HashMap的源码如下(加入了比较详细的注释): pac

Java集合源码剖析:ArrayList源码剖析

ArrayList简介 ArrayList是基于数组实现的,是一个动态数组,其容量能自动增长,类似于C语言中的动态申请内存,动态增长内存. ArrayList不是线程安全的,只能用在单线程环境下,多线程环境下可以考虑用Collections.synchronizedList(List l)函数返回一个线程安全的ArrayList类,也可以使用concurrent并发包下的CopyOnWriteArrayList类. ArrayList实现了Serializable接口,因此它支持序列化,能够通过

Java集合源码剖析:LinkedList源码剖析

LinkedList简介 LinkedList是基于双向循环链表(从源码中可以很容易看出)实现的,除了可以当做链表来操作外,它还可以当做栈.队列和双端队列来使用. LinkedList同样是非线程安全的,只在单线程下适合使用. LinkedList实现了Serializable接口,因此它支持序列化,能够通过序列化传输,实现了Cloneable接口,能被克隆. LinkedList源码剖析 LinkedList的源码如下(加入了比较详细的注释): package java.util; publi

Java 下一代: 没有继承性的扩展(二)探索 Clojure 协议

"没有继承性的扩展,第 1 部分" 主要讨论了 Goovy.Scala 和 Clojure 中为现有类添加新方法的机制,这也是 Java 下一代语言实现无继承扩展的方法之一.本文将探讨 Clojure 的协议如何以创新的方法拓展 Java 扩展功能,为表达式问题提供出色的解决方案. 尽管这期文章主要关注可扩展性,但也会略为涉及一些允许 Clojure 和 Java 代码无缝互操作的 Clojure 特性.这两种语言有着根本性的差别(Java 是命令式.面向对象的:而 Clojure 是

Java集合学习(十七) TreeSet详细介绍(源码解析)和使用示例

这一章,我们对TreeSet进行学习. 我们先对TreeSet有个整体认识,然后再学习它的源码,最后再通过实例来学会使用TreeSet. 第1部分 TreeSet介绍 TreeSet简介 TreeSet 是一个有序的集合,它的作用是提供有序的Set集合.它继承于AbstractSet抽象类,实现了NavigableSet<E>, Cloneable, java.io.Serializable接口. TreeSet 继承于AbstractSet,所以它是一个Set集合,具有Set的属性和方法.

Java集合学习(十六) HashSet详细介绍(源码解析)和使用示例

这一章,我们对HashSet进行学习. 我们先对HashSet有个整体认识,然后再学习它的源码,最后再通过实例来学会使用HashSet. 第1部分 HashSet介绍 HashSet 简介 HashSet 是一个没有重复元素的集合. 它是由HashMap实现的,不保证元素的顺序,而且HashSet允许使用 null 元素. HashSet是非同步的.如果多个线程同时访问一个哈希 set,而其中至少一个线程修改了该 set,那么它必须 保持外部同步.这通常是通过对自然封装该 set 的对象执行同步