级别: 高级会员
UID: 9732
精华: 0
发帖: 1095
威望: 2442
霏币: 2172
活跃度: 1119
技术分: 0
非凡币: 0
交易值: 0
在线时间: 765(小时)
注册时间: 2004-07-06
最后登录: 2018-04-24
30楼  发表于: 2018-03-03 10:48:10
楼主大神啊
硬件区3月优秀会员|资讯区3月热心会员
级别: 优秀会员

UID: 68643
精华: 0
发帖: 24074
威望: 16013
霏币: 51939.8
活跃度: 28048
技术分: 0
非凡币: 1077
交易值: 0
在线时间: 41906(小时)
注册时间: 2004-10-27
最后登录: 2018-04-24
31楼  发表于: 2018-03-04 09:02:28
我只能 乱看了 最主要的是不懂啊
承君此诺,必守一生!
级别: 超级会员
UID: 115534
精华: 0
发帖: 346
威望: 9275
霏币: 1274
活跃度: 423
技术分: 0
非凡币: 0
交易值: 0
在线时间: 98(小时)
注册时间: 2005-03-15
最后登录: 2018-04-18
32楼  发表于: 2018-03-06 17:51:12
我也是感兴趣编程,目前看看PYTHON3  据说是最简单的语言。
呵呵虫
体育竞猜区
级别: 竞猜管理组

UID: 109
精华: 0
发帖: 3864
威望: 4246
霏币: 351356.4
活跃度: 3879
技术分: 81
非凡币: 2101
交易值: 0
在线时间: 1859(小时)
注册时间: 2012-01-01
最后登录: 2018-04-23
33楼  发表于: 2018-03-07 09:27:34
引用
引用第33楼syitian于2018-03-06 17:51发表的  :
我也是感兴趣编程,目前看看PYTHON3  据说是最简单的语言。

还可以,java,python难度其实都差不多
优秀的开源框架都挺多的
为了而努力
呵呵虫
体育竞猜区
级别: 竞猜管理组

UID: 109
精华: 0
发帖: 3864
威望: 4246
霏币: 351356.4
活跃度: 3879
技术分: 81
非凡币: 2101
交易值: 0
在线时间: 1859(小时)
注册时间: 2012-01-01
最后登录: 2018-04-23
34楼  发表于: 2018-03-08 11:35:30
记录一下几个tips
1. 删除HashMap中的元素有技巧
先看项目中我写的代码
复制代码
  1.     private void deleteUsers() {
  2.         HashMap<String, baseBean> userlist = xxx.getEndUsers();
  3.         for (String userId : userlist.keySet()) {
  4.             EndUser tmpUser = (EndUser)userlist.get(userId);
  5.             if (tmpUser.getRole().equals(EndUser.ROLE_ORG_ADMIN)) {
  6.                 continue;
  7.             }
  8.             userlist.remove(userId);           //本地map删除用户
  9.             xxx.delUser_byID(userId);        //向服务器发送删除请求    
  10.         }        
  11.     }

代码的目的就是先获取当前的用户列表并存放到一张HashMap中,然后遍历map进行删除用户操作,同时本地的map表也进行相应的同步
但是当删除第一条数据后,进行第二次循环时会报一个java.util.ConcurrentModificationException
单线程居然也要报这个错误??具体原因可以网上baidu,介绍的很详细
下面时候我的一个解决方法:
复制代码
  1.     private void deleteUsers() {
  2.         HashMap<String, baseBean> userlist = xxx.getEndUsers();
  3.         Iterator<String> iter = userlist.keySet().iterator();
  4.         while (iter.hasNext()) {
  5.             String userId = iter.next();
  6.             EndUser tmpUser = (EndUser)userlist.get(userId);
  7.             iter.remove();
  8.             xxx.delUser_byID(userId);    
  9.         }        
  10.     }

当然还可以使用ConcurrentHashMap来代替,待研究

2. JsonObject的null天坑
现在项目中使用Json是家常便饭了,其中一个类JsonObject类一定使用频率非常高
最近开发过程中经常出现返回的JsonObject中个别String字段的值是null,上游的同学说改起来困难,于是就适配

