笔者最近在看jmeter源码,对多线程处理部分的了解记录如下。
Part1 线程与多线程概念
提到线程先来看一下进程(线程的容器)的概念,进程是一个具有独立功能的程序关于某个数据集合的一次运行活动。它可以申请和拥有系统资源,是一个动态的概念,是一个活动的实体。它不只是程序的代码,还包括当前的活动,通过程序计数器的值和处理寄存器的内容来表示。
对于jmeter来说,运行中的jmeter程序实例便是一个进程。而该进程中会包含大量线程。
线程是程序执行流的最小单位,是一组命令的集合。在jmeter中一个线程可以用来执行一个测试用例。在起停等基本属性设置的基础上,线程运行时会根据testtree解析出的sample取样器按需执行流程中包含的测试请求。
不同的资料中对线程状态有不完全相同的描述,基本上分为五种状态:新建 开始(等待) 运行 挂起 和 停止。
在jmeter/压测宝中,需要有高并发性,大量的线程并行执行,其中每个线程代表一个VU。这就涉及到多线程的概念。
对于多线程,在java中有两种实现方式,即,一种是通过继承thread类;另一种是实现Runnable接口。由于java单继承机制,继承thread实现有更多的局限性,一般采用Runnable接口方式。同时,实现了Runnable接口的类,可以通过thread类构造方法public Thread(Runnable target) 来转化运行。
Jmeter中线程的实现也是采用了Runnable接口方式。
并发大量线程执行相同任务,需要统一管理配置多线程的状态,便需要引入线程组(threadgroup)的概念。
<Ps:实现Runnable接口比继承Thread类所具有的优势:
1):适合多个相同的程序代码的线程去处理同一个资源
2):可以避免java中的单继承的限制
3):增加程序的健壮性,代码可以被多个线程共享,代码和数据独立。>
Part2:线程组
Java中有threadgroup的概念,ThreadGroup是一个类,它的目的是提供关于线程组的信息。ThreadGroup API比较薄弱,它并没有比Thread提供了更多的功能。它有两个主要的功能:一是获取线程组中处于活跃状态线程的列表;二是为线程设置捕获异常处理器。
而这薄弱的API不能满足JMeter的需求,所以JMeter是自定义的threadgroup类。用来实现对一组线程(也就是一个测试实例的并发线程)的管理。它针对线程组的各项属性,如线程的延时启动时间/开始时间/结束时间/线程总数/正在运行的线程数/线程启动时间间隔进行设置。当有线程延时启动时,先创建等量守护线程,由守护线程根据时间创建对应的用户线程并依次启动;没有延时启动时,直接创建用户线程并依次启动。
另外,线程组提供scheduleThread(This will schedule the time for the JMeterThread.),对线程启动进行时间安排;并能够控制线程的相关方法:启动 暂停 停止 等待停止线程, 获取活动的线程数等。
上面提到,实现runnable接口的方式适合多个相同的程序代码的线程去处理同一个资源。多线程共享资源便容易引起线程安全问题。
Part3:线程安全
线程安全问题产生原理:
线程的工作原理,jvm有一个main memory,而每个线程有自己的working memory,一个线程对一个variable进行操作时,都要在自己的working memory里面建立一个copy,操作完之后再写入main memory。多个线程同时操作同一个variable,就可能会出现不可预知的结果。
比如,两个线程同时为一个数组添加项,当前数组长度为0.两个线程同时对其操作,会分别在自己的工作内存中拷贝一份进行添加。两线程分别添加完返回,此时数组长度已经是2,但是每个线程返回的数组长度依然是1,这就引发了问题。
Jmeter中保证线程安全的方式:
一)在start方法执行之前定义好线程内部的变量;
Eg :
启动时间 终止时间等参数是通过第一种赋值,如下jmeter源码描述:
The following variables are set by StandardJMeterEngine.
This is done before start() is called, so the values will be published to the thread safely
线程的变量是在start方法执行之前,这样保证变量的作用范围仅限于线程内。
二)用volatile关键字来标识线程共用的变量(PS:当我们使用volatile关键字去修饰变量的时候,所以线程都会直接读取该变量并且不缓存它);
Eg:
线程共享的变量如running /onErrorStopTest等是采用volatile关键字。
三)通过synchronized同步代码块或方法体(PS:用synchronized的关键是建立一个monitor,这个monitor可以是要修改的variable也可以其他你认为合适的object比如method,然后通过给这个monitor加锁来实现线程安全,每个线程在获得这个锁之后,要执行完 从mainmemory load到workingmemory -> use&assign -> store到mainmemory 的过程,才会释放它得到的锁。这样就实现了所谓的线程安全。);
Eg:
对于方法/代码块,如 static synchronized void incrNumberOfThreads()新增活动线程方法等,多线程共用的代码,需要用synchronized 进行同步。
笔者对多线程了解尚浅,文中不当之处敬请指正,万分感谢!