通过String的不变性案例剖析Java变量的可变性

阅读本文之前,请先看以下几个问题:

1、String变量是什么稳定?final修饰变量时的稳定性指的又是什么稳定,是引用?照样内存地址?照样值?

2、java工具举行重赋值或者改变属性时在内存中是若何实现的?

3、以下是AQS中的一个方式代码,叨教第一次进入这个方式时,执行到return的时刻,t==node? head==tail?node.prev==head?head.next==node?这四个对照分别是true照样false?

 1 private Node enq(final Node node) {
 2         for (;;) {
 3             Node t = tail;
 4             if (t == null) { // Must initialize
 5                 if (compareAndSetHead(new Node()))
 6                     tail = head;
 7             } else {
 8                 node.prev = t;
 9                 if (compareAndSetTail(t, node)) {
10                     t.next = node;
11                     return t;
12                 }
13             }
14         }
15     }

若是你对以上几个问题一切能很清晰的答出来,那么就不用阅读本文了,否则还请逐步读来。

正文

1、从工作中的问题出发

写这篇文章的原由,是工作中遇到了一个场景,大要是这样的。

公司项目用Apollo作为设置中央,现在有5个短信验证码的发送场景,每个场景都有最大发送次数上限,由于场景差别以是这个上限也相互差别。每次发送短信前都市校验一下已发送次数是否已经跨越这个上限,而且上限可能随时动态调整以是需要将每个场景的发送次数上限作为apollo设置项设置起来。而作为一个有追求的开发攻城狮,不能容忍通过场景码用if else这种粗拙的手段来获取设置项,以是BZ想到了Map。开端实现是这样的:

 1 @Component
 2 @Getter
 3 public class ApolloDemo {
 4 
 5     @Value("scene1.times")
 6     private String scene1Times;
 7     @Value("scene2.times")
 8     private String scene2Times;
 9     @Value("scene3.times")
10     private String scene3Times;
11     @Value("scene4.times")
12     private String scene4Times;
13     @Value("scene5.times")
14     private String scene5Times;
15 
16     public static final Map<String, String> sceneMap = new HashMap<>();
17 
18     @PostConstruct
19     public void initMap () {
20         sceneMap.put("scene_code1", scene1Times);
21         sceneMap.put("scene_code2", scene2Times);
22         sceneMap.put("scene_code3", scene3Times);
23         sceneMap.put("scene_code4", scene4Times);
24         sceneMap.put("scene_code5", scene5Times);
25     }
26 }

但BZ是一个颇具智慧的攻城狮,这样的代码很明显存在问题:由于String是稳定的,以是在initMap中初始化了Map之后,若是后续成员变量scene1Times改变了值,Map中的值是不会同步改变的。以是BZ接纳了如下的改进版:

 1 package com.mydemo;
 2 
 3 import lombok.Getter;
 4 import org.springframework.beans.factory.annotation.Value;
 5 import org.springframework.stereotype.Component;
 6 import org.springframework.stereotype.Service;
 7 
 8 import javax.annotation.PostConstruct;
 9 import java.lang.reflect.Method;
10 import java.util.HashMap;
11 import java.util.Map;
12 
13 @Component
14 @Getter
15 public class ApolloDemo {
16 
17     @Value("scene1.times")
18     private String scene1Times;
19     @Value("scene2.times")
20     private String scene2Times;
21     @Value("scene3.times")
22     private String scene3Times;
23     @Value("scene4.times")
24     private String scene4Times;
25     @Value("scene5.times")
26     private String scene5Times;
27 
28     private static final Map<String, String> sceneMap = new HashMap<>();
29 
30     @PostConstruct
31     public void initMap () {
32         sceneMap.put("scene_code1", "getScene1Times");
33         sceneMap.put("scene_code2", "getScene2Times");
34         sceneMap.put("scene_code3", "getScene3Times");
35         sceneMap.put("scene_code4", "getScene4Times");
36         sceneMap.put("scene_code5", "getScene5Times");
37     }
38 
39     public String getTimesByScene(String sceneCode){
40         String methodName = sceneMap.get(sceneCode);
41         try {
42             Method method = ApolloDemo.class.getMethod(methodName);
43             Object result = method.invoke(this, null);
44             return (String)result;
45         } catch (Exception e) {
46             e.printStackTrace();
47         }
48         return "";
49     }
50 }

通过反射挪用get方式来获取实时的apollo设置值,功效算是交付出去了。但问题却刚刚开始。

我们都知道String是不能变的,那它为什么不能变呢?由于它的类由final修饰不能继续,而它用于存放字符串的成员变量char[]也是由final修饰的。继续追问,final修饰的变量不能变是指什么不能变?不能变有两种,一种是引用不能变,一种是值不能变。此处谜底是引用不能变。实在Java中,不管是给工具赋值,照样给工具中的属性赋值,赋的值实在都是引用。针对String的不能变是引用不能变的结论,通过一个例子就可以证实:

 1 public static void main(String[] args) {
 2         String text = "text";
 3         System.out.println(text);
 4         try {
 5             Field value = text.getClass().getDeclaredField("value");
 6             value.setAccessible(true);
 7             char[] valueArr = (char[])value.get(text);
 8             valueArr[1]='a';
 9         } catch (Exception e) {
10             e.printStackTrace();
11         }
12         System.out.println(text);
13     }

执行效果:

text
taxt

BZ通过反射改变了String的值,说明它的值是可变的,若是用反射执行 value.set(text, “aaa”),则会报错不让改,即引用不能变。

由此问题1获得了解答,内存地址只是用于疑惑人的,一个工具建立完成之后,其内存地址是不能改变的,直到被接纳后重新分配。

 

2、问题2与问题3一起剖析

Sentry实时应用错误跟踪系统在Kubernetes中私有化部署

针对问题3的方式,BZ用内存示意图来剖析:

1)、刚进入enq方式时,tail、head、node的内存结构是这样:

通过String的不变性案例剖析Java变量的可变性

 

2)、走完第一遍循环并之后,完成了对head和tail的赋值,此时内存漫衍是这样:

通过String的不变性案例剖析Java变量的可变性

 

 3)、进入第二遍循环中,走完第三行代码 Node t = tail 和node.prev=t之后的内存漫衍如下,由于赋值都是引用赋值,以是局部变量t和node.prev均指向了new Node()的引用地址。

通过String的不变性案例剖析Java变量的可变性

 

 4)、走完CAS tail之后是这样,即CAS是将tail的引用从new Node()改为了 node:

通过String的不变性案例剖析Java变量的可变性

 

 5)、走完最后一行t.next=node,内存漫衍如下所示,t指向的一直都是new Node(),而将node赋值给t.next之后,node和new Node()就组成了一个双向链表,new Node()是头,正好head指向它;node是尾,正好tail指向它,至此完成了AQS中双向链表的构建。

通过String的不变性案例剖析Java变量的可变性

 

 通过上面5张截图的转变,信赖能对于问题2已经有谜底了,至于问题3的谜底,看最后一张图也就水落石出了,t==node? head==tail?node.prev==head?head.next==node?谜底分别是:false;false;true;true。

本文到此为止,其中有形貌不清楚的或者明白不到位的地方,还请列位看官批评指正,谢谢!

原创文章,作者:28x29新闻网,如若转载,请注明出处:https://www.28x29.com/archives/5162.html