<–more–>

前几天也是蛮尴尬的,阿粉在进行电话面试的时候因为一个线程题,让自己白白的失去了一份不错的工作,是个什么问题那?那就是线程的封闭。

线程封闭

相比大家都知道,多线程的原子性,有序性,可见性,等等,但是不知道大家知道不知道线程的封闭,关于线程的其他的几种状态,

比如说阿粉之前说的并发编程的可见性,有序性和原子性,之前的文章连接奉上,

诡异的并发之可见性

上述的文章是讲述并发的可见性的,还有讲述有序性的:

诡异并发三大恶人之有序性

最后就是关于讲述原子性的:

诡异并发三大恶人之原子性

那么我们现在需要说的就是什么是线程封闭,因为面试官在问到关于线程的问题的时候,顺带在最后问了阿粉一句,你知道线程封闭么?

啥?瞬间发呆?啥事线程封闭,不懂呀,这东西我没怎么听说过呀,那可咋增,也不能瞎扯呀,只能对他说了一句,不好意思哈,这块我还真没有研究过,不是很熟,于是在下面的面试过程中,就流于形式了,也可能一个问题回答不会,确实有点不太好,但是会就是会,不会就是不会,你也不能装懂不是,于是阿粉也只能认倒霉了,但是还得认真学习不是么?

那么什么是线程封闭呢?

线程封闭

在《Java并发编程实战》中,是这样解释的,当我们访问共享的可变数据的时候,我们通常是需要使用同步的,而不使用同步的话,那么我们就不能共享收据,如果说仅仅是在单线程内访问数据的话,就不需要同步,这种技术被称之为线程封闭。

也就是说,当我们为了线程安全的时候,需要同步是为了保证可变数据共享的时候的安全,而另外的一种方式就是保证可变数据不共享,或者是使数据不可变,也就是说让可变数据不共享,而我们在面试中被问到的所谓的线程封闭,就是在单线程中访问数据,也就是说,让一个可变数据不被多个线程共享从而确保安全性和正确性。

线程封闭都有哪些种类

1.ThreadLocal类

首先我们先说第一种,使用ThreadLocal,这个类能够使线程中的某个值和保存值进行关联,而且它也提供了一系列的方法,ThreadLocal.get()、ThreadLocal.set()、ThreadLocal.initialValue(),这里面的所有的方法都会是该变量在内存中保存一个副本,

而这三个方法总的来说,都是对共享变量的一个改变,不论是进行初始化,还是进行赋值和改变,都是对共享变量的修改。

那么怎么使用ThreadLocal类来维持线程的封闭呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>(){
    //初始化ThreadLocal对象connectionHolder的共享变量为Connection类型的对象
    public Connection initialValue(){
        return DriverManager.getConnection(DB_URL);  
    }
};


public static Connection getConnection(){
    //使用ThreadLocal对象的get()方法获取其共享变量
    return connectionHolder.get();
}

以上代码来自于《Java并发编程实战》,那么我们就来分一下,ThreadLocal这个类,一般的来说就是用于防止对可变的变量进行共享的时候出现不安全的操作所针对的,在单线程的程序中我们需要时刻保证数据库的连接,也就是我们只有这一个 Connection,而JDBC的链接对象它是不安全的,所以,我们把这个JDBC的连接保存在ThreadLocal中,让每个线程都拥有自己的连接。

这么说是不是就很好理解了,那么为什么ThreadLocal这个类能够保存线程局部变量的状态,使得每次访问此变量时都能获得实时的、正确的值呢?这个就得看源码了,阿粉在这里也给大家讲一点,大家有兴趣的也可以自己去看看源码里面是怎么写的。

ThreadLocal对象为什么说通常用于防止对可变的单例变量或全局变量进行共享

说这个的时候我们就一定要去看源码,我们都知道ThreadLocal类是JDK的lang包下面的类,那么来吧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

public class ThreadLocal<T> {
         static class ThreadLocalMap {
             /**
             * The entries in this hash map extend WeakReference, using
             * its main ref field as the key (which is always a
             * ThreadLocal object).  Note that null keys (i.e. entry.get()
             * == null) mean that the key is no longer referenced, so the
             * entry can be expunged from table.  Such entries are referred to
             * as "stale entries" in the code that follows.
             */
            static class Entry extends WeakReference<ThreadLocal<?>> {
                /** The value associated with this ThreadLocal. */
                Object value;
    