例如当前是一个用户实例,我取一个用户信息字段(description)是这样取得:
this.description = source.getString("description") == null?"":source.getString("description");
后来发现有些字符串字段的null还是取不到,继续适配
this.description = (source.getString("description") == null && source.getString("description").equals(null))?"":source.getString("description");
虽然效果好了一些,但还是有些null的情况没有办法处理
debug的时候发现明明这个值是个null,上述判断却无能为力,最后意外发现这部分捕获不到的null的类型是jsonobject$null
真是一万个mmp在脑子里飘过,还好Json这个第三方开源库把这个坑填上了,JsonObject提供了一个isNull()方法来判断
最后只能做个函数来做判断如下:
复制代码
  1.     public static boolean isJsonValueNull(JSONObject jo, String key) {
  2.         Object o = jo.get(key);
  3.         if (o != null && !o.equals(null) && !jo.isNull(key)) {
  4.             return false;
  5.         } else {
  6.             return true;
  7.         }
  8.     }

有了这个函数,Json这个快把我折腾疯了的天坑总算堵上了。
呵呵虫
体育竞猜区
级别: 竞猜管理组

UID: 109
精华: 0
发帖: 3864
威望: 4246
霏币: 351356.4
活跃度: 3879
技术分: 81
非凡币: 2101
交易值: 0
在线时间: 1859(小时)
注册时间: 2012-01-01
最后登录: 2018-04-23
35楼  发表于: 2018-03-11 23:02:59
最近做项目需要对ArrayList进行扩展,所以顺便就研究了一下ArrayList的源代码,并做个记录
1. ArrayList虽然展现出来的是一个变长的集合,可以随时增删元素,但实际上ArraylList的底层实现仍然是一个数组。


如上图, elementData这个字段正是ArrayList用来存储数据的字段。
2. 既然ArrayList的底层是一个数组,那么是如何来实现变长的呢,往里面添加数据会不会有问题呢?
    1)ArrayList在构造的时候可以指定数组的大小,当然无参数会构造一个空的Object数组,即上图中的DEFAULTCAPACITY_EMPTY_ELEMENTDATA数组
    
复制代码
  1.     public ArrayList(int initialCapacity) {
  2.         if (initialCapacity > 0) {
  3.             this.elementData = new Object[initialCapacity];
  4.         } else if (initialCapacity == 0) {
  5.             this.elementData = EMPTY_ELEMENTDATA;
  6.         } else {
  7.             throw new IllegalArgumentException("Illegal Capacity: "+
  8.                                                initialCapacity);
  9.         }
  10.     }
  11.     /**
  12.      * Constructs an empty list with an initial capacity of ten.
  13.      */
  14.     public ArrayList() {
  15.         this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
  16.     }

    2)如果没有指定ArrayList的大小或者指定的size小小于10,那么往ArrayList里面加元素时,ArrayList的默认大小为DEFAULT_CAPACITY(10个元素)
    3)当添加新的元素时,如果元素的数量超过了elementData 数组的上限,那么需要进行数组扩容,然后再进行数组的拷贝。
复制代码
  1.     public E get(int index) {
  2.         rangeCheck(index);
  3.         return elementData(index);
  4.     }
  5.    public boolean add(E e) {
  6.         ensureCapacityInternal(size + 1);  // Increments modCount!!
  7.         elementData[size++] = e;
  8.         return true;
  9.     }
  10.     private void ensureCapacityInternal(int minCapacity) {
  11.         ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
  12.     }
  13.     private void ensureExplicitCapacity(int minCapacity) {
  14.         modCount++;
  15.         // overflow-conscious code
  16.         if (minCapacity - elementData.length > 0)
  17.             grow(minCapacity);
  18.     }
  19.     private void grow(int minCapacity) {
  20.         // overflow-conscious code
  21.         int oldCapacity = elementData.length;
  22.         int newCapacity = oldCapacity + (oldCapacity >> 1);
  23.         if (newCapacity - minCapacity < 0)
  24.             newCapacity = minCapacity;
  25.         if (newCapacity - MAX_ARRAY_SIZE > 0)
  26.             newCapacity = hugeCapacity(minCapacity);
  27.         // minCapacity is usually close to size, so this is a win:
  28.         elementData = Arrays.copyOf(elementData, newCapacity);
  29.     }

   4)上面的代码是从源代码中截取出来的,扩容的重点是grow方法,扩容后的容量为int newCapacity = oldCapacity + (oldCapacity >> 1),即扩容后的容量为扩容前的容量的1.5倍
   5)新的容量计算完毕后,通过调用lementData = Arrays.copyOf(elementData, newCapacity),将数据拷贝到新的数组中,然后进行elementData[size++] = e赋值
   6)根据上述总结,ArrayList容量的扩容依次是10,15,22,33, 49......

