将ThreadLocal与包含静态成员的现有类一起使用

时间:2009-08-23 23:03:29

标签: java multithreading

我正在尝试使用ThreadLocal为预先存在的非线程安全类提供线程安全性,但遇到问题。似乎没有执行隔离 - 线程仍然共享静态,而不是每个线程的本地。

我相信我的用法与this StackOverflow question中描述的SimpleDateFormatter的示例本地化几乎完全平行,但它并没有按照我希望的方式运行。

我所希望的是,那些使用过它的人会指出我必须犯下的令人难以置信的错误...所以我想我的问题是:你能否发现我在这里做错了什么? / p>

这是我的简单课程:

public class SimpleClassWithStaticMembers {
    private static String theStaticString =
        "StaticStringInClassWithStaticMember";
    public void setTheStaticString (String val) {
        SimpleClassWithStaticMembers.theStaticString = val; 
    }
    public String getTheStaticString () {
        return SimpleClassWithStaticMembers.theStaticString;
    }
}

这是创建SimpleClassWithStaticMembers

的threadlocal实例的线程类
public class SimpleTesterThread extends Thread {
    private void showMsg (String msg) {  
        System.out.println (msg); 
        System.out.flush(); 
    }
    public SimpleTesterThread (String threadId) {
        super(threadId);
    }
    public void run() {
        try { Thread.sleep(2000); } catch (InterruptedException ex) { }
        ThreadLocal<SimpleClassWithStaticMembers> localizedClass =
            new ThreadLocal<SimpleClassWithStaticMembers>();
        localizedClass.set(new SimpleClassWithStaticMembers());
        // repeating here to be sure we overlap all with all
        for (int ii=0; ii < 3; ii++) { 
            localizedClass.get().setTheStaticString ("Setby_" + this.getName());
            try { Thread.sleep(2000); } catch (InterruptedException ex) { }
            showMsg("            Thread [" + this.getName() + "] - " 
                + localizedClass.get().getTheStaticString() + "'.");
        }
        showMsg ("Thread [" + this.getName() 
            + "] complete. Our 'threadlocal' string is now - " 
            + localizedClass.get().getTheStaticString() + "'.");
        localizedClass.remove();
    }
}

当创建了10个SimpleTesterThread个实例(给它们不同的线程名称,如“AAAAAAAAAA”,“BBBBBBBBB”等),然后启动时,输出清楚地显示它们正在共享实例。日志输出包括:

...
            Thread [JJJJJJJJJJ] - Setby_CCCCCCCCCC'.
            Thread [DDDDDDDDDD] - Setby_JJJJJJJJJJ'.
            Thread [IIIIIIIIII] - Setby_DDDDDDDDDD'.
            Thread [GGGGGGGGGG] - Setby_IIIIIIIIII'.
            Thread [EEEEEEEEEE] - Setby_GGGGGGGGGG'.
Thread [EEEEEEEEEE] complete. Our 'threadlocal' string is now - Setby_GGGGGGGGGG'.
            Thread [HHHHHHHHHH] - Setby_GGGGGGGGGG'.
            Thread [BBBBBBBBBB] - Setby_GGGGGGGGGG'.
            Thread [FFFFFFFFFF] - Setby_GGGGGGGGGG'.
Thread [FFFFFFFFFF] complete. Our 'threadlocal' string is now - Setby_GGGGGGGGGG'.
...
            Thread [JJJJJJJJJJ] - Setby_GGGGGGGGGG'.
Thread [JJJJJJJJJJ] complete. Our 'threadlocal' string is now - Setby_GGGGGGGGGG'.
Thread [HHHHHHHHHH] complete. Our 'threadlocal' string is now - Setby_GGGGGGGGGG'.
            Thread [GGGGGGGGGG] - Setby_GGGGGGGGGG'.
Thread [GGGGGGGGGG] complete. Our 'threadlocal' string is now - Setby_GGGGGGGGGG'.
            Thread [IIIIIIIIII] - Setby_GGGGGGGGGG'.
            Thread [CCCCCCCCCC] - Setby_GGGGGGGGGG'.
Thread [CCCCCCCCCC] complete. Our 'threadlocal' string is now - Setby_GGGGGGGGGG'.
Thread [AAAAAAAAAA] complete. Our 'threadlocal' string is now - Setby_GGGGGGGGGG'.
            Thread [DDDDDDDDDD] - Setby_GGGGGGGGGG'.
Thread [DDDDDDDDDD] complete. Our 'threadlocal' string is now - Setby_GGGGGGGGGG'.
Thread [IIIIIIIIII] complete. Our 'threadlocal' string is now - Setby_GGGGGGGGGG'.
============== all threads complete.

我没有包含创建,启动和加入线程的类 - 如果感觉有用,我很乐意编辑添加它。

2 个答案:

答案 0 :(得分:5)

具有单独的实例没有帮助,因为所有实例都是相同的静态字段。可变的静力学是邪恶的。

如果你真的无法改变类,你可能只想使用一个锁,这样每个客户端就可以一次使用一个静态字段。如果你想拥有静态字段的不同实例,那么你可能需要使用类加载器(另一个明显的解决方案是重写字节码,这甚至不太令人愉快)。

答案 1 :(得分:2)

ThreadLocal类不会更改静态字段修饰符的行为。静态字段将继续在类的多个实例中具有一个化身,因为它们是在初始化类时创建和初始化的。

为了更多地了解这一点,我们使用基于每个线程的Map来内部管理ThreadLocal成员。 get()和set()调用在此Map上运行。 ThreadLocal中没有“魔法”,静态成员失去了具有单一化身的属性。设置为ThreadLocal变量时,静态成员只需添加到Map中,以便在线程执行的代码的另一部分中进行引用。这不会阻止第二个线程获取对静态成员的引用,并对成员字段执行操作。

正是由于这个原因,Tom Hawtin的陈述应该得到认真对待 - 可变静力学并不构成良好的设计。

PS:看一下Thread,ThreadLocal和ThreadLocal.ThreadLocalMap类的实现将有助于清除对ThreadLocal对象行为的任何误解。