JAVA并发-内置锁和ThreadLocal

简介:

上一篇博客讲过,当多个线程访问共享的可变变量的时候,可以使用锁来进行线程同步。那么如果线程安全性存在的3个前提条件不同时存在的话,自然就不需要考虑线程安全性了。或者说如果我们能够将某个共享变量变为局部变量,那么自然线程安全性问题就不存在了。 
我们把“诸如将全局变量变为局部变量”这种将某个对象封闭在一个线程中的技术称为线程封闭,在《JAVA并发编程实践》中是这样说的,这么说有一定道理。但我还是想说说个人对锁和线程封闭的理解: 
内置锁的机制是为了“使得多个线程都能够访问共享变量,而且能够留下对这个共享变量的影响”。 
线程封闭的机制是为了“使得多个线程都能够使用共享变量,但不需要留下对这个共享变量的影响”。
 
说到底,两种机制应对的代码使用场景不同,而非是解决线程安全问题的两种方案。 
线程封闭机制强调局部的概念,就是在写代码的时候,尽量使用局部变量代替全局变量(这种叫做栈封闭),如果一定要使用全局变量,而又想让多个线程之间在访问共享变量的时候互不影响,那就使用ThreadLocal<T>。ThreadLocal<T>提供了一种方式,可以让线程在操作共享变量时,复制该共享变量的一个副本到线程自己的栈空间,以后就操作这个副本空间来代替共享空间。这是一种封闭的手段,但我更加认为是一种代码场景。说个例子吧: 

Java代码   收藏代码
  1. @UnThreadSafe  
  2. pulic class TestNum{  
  3.     private int num=0;  
  4.     public int getNextNum(){  
  5.         ++num;  
  6.         return num;  
  7.     }  
  8.     public static void main(String [] args){  
  9.         TestNum tn=new TestNum();  
  10.         TestClass tc1=new TestClass(tn);  
  11.         TestClass tc2=new TestClass(tn);  
  12.         TestClass tc3=new TestClass(tn);  
  13.           
  14.         tc1.start();  
  15.         tc2.start();  
  16.         tc3.start();  
  17.     }  
  18.     class TestClass extends Thread{  
  19.         private TestNum tn;  
  20.         public TestClass(TestNum tn){  
  21.             this.tn=tn;  
  22.         }  
  23.         public void run(){  
  24.             for(int i=0;i<3;i++){  
  25.                 System.out.println("thread-"+Thread.currentThread().getName()+"-"+tn.getNextNum());  
  26.             }  
  27.         }  
  28.     }  
  29. }  


这是一个线程不安全的代码,输出的结果无法预测。将这段代码变为线程安全可以有几种方案,举其中两个例子来说明本文的内容: 

Java代码   收藏代码
  1. @ThreadSafe  
  2. pulic class TestNum{  
  3.     private int num=0;  
  4.     public synchronized int getNextNum(){  
  5.         ++num;  
  6.         return num;  
  7.     }  
  8.     public static void main(String [] args){  
  9.         TestNum tn=new TestNum();  
  10.         TestClass tc1=new TestClass(tn);  
  11.         TestClass tc2=new TestClass(tn);  
  12.         TestClass tc3=new TestClass(tn);  
  13.           
  14.         tc1.start();  
  15.         tc2.start();  
  16.         tc3.start();  
  17.     }  
  18.     class TestClass extends Thread{  
  19.         private TestNum tn;  
  20.         public TestClass(TestNum tn){  
  21.             this.tn=tn;  
  22.         }  
  23.         public void run(){  
  24.             for(int i=0;i<3;i++){  
  25.                 System.out.println("thread-"+Thread.currentThread().getName()+"-"+tn.getNextNum());  
  26.             }  
  27.         }  
  28.     }  
  29. }  


对于上面的代码,我们使用同步机制的来实现线程安全:tc1-3这三个线程都在访问同一个num空间,并且他们都在干一件事,那就是让这个空间的数字增加,并且能够留下自己的影响(即num++)。此时,输出的结果最大值一定是num=9(具体哪个线程贡献的哪一段就不知道了)。 

