java 中为什么说,String是线程安全的
首先我们谈谈线程安全.
线程安全估计都是老生常谈了.
例子:一个人的账户有银行卡和存折两个终端.共计5000元.当银行卡中取出2000的瞬间,服务器还没更新余额(此时应是3000),存折也向服务器发出取钱3000的请求,服务器查看余额剩余5000给予取款操作.此时存折出钞前,之前银行卡的余额更新到服务器.此时服务器余额为3000元,下个0.05秒存折出钞完毕,将余额更新到服务器,此时余额为2000元.
好了,取了5000元,共5000元最后卡里还剩2000.然后你就进去了.
2.线程安全的解决方案
(1)以上例子,最常用的解决方案就是使用同步语句块 synchronized来保护取款过程.当一个线程在访问该账号正在执行取款操作时,其他线程想要进行取款只能等待.这种方式是以时间换空间.
(2)使用ThreadLocal,ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。这种方式是以空间换时间.
好了,思维拓展结束.现在我们看问题,String为什么是线程安全的呢?
遍寻String的源码,你可能找不到几个synchronized关键字来.是的.我是在引导你走(2).
可是也没有看到ThreadLocal相关的东西啊.这是因为String在设计的时候字符的存储是放在char数组中的,而这个char数组是final的.也就是说,无论你是怎样的多线程环境.你做得修改操作
对原有的对象是没有任何影响的.因为String 在更改的时候是指向了另一个对象(也就是另一个char数组).每个线程修改的时候都是独立的一个char数组,这用的正是(2)方法.所以这个final的char数组才是String 安全的根本.
还有StringBuffer和StringBuilder 两个都是放弃了使用final char数组.所以二者在拼接字符串的时候省内存(不用拼一个字符 new一个char数组了).但是这样就线程不安全了.这就是StringBuilder.而为什么stringBuilder是线程安全的呢.这是因为他的线程安全是用的(1)方法(大量的synchronized).
这里需要解释下,(2)方案为什么能解决线程安全问题.也可以理解为为什么用了final的char数组就可以实现线程安全.原理其实很简单.我们反过来想.线程不安全的情景,无非就是在对同一个对象同时修改的时候,其中一个线程只操作了一半,另一个线程也开始操作.因为线程的执行时间是cpu分配的时间片.并不是谁先执行就是谁先结束.完全有可能a线程先执行修改,在修改没完成的时候b线程也开始操作该对象.结果B线程先修改完毕.想象下.最终的结果可以肯定不是预期的结果.只有这种情况才会出现线程不安全.而当每个线程拥有一个实例的时候.这种情况将不复存在.