在JDK API中,总会发现一些类说明写着,线程安全或者线程不安全。如StringBuffer文档中写到“可将字符串缓冲区安全地用于多个线程可以在必要时对这些方法进行同步,因此任意特定实例上的所有操作就好像是以串行顺序发生的,该顺序与所涉及的每个线程进行的方法调用顺序一致......从 JDK 5 开始,为该类补充了一个单个线程使用的等价类,即StringBuilder。与该类相比,通常应该优先使用 StringBuilder 类,因为它支持所有相同的操作,但由于它不执行同步,所以速度更快。”那么下面试验一下,看看有什么效果。
public class PrintString {
private static String[] cstring = { "a", "b", "c", "d", "e", "f", "g" };
private StringBuilder builder = new StringBuilder();
public void print() {
for (int i = 0; i < cstring.length; i++) {
builder.append(cstring[i]);
}
System.out.println(Thread.currentThread().getName() + "-" + builder);
}
}
public class ThreadTest {
public static void main(String[] args) {
Runnable runnable = new Runnable() {
PrintString printer = new PrintString();
public void run() {
printer.print();
}
};
for (int i = 0; i < 5; i++) {
new Thread(runnable).start();
}
}
}
结果如下:
Thread-0-abcdefg
Thread-1-abcdefgabcdefg
Thread-2-abcdefgabcdefgabcdefg
Thread-3-abcdefgabcdefgabcdefgabcdefg
Thread-4-abcdefgabcdefgabcdefgabcdefgabcdefg
只有Thread-0线程输出的结果是我们期望的,而输出的是每次都追加的,因为建立的PrintString对象是线程共享的,一个线程改变了其成员变量builder值,下一个线程正巧读到了修改后的builder,所以会追加输出。那么要想得到我们期望的结果,有几种解决方案:
1,将StringBuilder builder变为方法的局部变量。
public void print() {
StringBuilder builder = new StringBuilder();
for (int i = 0; i < cstring.length; i++) {
builder.append(cstring[i]);
}
System.out.println(Thread.currentThread().getName() + "-" + builder);
}
2,将线程类成员变量拿到run方法中,这时PrintString printer引用是线程内的局部变量;
public static void main(String[] args) {
Runnable runnable = new Runnable() {
public void run() {
PrintString printer = new PrintString();
printer.print();
}
};
for (int i = 0; i < 5; i++) {
new Thread(runnable).start();
}
}
上述测试,我们发现,存在线程不安全的成员变量的类用于多线程时是不安全的,不安全体现在这个线程不安全的成员变量可能发生非原子性的操作,而变量定义在方法内也就是局部变量是线程安全的。在使用struts1时,不推荐创建成员变量,因为action是单例的,如果创建了成员变量,就会存在线程不安全的隐患,而struts2是每一次请求都会创建一个action,就不用考虑线程安全的问题。所以,日常开发中,通常需要考虑成员变量或者说全局变量在多线程环境下,是否会引发一些问题。