3. 关于ArrayList中元素的增、删、查及效率问题探讨
先贴上相关代码:
复制代码
  1.     public void add(int index, E element) {
  2.         rangeCheckForAdd(index);
  3.         ensureCapacityInternal(size + 1);  // Increments modCount!!
  4.         System.arraycopy(elementData, index, elementData, index + 1,
  5.                          size - index);
  6.         elementData[index] = element;
  7.         size++;
  8.     }
  9.     public E remove(int index) {
  10.         rangeCheck(index);
  11.         modCount++;
  12.         E oldValue = elementData(index);
  13.         int numMoved = size - index - 1;
  14.         if (numMoved > 0)
  15.             System.arraycopy(elementData, index+1, elementData, index,
  16.                              numMoved);
  17.         elementData[--size] = null; // clear to let GC do its work
  18.         return oldValue;
  19.     }
  20.     public boolean remove(Object o) {
  21.         if (o == null) {
  22.             for (int index = 0; index < size; index++)
  23.                 if (elementData[index] == null) {
  24.                     fastRemove(index);
  25.                     return true;
  26.                 }
  27.         } else {
  28.             for (int index = 0; index < size; index++)
  29.                 if (o.equals(elementData[index])) {
  30.                     fastRemove(index);
  31.                     return true;
  32.                 }
  33.         }
  34.         return false;
  35.     }

从上述代码可以看出,ArrayList查询的方法直接从数组中查找对应的索引的元素,所以查找速度非常快。
如果直接增加元素,那么在扩容后直接将元素放到了数组的尾部,但是如果将元素放到某个索引位置,在数组扩容后还需要进行数组的后移拷贝
删除元素与增加元素类似,在删除某个元素后后面的元素需要向前面移动
所以ArrayList的增加和删除元素的效率为O(n)级别的

4. ArrayList如果不指定大小的情况下,会按照上述10,15,22,33, 49以1.5倍的数列自动扩容。如果ArrayList需要添加的元素比较多那么会频繁的进行扩容影响执行效率
   另外如果一个存放了很多元素的ArrayList,将其中大多数元素删除后,但是其数组仍然会占用内容空间,因此需要进行空间的释放
因此在实际项目中的做法如下:
   1)如果将要加入ArrayList的元素的规模已经可以预知,例如执行元素查找操作并将其存入ArrayList,那么可以提前使用 ensureCapacity(int minCapacity)手动进行扩容,这样避免ArrayList后续频繁自动扩容带来的效率损耗。
    2)如果一个大的ArrayList中的元素已经被删除了很多,后面预期短时间不会添加元素进行扩容,那么需要对数组资源进行释放,可以调用trimToSize来进行空间释放,代码如下:
复制代码
  1.     public void trimToSize() {
  2.         modCount++;
  3.         if (size < elementData.length) {
  4.             elementData = (size == 0)
  5.               ? EMPTY_ELEMENTDATA
  6.               : Arrays.copyOf(elementData, size);
  7.         }
  8.     }


好了现在对ArrayList的来龙去脉例包括实现机制、效率问题等知识点有了认识,那么后面对ArrayList进行改造也就方便多了
呵呵虫
体育竞猜区
级别: 竞猜管理组

