1. 程式人生 > >Java高併發及測試程式碼

Java高併發及測試程式碼

公司的妹子不會做併發測試。作為一名程式猿看著有點幹捉急。併發測試是多個人同時訪問一個服務,這不就是多執行緒嗎!於是靈光一現使用多執行緒來寫併發測試程式碼。想想心理都有點小激動咧。效果比工具還好,廢話不多說貼程式碼

新增Maven依賴
<!--新增OKHttp.jar包-->
<dependency>

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

</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);

}