• 变量和线程安全


      最近在看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只是为了保存当前线程私有的某种状态。

  • 相关阅读:
    android图片优化
    Android多线程断点下载的代码流程解析
    文件下载
    图片上传
    DomHelper
    SAX解析类:SaxHelper
    Android开发之画图的实现
    匿名内部类与equals之学习要扎实
    方法构造和方法重载之奥特曼与大boss之战
    排序之那些令人凌乱的那些小程序
  • 原文地址:https://www.cnblogs.com/winkey4986/p/3518805.html
Copyright © 2020-2023  润新知