Java高并发及测试代码

冰晶云梦 2019-06-27

公司的妹子不会做并发测试。做完一名程序猿看着有点干捉急。并发测试是多个人同时访问一个服务,这不就是多线程吗!于是灵光一现使用多线程来写并发测试代码。想想心理都有点小激动咧。效果比工具还好,废话不多说贴代码

添加Maven依赖
<!--添加OKHttp.jar包-->
<dependency>

<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>3.8.1</version>

</dependency>

<!-- https://mvnrepository.com/art... -->
<dependency>

<groupId>com.squareup.okio</groupId>
<artifactId>okio</artifactId>
<version>1.11.0</version>

</dependency>

<dependency>

<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.0</version>

</dependency>

先封装OKHTTP(使用CallBack思想做的封装),这个很早之前就封装了,公司移动端也是使用OKHTTP做的服务请求调用。经常遇到图片上传不了的问题,报的错是Socket连接超时的问题。解决这个问题so easy,把连接时间(KEEP_ALIVE)时间设置长一点就行了嘛!
OkHttp底层是用socket做的通信,现在很多应该的底层通信都用的Socket,例子不多说,全靠经验。

public abstract class HttpCommon {

/**
 * 设置连接超时时间为30000秒
 */
private final static int CONNECT_TIMT_OUT = 30000;

/**
 * 设置写超时时间为30000秒
 */
private final static int WRITE_TIME_OUT = 30000;


static {
    final OkHttpClient.Builder httpBuilder = new OkHttpClient.Builder();
    okHttpClient = httpBuilder.connectTimeout(CONNECT_TIMT_OUT, TimeUnit.SECONDS)
            .writeTimeout(WRITE_TIME_OUT, TimeUnit.SECONDS).build();

}

public abstract void callBack(String responseString);


/**
 * get请求
 *
 * @param url url地址
 * @param map 请求参数
 * @return 返回结果。如果为“”表示失败
 */
public void get(String url, Map<Object, Object> map) {

    url = wrapUrl(url, map);

    // 创建请求参数
    Request request = new Request.Builder().url(url).build();

    //创建请求对象
    Call call = okHttpClient.newCall(request);

    try {
        Response response = call.execute();
        if (response.isSuccessful()) {
            callBack(response.body().string());
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}

/**
 * post请求
 *
 * @param url post请求的url
 * @param t   post请求的表单实体
 * @return 返回结果。如果为“”表示失败
 */
public <T> void post(String url, Map<Object, Object> map, T t) {
    url = wrapUrl(url, map);

    String json = new Gson().toJson(t);
    RequestBody body = RequestBody.create(JSON, json);
    Request request = new Request.Builder().url(url).post(body).build();

    Response response = null;
    try {
        response = okHttpClient.newCall(request).execute();
        if (response.isSuccessful()) {
            callBack(response.body().string());
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}


/**
 * post请求
 *
 * @param url post请求的url
 * @param t   post请求的表单实体
 * @return 返回结果。如果为“”表示失败
 */
public <T> void post(String url, T t) {
    String json = new Gson().toJson(t);
    RequestBody body = RequestBody.create(JSON, json);
    Request request = new Request.Builder().url(url).post(body).build();

    Response response = null;
    try {
        response = okHttpClient.newCall(request).execute();
        if (response.isSuccessful()) {
            callBack(response.body().string());
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}


/**
 * 上传文件请求
 *
 * @param url      请求url
 * @param map      请求参数
 * @param filePath 文件路径
 * @return 返回结果。结果为""表示失败
 */
private void uploadFile(String url, Map<Object, Object> map, String filePath) {
    url = wrapUrl(url, map);

    File file = new File(filePath);
    RequestBody fileBody = RequestBody.create(OCTET, file);
    RequestBody requestBody = new MultipartBody.Builder().setType(MultipartBody.FORM)
            .addFormDataPart("image", file.getName(), fileBody).build();
    Request request = new Request.Builder().url(url).post(requestBody).build();

    execute(request);
}

/**
 * 上传多个文件请求
 *
 * @param url       请求url
 * @param map       请求参数
 * @param filePaths 文件路径
 * @return 返回结果。结果为""表示失败
 */
private void uploadFiles(String url, Map<Object, Object> map, List<String> filePaths) {
    url = wrapUrl(url, map);

    MultipartBody.Builder builder = new MultipartBody.Builder();
    builder.setType(MultipartBody.FORM);
    for (String str : filePaths) {
        File file = new File(str);
        RequestBody fileBody = RequestBody.create(OCTET, file);
        builder.addFormDataPart("image", file.getName(), fileBody);
    }
    RequestBody requestBody = builder.build();
    Request request = new Request.Builder().url(url).post(requestBody).build();

    execute(request);
}

/**
 * 执行文件上传操作
 *
 * @param request
 */
private void execute(Request request) {

    try {
        Response response = okHttpClient.newCall(request).execute();
        if (response.isSuccessful()) {
            callBack(response.body().string());
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}


/**
 * 拼接get请求url
 *
 * @param url 请求url
 * @param map 参数
 * @return 返回拼接完的url地址
 */
private String wrapUrl(String url, Map<Object, Object> map) {

    if (null == map) {
        return url;
    }

    url += "?";

    for (Map.Entry entry : map.entrySet()) {
        url += entry.getKey() + "=" + entry.getValue() + "&";
    }


    if (url.endsWith("&")) {
        url = url.substring(0, url.length() - 1);
    }
    return url;
}


/**
 * 请求客户端
 */
private static OkHttpClient okHttpClient;


/**
 * Json媒体类型
 */
private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");

/**
 * 二进制流的媒体类型
 */
private static final MediaType OCTET = MediaType.parse("application/octet-stream");

}

public class RunThread {

private final String URL;

private HttpCommon httpCommon;

private int num;

private static ThreadPoolExecutor executor = new ThreadPoolExecutor(10, 100, 1000000L, TimeUnit.SECONDS, new LinkedBlockingDeque<>());

private CountDownLatch countDownLatch;

/**
 * @param url 服务URL地址,
 * @param num 并发访问次数,一般配置50+
 */
public RunThread(String url, int num) {



    this.URL = url;
    this.num = num;
    this.countDownLatch = new CountDownLatch(num);

    httpCommon = new HttpCommon() {
        @Override
        public void callBack(String responseString) {
            System.out.println(responseString);
        }
    };



}

public void testGet(Map<Object, Object> map) {

    long startTime = System.currentTimeMillis();

    for (int i = 0; i < num; i++) {
        executor.execute(new Runnable() {
            @Override
            public void run() {
                httpCommon.get(URL, map);
                countDownLatch.countDown();
            }
        });
    }

    try {
        countDownLatch.await();
        long executeTime = System.currentTimeMillis() - startTime;
        System.out.println("一共消耗:" + executeTime +"毫秒");
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

public <T> void testPost(Map<Object, Object> map, T t) {

    long startTime = System.currentTimeMillis();

    for (int i = 0; i < num; i++) {
        executor.execute(new Runnable() {
            @Override
            public void run() {
                httpCommon.post(URL, map, t);
                countDownLatch.countDown();
            }
        });
    }

    try {
        countDownLatch.wait();
        long executeTime = System.currentTimeMillis() - startTime;
        System.out.println("一共消耗:" + executeTime +"毫秒");
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

}

public static void main(String[] args) {

    String Url = "http://localhost:8085/test/add";
    RunThread testMain = new RunThread(Url, 1000);

    // 测试Get请求
    testMain.testGet(new HashMap<>());

// // 测试POST请求、PUT请求、DELETE请求
// testMain.testPost(new HashMap<>(), null);

}

上面是并发测试代码,那么如何写高并发测试代码呢!想到两点:一个锁、一个事务。先用Oracle做实验。
<insert id="insert" parameterType="int">

insert into testa
  (aaaa, bbbb)
values
  (#{aaa}, #{aaa})

</insert>

<select id="select" resultType="int">

select max(aaaa) from testa

</select>
Service层代码,设置事务的隔离级别为不可重复读
Isolation.REPEATABLE_READ,结果报错“Could not open JDBC Connection for transaction; nested exception is java.sql.SQLException: 仅 READ_COMMITTED 和 SERIALIZABLE 是有效的事务处理级”。卧槽!还能不能一起愉快地玩耍了,Oracle居然只支持可重复读和可系列化两种事务级别,真是让人大跌眼镜。

贴一下高并发代码吧,经过实验,通过1000个并发请求,使用Durid + Lock成功1百个不到(在这里还是得喷一下阿里的技术),使用dbcp2 + Lock成功2百多个,使用dbcp2 + synchronized 竟然成功了940个。
@Autowired
private TestMapper testMapper;

//private Lock lock = new ReentrantLock();

@Transactional(isolation = Isolation.SERIALIZABLE)
public synchronized Integer test(Integer a, Integer b) {

int c = testMapper.select();
    c += 1;
    testMapper.insert(c);

    return c;

}

代码有问题,找找错误原因吧。Spring AOP执行事务,会在Service方法执行之前就开始事务,再执行Synchronized同步方法。这样会导致查询数据并没有做同步,修改成如下代码,能完美解决问题。测试得出如下代码的执行效率最高,1000个并发耗时9018毫秒
@Autowired
private TestMapper testMapper;

//private Lock lock = new ReentrantLock();

public synchronized Integer test(Integer a, Integer b) {

int c = testMapper.select();
c += 1;

update(c);

return c;

}

@Transactional(isolation = Isolation.SERIALIZABLE)
public void update(int c) {

testMapper.insert(c);

}

相关推荐

qianbingbing / 0评论 2013-03-14