UID: 109
精华: 0
发帖: 3864
威望: 4246
霏币: 351356.4
活跃度: 3879
技术分: 81
非凡币: 2101
交易值: 0
在线时间: 1859(小时)
注册时间: 2012-01-01
最后登录: 2018-04-23
36楼  发表于: 2018-03-17 10:22:10
近期在学习过程中一个有个疑问:
1.现在SSM框架这么火爆,不论是各种书籍还是demo提到最后的临门一脚基本都是JSP或者其它方式的视图渲染,但是为什么大多数网站的web的交互返回的却是JSON?
2. 既然没有说好的服务器将渲染好的浏览器可识别的html返回,而是只返回了JSON数据,那么这些数据是如何显示出来的?

原来这些年我错过了java web发展的3个阶段
从最初的servlet+JSP +JavaBean 到SSH框架再到SSM框架再到目前这种前后端分离的框架。

这样做的好处:
1. 前后端的开发可以相互独立,后端只需要提供接口和固定的Json格式,前端工程师就可以开始干活了,职责比较明确
2. 更重要的是MVC中view这一层由浏览器来完成,从而减轻了服务器的压力

前后端分离涉及到的一些前端的技术我已经记下了,以后有时间学习学习,但是后端采用restful API技术肯定是没跑了
级别: 新手上路
UID: 208339
精华: 0
发帖: 354
威望: 1
霏币: -1114
活跃度: 349
技术分: 0
非凡币: 0
交易值: 0
在线时间: 55(小时)
注册时间: 2011-06-28
最后登录: 2018-04-24
37楼  发表于: 2018-03-17 10:25:32
上个世纪的程序员可是按科学家对待啊,现今都变成农民了,
呵呵虫
体育竞猜区
级别: 竞猜管理组

UID: 109
精华: 0
发帖: 3864
威望: 4246
霏币: 351356.4
活跃度: 3879
技术分: 81
非凡币: 2101
交易值: 0
在线时间: 1859(小时)
注册时间: 2012-01-01
最后登录: 2018-04-23
38楼  发表于: 2018-03-18 08:14:00
引用
引用第37楼xfourteen于2018-03-17 10:25发表的  :
上个世纪的程序员可是按科学家对待啊,现今都变成农民了,

说的对,现在各种培训机构导致程序员直接贬值为码农了
所以想混下去得做个有深度的码农
呵呵虫
体育竞猜区
级别: 竞猜管理组

UID: 109
精华: 0
发帖: 3864
威望: 4246
霏币: 351356.4
活跃度: 3879
技术分: 81
非凡币: 2101
交易值: 0
在线时间: 1859(小时)
注册时间: 2012-01-01
最后登录: 2018-04-23
39楼  发表于: 2018-03-18 18:27:04
搞定一只玩具:汤姆猫
最近在学习过程中看一些书比较晕乎乎的,根据我以前的学习经验学习servlet和tomcat还是理论和实践相结合比较快一些
所以要是能把Tomcat的源码在本地运行起来并进行debug那就好了
好了心动不如行动,下面就开始搞定这个玩具:
步骤1:去tomcat官网下载tomcat的源代码,这里我下载的源代码为:apache-tomcat-8.5.29-src
步骤2:在eclipse上创建一个java工程,名曰:apache-tomcat-8.5.29
步骤3:工程的根目录下右键点导入(import),选择导入文件系统导入tomcat的源码,如下图
这里我们选择导入的目录包括java(核心)、test、webapps、conf目录
其中java目录包括了tomcat和servlet的源码是核心
test目录包括了tomcat的一些测试代码,可选
webapps包括了tomcat附带的一些例子,学习初期建议选择
conf目录包括了tomcat的一些配置文件


导入后的样子如下图:


步骤4: 下面分别创建源码文件夹:src/java, src/test,如下图:



步骤5:将上面java目录下和test目录下的所有内容剪切到上面两个源码文件夹下

步骤6:在工程下面创建一个lib目录,并且下载下图中tomcat依赖的jar包列表,拷贝到lib目录



步骤7:配置工程的buildpath



经过上述步骤后,eclipse会自动构建workspace, 经过漫长的等待后tocat的工程如下:



note:
1. 为了工程简洁这里可以把空的java和test目录删掉了
2. 这里还有一些error,但都是test目录下的error,不影响tomcat整体的运行

步骤8:根据需要配置conf目录下的server.xml文件,例如我们这里运行tomcat打开的是8082端口
    <Connector port="8082" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" />
