在 Java 5.0 提供了 java.util.concurrent(简称JUC)并发编程容器包,在此包中增加了在并发编程中很常用的工具类,用于定义类似于线程的自定义子系统,包括线程池,异步 IO 和轻量级任务框架;还提供了设计用于多线程上下文中的 Collection 实现等


我们拿其中常用的list容器来作为例子,进行高压(高并发测试),使用代码和JMter测试工具来分别测试线程安全问题

demo GitHub https://github.com/zhang-xiaoxiang/gaobinfa

1:使用代码展示list容器的并发测试结果

package com.example.gaobinfa.common;

import com.example.gaobinfa.annoations.NotThreadSafe;
import lombok.extern.slf4j.Slf4j;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

@Slf4j
@NotThreadSafe//这是自定义注解,标注为不安全(可以不使用)
public class ArrayListExample {

    /**
     * 请求总数
     */
    public static int clientTotal = 5000;

    /**
     * 同时并发执行的线程数
     */
    public static int threadTotal = 200;

    private static List<Integer> list = new ArrayList<>();

    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newCachedThreadPool();
        final Semaphore semaphore = new Semaphore(threadTotal);
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal; i++) {
            final int count = i;
            executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    update(count);
                    semaphore.release();
                } catch (Exception e) {
                    log.error("exception", e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        //甚至可能抛异常 java.lang.ArrayIndexOutOfBoundsException: 33
        log.info("size:{}", list.size());
    }

    private static void update(int i) {
        list.add(i);
    }
}

运行结果如下(多次测试也没有达到预期的5000)

甚至抛异常

 2:使用Jmter测试工具测试(我们这里以web接口作为测试,方便使用JMter)

接口代码

package com.example.gaobinfa.api;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.ArrayList;
import java.util.List;

/**
 * CommonApi:这里模拟常用的集合类,在高并发下体现不安全的演示示例
 *
 * @author zhangxiaoxiang
 * @date: 2019/08/11
 */
@RestController
@RequestMapping("/get")
@Slf4j
public class CommonApi {

    Integer i = 0;
    List<Integer> list = new ArrayList<>();

    /**
     * 调用一次接口对集合add操作
     *
     * @return
     */
    @RequestMapping("/testarraylist")
    public Object testArrayList() {
        list.add(i);
        i++;
        //在没有高并发下正常请求5000次是从1到5000的
        log.info("数组长度=" + list.size());
        return "数组长度=" + list.size();
    }
}

 工具测试调试

 

 测试结果(仍然没有达到5000,和预期不一致,线程不安全)

结论:List容器是存在安全问题的

使用并发容器改善 list对应的并发容器是CopyOnWriteArrayList

1代码测试

package com.example.gaobinfa.common.concurrent;

import com.example.gaobinfa.annoations.ThreadSafe;
import lombok.extern.slf4j.Slf4j;

import java.util.List;
import java.util.concurrent.*;

@Slf4j
@ThreadSafe
public class CopyOnWriteArrayListExample {

    /**
     * 请求总数
     */
    public static int clientTotal = 5000;

    /**
     * 同时并发执行的线程数
     */
    public static int threadTotal = 200;

    private static List<Integer> list = new CopyOnWriteArrayList<>();

    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newCachedThreadPool();
        final Semaphore semaphore = new Semaphore(threadTotal);
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal; i++) {
            final int count = i;
            executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    update(count);
                    semaphore.release();
                } catch (Exception e) {
                    log.error("exception", e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        log.info("size:{}", list.size());
    }

    private static void update(int i) {
        list.add(i);
    }
}

 运行结果(多次测试)

2JMter工具测试

 

package com.example.gaobinfa.api;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * ConcurrentApi:测试JUC并发容器改善后的测试
 *
 * @author zhangxiaoxiang
 * @date: 2019/08/11
 */
@RestController
@RequestMapping("/get")
@Slf4j
public class ConcurrentApi {
    Integer i = 0;
     List<Integer> list = new CopyOnWriteArrayList<>();

    /**
     * 调用一次接口对集合add操作
     *
     * @return
     */
    @RequestMapping("/copyonwritearraylist")
    public Object testArrayList() {
        list.add(i);
        i++;
        //在没有高并发下正常请求5000次是从1到5000的
        log.info("使用并发集合JUC改善后数组长度=" + list.size());
        return "使用并发集合JUC改善后数组长度=" + list.size();
    }
}

 工具(多次)测试结果

 

 这下和预期的一致了,线程安全了,其他像map集合等类似的操作即可,我的文件结构

 

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