Java代码   收藏代码
  1. @ThreadSafe  
  2. pulic class TestNum{  
  3.     private ThreadLocal<Integer> num=new ThreadLocal<Integer>(){  
  4.         public Integer initialValue(){  
  5.             return 0;  
  6.         }  
  7.     };  
  8.     public  int getNextNum(){  
  9.         num.set(num.get()+1);  
  10.         return num.get();  
  11.     }  
  12.     public static void main(String [] args){  
  13.         TestNum tn=new TestNum();  
  14.         TestClass tc1=new TestClass(tn);  
  15.         TestClass tc2=new TestClass(tn);  
  16.         TestClass tc3=new TestClass(tn);  
  17.           
  18.         tc1.start();  
  19.         tc2.start();  
  20.         tc3.start();  
  21.     }  
  22.     class TestClass extends Thread{  
  23.         private TestNum tn;  
  24.         public TestClass(TestNum tn){  
  25.             this.tn=tn;  
  26.         }  
  27.         public void run(){  
  28.             for(int i=0;i<3;i++){  
  29.                 System.out.println("thread-"+Thread.currentThread().getName()+"-"+tn.getNextNum());  
  30.             }  
  31.         }  
  32.     }  
  33. }  


对于上面的代码,我们使用线程封闭来完成,tc1-3这三个线程访问共享变量在自己栈空间的一个副本,他们都在干自己的事(不是一件事),只不过在干自己的事的过程中使用到了共享变量这个载体,而且他们也不关心最终对共享变量产生了多少影响。此时,输出的结果最大值一定是num=3(每个线程在干自己的事情)。 

综上,个人觉得使用锁还是线程封闭去解决线程安全问题,终究是业务逻辑的不同,或者说是代码功能的不同。我们要掌握的就是有这些个解决代码安全行的方法,然后放到具体的场景下去应用。 



本文转自农夫山泉别墅博客园博客,原文链接:http://www.cnblogs.com/yaowen/p/6293836.html,如需转载请自行联系原作者

相关文章
|
7月前
|
Java 大数据 Go
从混沌到秩序:Java共享内存模型如何通过显式约束驯服并发?
并发编程旨在混乱中建立秩序。本文对比Java共享内存模型与Golang消息传递模型,剖析显式同步与隐式因果的哲学差异,揭示happens-before等机制如何保障内存可见性与数据一致性,展现两大范式的深层分野。(238字)
216 4
|
消息中间件 算法 安全
JUC并发—1.Java集合包底层源码剖析
本文主要对JDK中的集合包源码进行了剖析。
|
10月前
|
Java API 调度
从阻塞到畅通:Java虚拟线程开启并发新纪元
从阻塞到畅通:Java虚拟线程开启并发新纪元
488 83
|
10月前
|
存储 Java 调度
Java虚拟线程:轻量级并发的革命性突破
Java虚拟线程:轻量级并发的革命性突破
511 83
|
12月前
|
机器学习/深度学习 消息中间件 存储
【高薪程序员必看】万字长文拆解Java并发编程!(9-2):并发工具-线程池
🌟 ​大家好,我是摘星!​ 🌟今天为大家带来的是并发编程中的强力并发工具-线程池,废话不多说让我们直接开始。
423 0
|
7月前
|
缓存 安全 Java
如何理解Java中的并发?
Java并发指多任务交替执行,提升资源利用率与响应速度。通过线程实现,涉及线程安全、可见性、原子性等问题,需用synchronized、volatile、线程池及并发工具类解决,是高并发系统开发的关键基础。(238字)
383 5
|
存储 Java
【源码】【Java并发】【ThreadLocal】适合中学者体质的ThreadLocal源码阅读
前言 下面,跟上主播的节奏,马上开始ThreadLocal源码的阅读( ̄▽ ̄)" 内部结构 如下图所示,我们可以知道,每个线程,都有自己的threadLocals字段,指向ThreadLocalMap
631 81
【源码】【Java并发】【ThreadLocal】适合中学者体质的ThreadLocal源码阅读
|
10月前
|
SQL 缓存 安全
深度理解 Java 内存模型:从并发基石到实践应用
本文深入解析 Java 内存模型(JMM),涵盖其在并发编程中的核心作用与实践应用。内容包括 JMM 解决的可见性、原子性和有序性问题,线程与内存的交互机制,volatile、synchronized 和 happens-before 等关键机制的使用,以及在单例模式、线程通信等场景中的实战案例。同时,还介绍了常见并发 Bug 的排查与解决方案,帮助开发者写出高效、线程安全的 Java 程序。
528 0
|
11月前
|
Java 物联网 数据处理
Java Solon v3.2.0 史上最强性能优化版本发布 并发能力提升 700% 内存占用节省 50%
Java Solon v3.2.0 是一款性能卓越的后端开发框架,新版本并发性能提升700%,内存占用节省50%。本文将从核心特性(如事件驱动模型与内存优化)、技术方案示例(Web应用搭建与数据库集成)到实际应用案例(电商平台与物联网平台)全面解析其优势与使用方法。通过简单代码示例和真实场景展示,帮助开发者快速掌握并应用于项目中,大幅提升系统性能与资源利用率。
309 6
Java Solon v3.2.0 史上最强性能优化版本发布 并发能力提升 700% 内存占用节省 50%