步骤9:配置conf目录下的tomcat-users.xml文件,这里配置一个用户方便后续tomcat的测试



步骤10:现在这个可爱的玩具汤姆猫基本配置好了,下面运行Bootstrap.java看看
  

看样子貌似tomcat起来了,下面访问以下8082端口试试:
 

访问一切正常

步骤11:进入tomcat的管理界面:
点击主页上的server status按钮,输入用户名和密码(即之前创建的hehec/hehec),访问一下看看


访问后的页面如下:

[ 此帖被hehec在2018-03-18 18:33重新编辑 ]
呵呵虫
体育竞猜区
级别: 竞猜管理组

UID: 109
精华: 0
发帖: 3864
威望: 4246
霏币: 351356.4
活跃度: 3879
技术分: 81
非凡币: 2101
交易值: 0
在线时间: 1859(小时)
注册时间: 2012-01-01
最后登录: 2018-04-23
40楼  发表于: 2018-03-18 19:13:47
玩具弄好了,还得看中不中用,能不能满足咱们的需求才行,咱们还需要能debug才行
下面以刚才访问的http://127.0.0.1:8082/manager/status为例
经过查找其对应的servlet的class文件为:StatusManagerServlet


下面赶紧在init函数以及doget方法打上断点,看看servlet初始化以及servlet运行时的效果
先debug运行起来,看到tomcat运行起来以后还是蛮壮观的,有好多的线程在运行(这些需要后面去学习了)



在学习servlet的过程中老生常谈的话题是,init会引用一个servletConfig并调用init()进行初始化
下面是init的调试效果:(init的调用栈以及servletConfig的真面貌)



下面再看看service方法的调用情况
  


好了经过两个小时调教出来的这只汤姆猫果然没有让我们失望,还是能够满足我们后续debug和学习目的的
作为一个玩具还是蛮称职的,后面就能够愉快的进行玩耍了
一念成佛,一念成魔!
级别: 超级会员

UID: 176800
精华: 0
发帖: 2012
威望: 11626
霏币: 414.2
活跃度: 2148
技术分: 20
非凡币: 0
交易值: 0
在线时间: 385(小时)
注册时间: 2005-10-02
最后登录: 2018-04-21
41楼  发表于: 2018-03-21 09:14:52
支持和鼓励,楼 主别断更。
呵呵虫
体育竞猜区
级别: 竞猜管理组

UID: 109
精华: 0
发帖: 3864
威望: 4246
霏币: 351356.4
活跃度: 3879
技术分: 81
非凡币: 2101
交易值: 0
在线时间: 1859(小时)
注册时间: 2012-01-01
最后登录: 2018-04-23
42楼  发表于: 2018-03-28 15:53:32
最近使用apache的httpclient过程中,需要针对一些特殊的情况做一些重发机制,在探索过程中学到了一些小东西记录一下:

第一次用断点debug 一条http请求的时直接被搞晕了,感觉一直在this.requestExecutor.execute(route, request, context, execAware)这条语句里绕圈圈,然后不知道什么时候就跳出去了,通过这次学习终于搞明白了,先看一下httpclient在执行一条http请求时的一个调用栈



难怪之前被折腾的晕晕的原来调用层次这么深,而且有的exec会循环执行。
先看一下这个流程的两端,前面是自己的应用程序调用httpclient的方法去执行,然后进入了几个xxxExec的execute方法,最后从xxxExex方法出来由HttpRequestExector将http请求发出去。

下面就要具体分析一下这几个Exec了
通过查看源码发现这几个都实现ClientExecChain接口的execute方法


其调用关系为层次封装,下一级的exec对象作为当前exec的一个requestExecutor对象,当前对象在执行execute()方法的过程中,会处理当前Exec的一些功能并调用requestExecutor(下一级Exec)的execute()方法,具体如下图所示:



本次研究的对象是httpclient的消息重发,在这几个Exec中RetryExec就是用来负责异常重发的,下面就来分析一下其源码


其执行逻辑比较简单,就是一个死循环,如果执行成功就直接返回response,如果抛出异常就进入重试判断环节,如果满足重试条件就继续循环调用,否则就抛出异常退出循环。

