ThreadLocal,静态变量,实例变量,局部变量的线程安全

之前都是业务层次开发,现在公司进行的网络编程,一下子要了解太多java底层的东西并进行应用,我现在边学习边应用。由于知识能力有限,在上次发博客时出现了一个小小的纰漏,而这个纰漏被细心的博友发现了。

首先感谢你的关注,其次非常感谢你的建议和批评。其实上次博客中说道要线程安全的取得缓冲变量确实有安全取得某变量的意思,不过那个例子只是一个讲解Socket应用的小示例。如果真的要保证变量安全,使用静态变量,这好像有点不正常了。

其实这一下子就围绕在了一个话题上面,那就是变量的线程安全性。现在就一个个来说。

首先要肯定的是除了ThreadLocal和局部变量安全以外,静态和实例变量都是不安全的。

首先来看静态变量:

Java代码  收藏代码
  1. package com;  
  2. /** 
  3.  * @说明 变量安全测试 
  4.  * @author 崔素强 
  5.  */  
  6. public class ThreadLocalTest {  
  7.       
  8.     public static void main(String[] args) {  
  9.         Runnable accumelatora = new Accumulatort();  
  10.         Thread threada = new Thread(accumelatora, "ThreadA");  
  11.         Thread threadb = new Thread(accumelatora, "ThreadB");  
  12.         threada.start();  
  13.         threadb.start();  
  14.     }  
  15. }  
  16. class Accumulatort implements Runnable {  
  17.     // 静态变量  
  18.     private static int local = 0;  
  19.     @SuppressWarnings("unchecked")  
  20.     public void run() {  
  21.          // 静态变量  
  22.         for (int i = 0; i <= 10; i++) {  
  23.             local += 1;  
  24.             try {  
  25.                 Thread.sleep(500);  
  26.             } catch (Exception e) {  
  27.             }  
  28.             System.out.println(Thread.currentThread().getName() + "-->"  
  29.                     + local);  
  30.         }  
  31.     }  
  32. }  

运行后看控制台输出,很容就发现有时候某线程使用变量时已经被另一个线程修改了。 

因为静态变量是 静态存储方式,所谓静态存储方式是指在程序运行期间分配固定的存储空间的方式。也就是说不管多少线程,访问都是一个变量,安全问题显而易见。

再说说实例变量:

Java代码  收藏代码
  1. package com;  
  2. /** 
  3.  * @说明 变量安全测试 
  4.  * @author 崔素强 
  5.  */  
  6. public class ThreadLocalTest {  
  7.     public static void main(String[] args) {  
  8.         Runnable accumelatora = new Accumulatort();  
  9.         Thread threada = new Thread(accumelatora, "ThreadA");  
  10.         Thread threadb = new Thread(accumelatora, "ThreadB");  
  11.         threada.start();  
  12.         threadb.start();  
  13.     }  
  14. }  
  15. class Accumulatort implements Runnable {  
  16.     // 实例变量  
  17.     int locals = 0;  
  18.     @SuppressWarnings("unchecked")  
  19.     public void run() {  
  20.         for (int i = 0; i <= 10; i++) {  
  21.             locals += 1;  
  22.             try {  
  23.                 Thread.sleep(1000);  
  24.             } catch (Exception e) {  
  25.             }  
  26.             System.out.println(Thread.currentThread().getName() + "-->"  
  27.                     + locals);  
  28.         }  
  29.     }  
  30. }  

也许你觉得这会安全,但是运行后安全问题你会马上发现。

实例变量为对象实例私有,在java虚拟机的堆中分配,如果在系统中只存在一个此对象的实例,在多线程环境下,就像静态变量那样,被某个线程修改后,其他线程对修改均可见,故线程非安全;如果每个线程执行都是在不同的对象中,那对象与对象之间的实例变量的修改将互不影响,所以线程安全。 而上面我们虽然是两个线程,但是对象却是一个,所以不是你想想中的安全了。

局部变量:

