最近在看spring,发现spring对bean scope的管理与struts2不同,直接体现在spring Controller默认是singleton而struts2的Action是prototype,了解到两大框架这样设计的原因是与多线程安全有关。简而言之Struts2的参数都绑定在类中的属性上,为了保证线程安全,必须每个请求都会创建一个Action实例,所以scope是prototype。而在SpringMVC中,所有请求共享一个Controller实例,参数绑定在Controller的方法里,相当于局部变量,故scope设置成singleton不会有问题。
由此延伸,做了些关于变量类型与线程安全的思考,参考网上已有资料,做了些测试,发现除了ThreadLocal和局部变量安全以外,静态和实例变量都是不安全的。现在就一个个来说。
首先来看静态变量(类变量):
1 package org.thread; 2 /** 3 * 静态变量不安全 4 * @author winkey 5 * 6 */ 7 public class StaticVar { 8 9 public static void main(String[] args) { 10 Runnable accumelatora = new Accumulatort(); 11 Thread threada = new Thread(accumelatora, "ThreadA"); 12 Thread threadb = new Thread(accumelatora, "ThreadB"); 13 threada.start(); 14 threadb.start(); 15 } 16 17 } 18 19 class Accumulatort implements Runnable { 20 // 静态变量 21 private static int local = 0; 22 @SuppressWarnings("unchecked") 23 public void run() { 24 // 静态变量 25 for (int i = 0; i <= 10; i++) { 26 local += 1; 27 try { 28 Thread.sleep(500); 29 } catch (Exception e) { 30 } 31 System.out.println(Thread.currentThread().getName() + "-->" 32 + local); 33 } 34 } 35 }
运行后看控制台输出,很容就发现有时候某线程使用变量时已经被另一个线程修改了。
因为静态变量是 静态存储方式,所谓静态存储方式是指在程序运行期间分配固定的存储空间的方式。也就是说不管多少线程,访问都是一个变量,安全问题显而易见。
再说说实例变量:
1 package org.thread; 2 /** 3 * 当scope=singleton时,实例变量也不安全 4 * @author winkey 5 * 6 */ 7 public class InstanceVar { 8 9 public static void main(String[] args) { 10 Runnable accumelatora = new Accumulatort2(); 11 Thread threada = new Thread(accumelatora, "ThreadA"); 12 Thread threadb = new Thread(accumelatora, "ThreadB"); 13 threada.start(); 14 threadb.start(); 15 } 16 17 } 18 19 class Accumulatort2 implements Runnable { 20 // 实例变量 21 int locals = 0; 22 @SuppressWarnings("unchecked") 23 public void run() { 24 for (int i = 0; i <= 10; i++) { 25 locals += 1; 26 try { 27 Thread.sleep(1000); 28 } catch (Exception e) { 29 } 30 System.out.println(Thread.currentThread().getName() + "-->" 31 + locals); 32 } 33 } 34 }
也许你觉得这会安全,但是运行后安全问题你会马上发现。结果与static情况相同
实例变量为对象实例私有,在java虚拟机的堆中分配,如果在系统中只存在一个此对象的实例,在多线程环境下,就像静态变量那样,被某个线程修改后,其他线程对修改均可见,故线程非安全;如果每个线程执行都是在不同的对象中,那对象与对象之间的实例变量的修改将互不影响,所以线程安全。 而上面我们虽然是两个线程,但是对象却是一个,所以不是你想想中的安全了。
局部变量:
1 package org.thread; 2 /** 3 * 局部变量安全 4 * @author winkey 5 * 6 */ 7 public class LocalVar { 8 9 public static void main(String[] args) { 10 Runnable accumelatora = new Accumulatort3(); 11 Thread threada = new Thread(accumelatora, "ThreadA"); 12 Thread threadb = new Thread(accumelatora, "ThreadB"); 13 threada.start(); 14 threadb.start(); 15 } 16 17 } 18 19 class Accumulatort3 implements Runnable { 20 @SuppressWarnings("unchecked") 21 public void run() { 22 // 局部变量 23 int locals = 0; 24 for (int i = 0; i <= 10; i++) { 25 locals += 1; 26 try { 27 Thread.sleep(1000); 28 } catch (Exception e) { 29 } 30 System.out.println(Thread.currentThread().getName() + "-->" 31 + locals); 32 } 33 } 34 }
不行你就多运行几遍,没事的,线程安全。
每个线程执行时将会把局部变量放在各自栈帧的工作内存中,线程间不共享,所以没有安全问题。
一般多线程编程时最会想到的是ThreadLocal:
ThreadLocal使用场合主要解决多线程中数据数据因并发产生不一致问题。ThreadLocal为每个线程的中并发访问的数据提供一个副本,通过访问副本来运行业务,这样的结果是耗费了内存,单大大减少了线程同步所带来性能消耗,也减少了线程并发控制的复杂度。
该类提供了线程局部 (thread-local) 变量。这些变量不同于它们的普通对应物,因为访问某个变量(通过其 get 或 set 方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本。ThreadLocal 实例通常是类中的 private static 字段,它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联。
1 package org.thread; 2 3 public class ThreadLocalVar { 4 // 线程安全变量 5 @SuppressWarnings("unchecked") 6 public static ThreadLocal threadLocal = new ThreadLocal(); 7 8 public static void main(String[] args) { 9 Runnable accumelatora = new Accumulatort4(); 10 Thread threada = new Thread(accumelatora, "ThreadA"); 11 Thread threadb = new Thread(accumelatora, "ThreadB"); 12 threada.start(); 13 threadb.start(); 14 } 15 16 } 17 18 class Accumulatort4 implements Runnable { 19 @SuppressWarnings("unchecked") 20 public void run() { 21 // 测试线程安全 22 ThreadLocal threadLocal = ThreadLocalVar.threadLocal; 23 for (int i = 1; i <= 10; i++) { 24 if (threadLocal.get() == null) 25 threadLocal.set(new Integer(0)); 26 int x = ((Integer) threadLocal.get()).intValue(); 27 x += 1; 28 threadLocal.set(new Integer(x)); 29 try { 30 Thread.sleep(1000); 31 } catch (InterruptedException e) { 32 } 33 System.out.println(Thread.currentThread().getName() + "-->" 34 + ((Integer) threadLocal.get()).intValue()); 35 } 36 } 37 }
上面的代码其实每个线程都会有自己的变量副本,所以也不会有安全问题的。
至于它和synchronized的区别,虽然都是为了线程安全,但是却又本质的区别。
synchronized是利用锁的机制,使变量或代码块在某一时该只能被一个线程访问。而ThreadLocal为每一个线程都提供了变量的副本,使得每个线程在某一时间访问到的并不是同一个对象,这样就隔离了多个线程对数据的数据共享。而Synchronized却正好相反,它用于在多个线程间通信时能够获得数据共享。Synchronized用于线程间的数据共享,而ThreadLocal则用于线程间的数据隔离。当然ThreadLocal并不能替代synchronized,它们处理不同的问题域。synchronized是用来处理多线程环境下的数据同步,而ThreadLocal只是为了保存当前线程私有的某种状态。