下个话题是一个httpclient终端是如何形成这种层次的调用关系,这个要从一个httpclient的构造说起
一般来说构造一个CloseableHttpClient有两种方法:
1)CloseableHttpClient httpclient = HttpClients.createDefault();
2)CloseableHttpClient httpclient = HttpClients.custom().setxxx().setxxx().build();
上面无论哪种方法最终都会调用HttpClientBuilder的build()方法去初始化一个httpclient
这个build方法略复杂下面把无关的代码剔除只保留关心的代码如下:


因为这里会一层一层的封装,所以后面也会一层一层的调用。由于httpclient的配置关系,上面debug使用的httpclient没有封装ServiceUnavailableRetryExec以及BackoffStrategyExec

那么这个build方法中的这些Exec按照这样的顺序去封装有什么意义呢?换个问题将顺序颠倒了行不行?
粗略的看了一下每个Exec的相关代码,越先封装的Exec越接近底层,后封装的Exec接近应用逻辑
例如做redirect的Exec做重新向,需要http执行完毕等response返回以后才能再做重定向,而底层的MainExec则需要处理一些http连接池的事情后再发送http请求。


最后一个话题再次回到重试这个主题,谈谈如何去实现重试
在retryExec的代码中看到了retryHandler.retryRequest(ex, execCount, context)这个调用,其入参分别为抛出的异常、执行次数以及http的上下文
其中retryHandler其实就是一个HttpRequestRetryHandler接口,需要自己去实现retryRequest
这些参数给了我们操作的空间,比如可以设置重试次数,遇到什么样的exception进行重试等等,下面这个例子是我设置的一些重试规则
复制代码
  1.         HttpRequestRetryHandler httpRequestRetryHandler = new HttpRequestRetryHandler() {
  2.             @Override
  3.             public boolean retryRequest(IOException exception, int executionCount, HttpContext context) {
  4.                 if (executionCount > 3) {
  5.                     // 重试超过3次就返回
  6.                     return false;
  7.                 }
  8.                 if (exception instanceof InterruptedIOException) {
  9.                     // An input or output transfer has been terminated
  10.                     return false;
  11.                 }
  12.                 if (exception instanceof UnknownHostException) {
  13.                     // Unknown host 修改代码让不识别主机时重试,实际业务当不识别的时候不应该重试,再次为了演示重试过程,执行会显示retryCount次下面的输出
  14.                     System.out.println("不识别主机重试");
  15.                     return true;
  16.                 }
  17.                 if (exception instanceof ConnectException) {
  18.                     // Connection refused
  19.                     return true;
  20.                 }
  21.                 if (exception instanceof ConnectionClosedException) {
  22.                     return true;
  23.                 }
  24.                 if (exception instanceof SSLException) {
  25.                     // SSL handshake exception
  26.                     return false;
  27.                 }
  28.                 
  29.                 if (exception instanceof NoHttpResponseException) {
  30.                     // SSL handshake exception
  31.                     return true;
  32.                 }
  33.                 
  34.                 HttpClientContext clientContext = HttpClientContext.adapt(context);
  35.                 HttpRequest request = clientContext.getRequest();
  36.                 boolean idempotent = !(request instanceof HttpEntityEnclosingRequest);
  37.                 if (idempotent) {
  38.                     // Retry if the request is considered idempotent
  39.                     return true;
  40.                 }
  41.                 return false;
  42.             }
  43.         };


想要配置httpclient的重试,只需要使用第二种构造方法在构造httpclient的时候将上面的retryHandlerset配置即可,例如:
CloseableHttpClient client = HttpClients.custom().setRetryHandler(httpRequestRetryHandler).setxxx().build();
null……
级别: 青铜长老

UID: 3135
精华: 0
发帖: 92014
威望: 17080
霏币: 5753.5
活跃度: 123920
技术分: 266
非凡币: 1
交易值: 0
在线时间: 8375(小时)
注册时间: 2004-06-28
最后登录: 2018-04-24
43楼  发表于: 2018-03-29 23:29:20
楼主水平很高啊
[ 此帖被TENTEN100FUN在2018-04-03 05:07重新编辑 ]
呵呵虫
体育竞猜区
级别: 竞猜管理组