Java代码  收藏代码
  1. package com;  
  2. /** 
  3.  * @说明 变量安全测试 
  4.  * @author 崔素强 
  5.  */  
  6. public class ThreadLocalTest {  
  7.     public static void main(String[] args) {  
  8.         Runnable accumelatora = new Accumulatort();  
  9.         Thread threada = new Thread(accumelatora, "ThreadA");  
  10.         Thread threadb = new Thread(accumelatora, "ThreadB");  
  11.         threada.start();  
  12.         threadb.start();  
  13.     }  
  14. }  
  15. class Accumulatort implements Runnable {  
  16.     @SuppressWarnings("unchecked")  
  17.     public void run() {  
  18.         // 局部变量  
  19.         int locals = 0;  
  20.         for (int i = 0; i <= 5; i++) {  
  21.             locals += 1;  
  22.             try {  
  23.                 Thread.sleep(1000);  
  24.             } catch (Exception e) {  
  25.             }  
  26.             System.out.println(Thread.currentThread().getName() + "-->"  
  27.                     + locals);  
  28.         }  
  29.     }  
  30. }  

不行你就多运行几遍,没事的,线程安全。

每个线程执行时将会把局部变量放在各自栈帧的工作内存中,线程间不共享,所以没有安全问题。

一般多线程编程时最会想到的是ThreadLocal:

ThreadLocal使用场合主要解决多线程中数据数据因并发产生不一致问题。ThreadLocal为每个线程的中并发访问的数据提供一个副本,通过访问副本来运行业务,这样的结果是耗费了内存,单大大减少了线程同步所带来性能消耗,也减少了线程并发控制的复杂度。

该类提供了线程局部 (thread-local) 变量。这些变量不同于它们的普通对应物,因为访问某个变量(通过其 get 或set 方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本。ThreadLocal 实例通常是类中的 private static 字段,它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联。

 

Java代码  收藏代码
  1. package com;  
  2. /** 
  3.  * @说明 变量安全测试 
  4.  * @author 崔素强 
  5.  */  
  6. public class ThreadLocalTest {  
  7.     // 线程安全变量  
  8.     @SuppressWarnings("unchecked")  
  9.     public static ThreadLocal threadLocal = new ThreadLocal();  
  10.   
  11.     public static void main(String[] args) {  
  12.         Runnable accumelatora = new Accumulatort();  
  13.         Thread threada = new Thread(accumelatora, "ThreadA");  
  14.         Thread threadb = new Thread(accumelatora, "ThreadB");  
  15.         threada.start();  
  16.         threadb.start();  
  17.     }  
  18. }  
  19. class Accumulatort implements Runnable {  
  20.     @SuppressWarnings("unchecked")  
  21.     public void run() {  
  22.         // 测试线程安全  
  23.         ThreadLocal threadLocal = ThreadLocalTest.threadLocal;  
  24.         for (int i = 1; i <= 10; i++) {  
  25.             if (threadLocal.get() == null)  
  26.                 threadLocal.set(new Integer(0));  
  27.             int x = ((Integer) threadLocal.get()).intValue();  
  28.             x += 1;  
  29.             threadLocal.set(new Integer(x));  
  30.             try {  
  31.                 Thread.sleep(1000);  
  32.             } catch (InterruptedException e) {  
  33.             }  
  34.             System.out.println(Thread.currentThread().getName() + "-->"  
  35.                     + ((Integer) threadLocal.get()).intValue());  
  36.         }  
  37.     }  
  38. }  

上面的代码其实每个线程都会有自己的变量副本,所以也不会有安全问题的。 

至于它和synchronized的区别,虽然都是为了线程安全,但是却又本质的区别。

synchronized是利用锁的机制,使变量或代码块在某一时该只能被一个线程访问。而ThreadLocal为每一个线程都提供了变量的副本,使得每个线程在某一时间访问到的并不是同一个对象,这样就隔离了多个线程对数据的数据共享。而Synchronized却正好相反,它用于在多个线程间通信时能够获得数据共享。Synchronized用于线程间的数据共享,而ThreadLocal则用于线程间的数据隔离。当然ThreadLocal并不能替代synchronized,它们处理不同的问题域。synchronized是用来处理多线程环境下的数据同步,而ThreadLocal只是为了保存当前线程私有的某种状态。

posted @ 2017-03-21 10:23  Pearl_zhen  阅读(1258)  评论(0编辑  收藏  举报