java無鎖並發
我想大声告诉你
我想大声告诉你 2017-05-17 10:01:02
0
1
664

下面程式碼裡無鎖和有鎖比是更好的實作嗎?我用jmeter每秒20個請求,無鎖定程式碼執行test()裡的sleep操作的輸出大部分與500毫秒差別巨大,而有鎖定程式碼的輸出基本上就是500毫秒相差1,2毫秒的樣子,這個問題很怪異啊....

@Controller
@RequestMapping("/bench/")
public class BenchController {

    @Autowired
    private FlowService flowService;

    private static Object[] lockObj;
    private static AtomicReference<Integer>[] locks;

    static {
        lockObj = new Object[100];
        for (int i = 0; i < lockObj.length; i++) {
            lockObj[i] = new Object();
        }

        locks = new AtomicReference[100];
        for (int i = 0; i < locks.length; i++) {
            locks[i] = new AtomicReference<Integer>(null);
        }
    }

    @RequestMapping("a")
    @ResponseBody
    public long a(int id) throws Exception {
        long start = System.currentTimeMillis();
        int index = id % 100;
        long inner=0;
        synchronized (lockObj[index]) {
            inner=test();
        }
        long result = System.currentTimeMillis() - start;
        System.out.println("all: "+result+" inner: "+inner);
        return result;
    }

    @RequestMapping("b")
    @ResponseBody
    public long b(int id) throws Exception {
        long start = System.currentTimeMillis();
        AtomicReference<Integer> lock=locks[id % 100];
        while (!lock.compareAndSet(null, id)) {}
        long inner=test();
        boolean flag=lock.compareAndSet(id, null);
        long result = System.currentTimeMillis() - start;
        System.out.println("all: "+result+" inner: "+inner+" flag:"+flag);
        return result;
    }

    public long test()throws Exception{
        long innerstart = System.currentTimeMillis();
        Thread.sleep(500);
        System.out.println(System.currentTimeMillis()-innerstart);
        return System.currentTimeMillis()-innerstart;
    }
}
我想大声告诉你
我想大声告诉你

全部回覆(1)
曾经蜡笔没有小新

1.首先,明確兩個問題,synchronized 一般不是跟AtomicXX類進行比較,更多的是跟ReentrantLock這個類進行比較,網上關於這2者的比較很多,可以自行google之。

2.問題中關於無鎖跟有鎖的疑問,測試代碼b中的代碼是有問題的,

  • 對於方法a,synchronized程式碼區塊來說,鎖被第一個進來的執行緒持有後,後續執行緒請求取得鎖會被阻塞掛起,直到前面一個執行緒釋放鎖,後續的執行緒會恢復執行,由於鎖的存在,20個請求類似於順序執行,這一層由jvm調度

  • 對於方法b,cas操作是非阻塞的,方法中的while循環其實是一直在執行(不斷嘗試進行cas操作),而我們知道,死循環是會消耗cpu資源的,並發數越多,執行緒越多,這裡的cas操作越多,必然導致cpu使用率飆升,方法b中的程式碼由jmeter測試的時候理論上應該一直由20個活躍的工作執行緒存在,cpu與執行緒模型是另外一個主題,線程數的調優是jvm一個比較高級的話題,感興趣可以自行google之

  • 說說ReentrantLock與synchronized:通常情況下在高並發下,ReentrantLock比synchronized擁有更好的性能,而且ReentrantLock提供來一些synchronized並不提供的功能(鎖超時自動放棄等),示例代碼中可以減少sleep的時間,從而模擬更短停頓,更高的並發,500ms對於人來說很短,對於cpu來說基本就是天文數字了,基本用“慢如蝸牛”來形容也不為過,修改類一下示例代碼:

package com.gzs.learn.springboot;

import java.util.LinkedList;
import java.util.Random;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.LockSupport;
import java.util.concurrent.locks.ReentrantLock;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
@RequestMapping("/bench/")
public class BenchController {
    private Random random = new Random();
    private static Object[] lockObj;
    private static AtomicReference<Integer>[] locks;
    private static ReentrantLock[] reentrantLocks;
    static {
        lockObj = new Object[100];
        for (int i = 0; i < lockObj.length; i++) {
            lockObj[i] = new Object();
        }

        locks = new AtomicReference[100];
        for (int i = 0; i < locks.length; i++) {
            locks[i] = new AtomicReference<Integer>(null);
        }
        reentrantLocks = new ReentrantLock[100];
        for (int i = 0; i < reentrantLocks.length; i++) {
            reentrantLocks[i] = new ReentrantLock();
        }
    }

    @RequestMapping("a/{id}")
    @ResponseBody
    public long a(@PathVariable("id") int id) throws Exception {
        long start = System.currentTimeMillis();
        int index = id % 100;
        long inner = 0;
        synchronized (lockObj[index]) {
            inner = test();
        }
        long result = System.currentTimeMillis() - start;
        System.out.println("all: " + result + " inner: " + inner);
        return result;
    }

    @RequestMapping("b/{id}")
    @ResponseBody
    public long b(@PathVariable("id") int id) throws Exception {
        long start = System.currentTimeMillis();
        id = id % 100;
        AtomicReference<Integer> lock = locks[id];
        int b = 0;
        while (!lock.compareAndSet(null, id)) {
            b = 1 + 1;
        }
        long inner = test();
        boolean flag = lock.compareAndSet(id, null);
        long result = System.currentTimeMillis() - start;
        System.out.println("all: " + result + " inner: " + inner + " flag:" + flag);
        System.out.println(b);
        return result;
    }

    @RequestMapping("c/{id}")
    @ResponseBody
    public long c(@PathVariable("id") int id) throws Exception {
        long start = System.currentTimeMillis();
        id = id % 100;
        ReentrantLock lock = reentrantLocks[id];
        lock.lock();
        long inner = test();
        lock.unlock();
        long result = System.currentTimeMillis() - start;
        System.out.println("all: " + result + " inner: " + inner);
        return result;
    }

    public long test() throws Exception {
        long innerstart = System.currentTimeMillis();
        Thread.sleep(0, 100);
        // Thread.sleep(500);
        System.out.println(System.currentTimeMillis() - innerstart);
        return System.currentTimeMillis() - innerstart;
    }
}
  • 方法c是用ReentrantLock實現的,絕大多少情況下ReentrantLock比synchronized高效

  • juc(java.util.concurrent)中的核心類別Aqs(AbstractQueuedSynchronizer)是一個基於隊列的並發包,默認線程在鎖定競爭(自旋)超過1000納秒的時候會被park(掛起操作),從而減少cpu頻繁的執行緒切換,可以嘗試調整方法c中的sleep的時間參數。

  • 測試方法,本機沒有裝jmeter,用apache ab做的測試,測試指令:

ab -n 100 -c 20  http://localhost:8080/bench/a/10
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板
關於我們 免責聲明 Sitemap
PHP中文網:公益線上PHP培訓,幫助PHP學習者快速成長!