UID: 109
精华: 0
发帖: 3864
威望: 4246
霏币: 351356.4
活跃度: 3879
技术分: 81
非凡币: 2101
交易值: 0
在线时间: 1859(小时)
注册时间: 2012-01-01
最后登录: 2018-04-23
44楼  发表于: 2018-04-04 21:17:35
Jmeter代码研究之ThreadLocal的使用

最近研究Jmeter运行脚本相关的源码,Jmeter作为性能工具首先是多线程运行的
每个线程(测试任务)都会有自己的执行进度和执行顺序,同时还会有自己的一些变量
问题1:
使用过Jmeter的都知道在编写beanshell测试脚本或者JSR223脚本会用到类似ctx,vars,Sampler, SamplerResult这样的系统变量
那么问题来了,这些变量明显是Jmeter自己自定义的,那么脚本语言引擎是如何识别的呢?看一下如下代码:


上述方法的调用代码:
复制代码
  1.     public String execute() throws InvalidVariableException {
  2.         JMeterContext context = JMeterContextService.getContext();
  3.         SampleResult previousResult = context.getPreviousResult();
  4.         Sampler currentSampler = context.getCurrentSampler();
  5.         return execute(previousResult, currentSampler);
  6.     }

从上面Jmeter的代码可以看出上面的变量其实都对应着一个实例,在脚本引擎执行脚本前会将变量对应的实例设置到引擎中。
顺便解释一下这些变量的含义:
vars变量可以进行类似map的操作,用于保存或者取出脚本运行过程中的临时变量
ctx,获取当前执行线程的上下文
Sampler用于获取当前正在执行的sampler对象
SamplerResult 用于取出上一个sampler执行的结果

问题2:
上面的这些变量和本次文章的内容有什么关系呢?
通过查看上述代码发现这些变量最终都来自于一个地方那就是context(即JMeterContext),这个context保存了当前测试线程的一些变量,如下图:



很明显context中的这些属性与测试线程相关的,所以必须要求线程安全,即每个线程都会有自己的context实例,而且不共享

问题3:JmeterContext如何做到多个测试线程之间线程安全的?
在上面的代码中注意到线程的context实例来源于JMeterContextService.getContext(),下面再继续分析JMeterContextService这个类


代码很简单这里声明了一个final static的ThreadLocal对象,获取context时调用其get()方法。

问题4:上面的调用ThreadLocal的get()方法如何保证每个线程的context唯一且线程安全呢?
第一次看到这行代码的时候层一度以为这个是单例模式,怀疑Jmeter代码的正确性,当了解ThreadLocal的作用以后豁然开朗。
ThreadLocal这个类就是Java设计出来保证线程安全的,ThreadLocal本身没有多线程的概念,它只是把当前线程对应的数据(或者副本)保存起来(与线程关联),保证每个线程都有自己唯一的一个副本。
先看看ThreadLocal的构造,如下图:


其中:
threadLocalHashCode是其唯一的变量,其实也是一个hash值,用于后续在map中快速找到对应的副本
重要的方法包括get(),set()和initialValue()方法
同时还有两个内部类,其中ThreadLocalMap比较重要,用来保定到线程中存放副本

下面来看一下ThreadLocal的几个重要方法



1. ThreadLocal是支持泛型的,很容易理解我们的副本在这里是一个JmeterContexxt类型,在别的地方就是另外一种类型,所以使用泛型没毛病
2. 给ThreadLocal设置副本的两种方法:
1)直接调用set(T value)
2)在没有进行set的情况下强行使用get()方法,会调用setInitialValue()方法,并最终调用initialValue()方法,ThreadLocal的initialValue默认返回一个null,这里在声明一个ThreadLocal实例时可以重写其initialValue方法,上面的JmeterContextService就是这么干的。
上面的两种方式,都有相同的代码:
复制代码
  1.         Thread t = Thread.currentThread();     //获取当前的线程
  2.         ThreadLocalMap map = getMap(t);     //获取当前线程的ThreadLocalMap对象
  3.         if (map != null)
  4.             map.set(this, value);      //如果当前线程存在map,那么将当前ThreadLocal作为key值,将副本保存
  5.         else
  6.             createMap(t, value);       //先为当前线程创建map,然后再将当前ThreadLocal作为key,将副本作为value保存