                Entry(ThreadLocal<?> k, Object v) {
                    super(k);
                    value = v;
                }
            }

         }
}

有心的读者们已经看到了,Entry?ThreadLocalMap内创建了Entry数组,其构造行为和HashMap那真的是有点兄弟的意思了,而阿粉刚才也说了它的get,initialValue,和set方法都是为了对共享变量进行操作,就是在这里,当第一次调用get()方法时,会调用initialValue()方法,默认返回一个空值。所以在使用ThreadLocal时需要将其子类化并重写此方法,创建需要关联的变量,

而初始化的过程,就是建立新的ThreadLocalMap对象,将ThreadLocal对象与变量关联起来,而我们在Thread的方法中就能找到ThreadLocal和ThreadLocalMap关联的证明,这里不给大家去寻找了,大家去源码里面搜一下一定可以看到,位置在180行附近。而这样我们就不难理解为什么会说ThreadLocal对象为什么说通常用于防止对可变的单例变量或全局变量进行共享了。

  1. 使用栈封闭来进行线程封闭

而在《Java并发编程实战》当中,说了一句话叫做栈封闭是线程封闭的一种特例,在栈封闭中,只能通过局部变量才能访问对象。

而阿粉在看到这句话的时候,也是琢磨了很久,不是很理解其中的意思,但是因为有了案例的解释,阿粉就瞬间感觉变得很明朗了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public int loadTheArk(Collection<Animal> candidates) {
    SortedSet<Animal> animals;
    int numPairs = 0;
    Animal candidate = null;
 
    // animals被封闭在方法中,不要使它们逸出!
    animals = new TreeSet<Animal>(new SpeciesGenderComparator());
    animals.addAll(candidates);
    for (Animal a : animals) {
        if (candidate == null || !candidate.isPotentialMate(a))
            candidate = a;
        else {
            ark.load(new AnimalPair(candidate, a));
            ++numPairs;
            candidate = null;
        }
    }
    return numPairs;
}

以上的代码同样是来自于《Java并发编程实战》当中,而这个案例为什么一看就能明白其中的意思呢?

我们可以看一下,numPairs这个局部的基本类型的变量,就是你不管干什么,你都无法去破坏这个numPairs,也无法去破坏栈的封闭性,因为你这个局部变量出不去,只是定义在这loadTheArk的程序中,外边的任何方法想搞事情,不好意思,完全不存在,所以,我们就明白这种栈的封闭是如何实现线程封闭的了。

  1. Ad-hoc线程封闭

首先我们先说这个Ad-hoc线程封闭是个什么意思,这忽然多出来个名词,你不知道他是什么意思,那么你肯定是不行滴,其实这就是个单词的事,Ad-hoc的翻译是来自拉丁语,这个短语的意思是’特设的、特定目的的(地)、即席的、临时的、将就的、专案的’。这个短语通常用来形容一些特殊的、不能用于其它方面的的,为一个特定的问题、任务而专门设定的解决方案。这个词汇须与apriori区分。

其实这Ad-hoc线程封闭最简单的一句话,就是维护线程封闭性,让程序自己负责,就这么low,一个这么高大上的词汇,解释了半天意思就这么直白,但是还是得把他原来翻译的解释出来。

Ad-hoc线程封闭是指,维护线程封闭性的职责完全由程序实现来承担。Ad-hoc线程封闭是非常脆弱的,因为没有任何一种语言特性,例如可见性修饰符或局部变量,能将对象封闭到目标线程上。事实上,对线程封闭对象(例如,GUI应用程序中的可视化组件或数据模型等)的引用通常保存在公有变量中。

说了半天,就第一句话最重要,而且他它还最脆弱,还不推荐使用,那我们一定是放在最后讲解,而最重要的已经在前面讲过了。

以后大家在遇到这样的面试的时候,是不是就能够不再因为这一个问题而出现问题了呢?

Java Geek Tech wechat
欢迎订阅 Java 极客技术,这里分享关于 Java 的一切。