然后再看get()方法:
复制代码
  1.     public T get() {
  2.         Thread t = Thread.currentThread();   //获取当前的线程
  3.         ThreadLocalMap map = getMap(t);    //获取当前线程的map
  4.         if (map != null) {
  5.             ThreadLocalMap.Entry e = map.getEntry(this);     //以当天的ThreadLocal做为key值查找Entry
  6.             if (e != null) {
  7.                 @SuppressWarnings("unchecked")
  8.                 T result = (T)e.value;      //获取线程副本
  9.                 return result;
  10.             }
  11.         }
  12.         return setInitialValue();    //如果没有复制,返回初始值
  13.     }


问题5:Thread以及ThreadLocal的关系是什么?
如下图所示:


线程的副本保存在了Thread的ThreadLocals这张map中,通过ThreadLocal作为媒介将副本取出来
同时ThreadLocal可以定位为不同种类的数据
呵呵虫
体育竞猜区
级别: 竞猜管理组

UID: 109
精华: 0
发帖: 3864
威望: 4246
霏币: 351356.4
活跃度: 3879
技术分: 81
非凡币: 2101
交易值: 0
在线时间: 1859(小时)
注册时间: 2012-01-01
最后登录: 2018-04-23
45楼  发表于: 2018-04-04 21:23:05
最后配置一个Jmeer脚本,线程组个数为2
再debug看一下Jmeter中的ThreadLocal的运行情况






从图中可以得到结论:
1. Threadlocal实例为在3个线程中相同,为同一个ThreadLocal实例
2. 不同的进程最终获取的ThreadContext对象不同,所以这样可以保证线程的安全性

注:
上面的第一个进程为主线程
后面两个进程为实际测试执行的线程
呵呵虫
体育竞猜区
级别: 竞猜管理组

UID: 109
精华: 0
发帖: 3864
威望: 4246
霏币: 351356.4
活跃度: 3879
技术分: 81
非凡币: 2101
交易值: 0
在线时间: 1859(小时)
注册时间: 2012-01-01
最后登录: 2018-04-23
46楼  发表于: 2018-04-04 21:24:56
引用
引用第43楼TENTEN100FUN于2018-03-29 23:29发表的  :
楼主水平很高啊

过奖了,我也是新手一个
级别: 青铜长老

UID: 204755
精华: 0
发帖: 32953
威望: 14043
霏币: 620.5
活跃度: 33572
技术分: 7
非凡币: 261
交易值: 0
朋友圈: 霏翔佳乐园
在线时间: 3726(小时)
注册时间: 2011-06-28
最后登录: 2018-04-24
47楼  发表于: 2018-04-11 09:46:55
    围观hehec~~大牛~~
嗟尔君子无恒安息靖共尔位好是正直神之听之介尔景福
级别: 高级会员
UID: 216994
精华: 0
发帖: 3135
威望: 2692
霏币: 3133
活跃度: 2923
技术分: 0
非凡币: 159
交易值: 0
在线时间: 151(小时)
注册时间: 2013-12-31
最后登录: 2018-04-24
48楼  发表于: 2018-04-12 09:23:10
做软件很辛苦也应该很有乐趣
级别: 初级会员
UID: 199199
精华: 0
发帖: 437
威望: 50
霏币: -1019
活跃度: 395
技术分: 0
非凡币: 0
交易值: 0
在线时间: 42(小时)
注册时间: 2010-01-01
最后登录: 2018-04-24
49楼  发表于: 2018-04-14 09:32:47
努力!加油!
嗟尔君子无恒安息靖共尔位好是正直神之听之介尔景福
级别: 高级会员
UID: 216994
精华: 0
发帖: 3135
威望: 2692
霏币: 3133
活跃度: 2923
技术分: 0
非凡币: 159
交易值: 0
在线时间: 151(小时)
注册时间: 2013-12-31
最后登录: 2018-04-24
50楼  发表于: 2018-04-24 08:50:26
再来看看你的心路历程