1. 程式人生 > >和朱曄一起復習Java併發(三):鎖(含鎖效能測試)

和朱曄一起復習Java併發(三):鎖(含鎖效能測試)

這個專題我發現怎麼慢慢演化為效能測試了,遇到任何東西我就忍不住去測一把。本文我們會大概看一下各種鎖資料結構的簡單用法,順便也會來比拼一下效能。

各種併發鎖

首先,我們定一個抽象基類,用於各種鎖測試的一些公共程式碼:

  • 我們需要使用鎖來保護counter和hashMap這2個資源
  • write欄位表示這個執行緒是執行寫操作還是讀操作
  • 每一個執行緒都會執行loopCount次讀或寫操作
  • start的CountDownLatch用於等待所有執行緒一起執行
  • finish的CountDownLatch用於讓主執行緒等待所有執行緒都完成
@Slf4j
abstract class LockTask implements Runnable {
    protected volatile static long counter;
    protected boolean write;
    protected static HashMap<Long, String> hashMap = new HashMap<>();
    int loopCount;
    CountDownLatch start;
    CountDownLatch finish;

    public LockTask(Boolean write) {
        this.write = write;
    }

    @Override
    public void run() {
        try {
            start.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for (int i = 0; i < loopCount; i++) {
            doTask();
        }
        finish.countDown();
    }

    abstract protected void doTask();
}

下面我們實現最簡單的使用synchronized來實現的鎖,拿到鎖後我們針對hashMap和counter做一下最簡單的操作:

@Slf4j
class SyncTask extends LockTask {
    private static Object locker = new Object();

    public SyncTask(Boolean write) {
        super(write);
    }

    @Override
    protected void doTask() {
        synchronized (locker) {
            if (write) {
                counter++;
                hashMap.put(counter, "Data" + counter);
            } else {
                hashMap.get(counter);
                //log.debug("{}, {}", this.getClass().getSimpleName(), value);
            }
        }
    }
}

然後是ReentrantLock,使用也是很簡單,需要在finally中釋放鎖:

@Slf4j
class ReentrantLockTask extends LockTask {
    private static ReentrantLock locker = new ReentrantLock();

    public ReentrantLockTask(Boolean write) {
        super(write);
    }

    @Override
    protected void doTask() {
        locker.lock();
        try {
            if (write) {
                counter++;
                hashMap.put(counter, "Data" + counter);
            } else {
                hashMap.get(counter);
            }
        } finally {
            locker.unlock();
        }
    }
}

然後是ReentrantReadWriteLock,可重入的讀寫鎖,這屋裡我們需要區分讀操作還是寫操作來獲得不同型別的鎖:

@Slf4j
class ReentrantReadWriteLockTask extends LockTask {
    private static ReentrantReadWriteLock locker = new ReentrantReadWriteLock();

    public ReentrantReadWriteLockTask(Boolean write) {
        super(write);
    }

    @Override
    protected void doTask() {
        if (write) {
            locker.writeLock().lock();
            try {
                counter++;
                hashMap.put(counter, "Data" + counter);
            } finally {
                locker.writeLock().unlock();
            }
        } else {
            locker.readLock().lock();
            try {
                hashMap.get(counter);
            } finally {
                locker.readLock().unlock();
            }
        }
    }
}

然後是可重入鎖和可重入讀寫鎖的公平版本:

@Slf4j
class FairReentrantLockTask extends LockTask {
    private static ReentrantLock locker = new ReentrantLock(true);

    public FairReentrantLockTask(Boolean write) {
        super(write);
    }

    @Override
    protected void doTask() {
        locker.lock();
        try {
            if (write) {
                counter++;
                hashMap.put(counter, "Data" + counter);
            } else {
                hashMap.get(counter);
            }
        } finally {
            locker.unlock();
        }
    }
}

@Slf4j
class FairReentrantReadWriteLockTask extends LockTask {
    private static ReentrantReadWriteLock locker = new ReentrantReadWriteLock(true);

    public FairReentrantReadWriteLockTask(Boolean write) {
        super(write);
    }

    @Override
    protected void doTask() {
        if (write) {
            locker.writeLock().lock();
            try {
                counter++;
                hashMap.put(counter, "Data" + counter);
            } finally {
                locker.writeLock().unlock();
            }
        } else {
            locker.readLock().lock();
            try {
                hashMap.get(counter);
            } finally {
                locker.readLock().unlock();
            }
        }
    }
}

最後是1.8推出的StampedLock:

@Slf4j
class StampedLockTask extends LockTask {
    private static StampedLock locker = new StampedLock();

    public StampedLockTask(Boolean write) {
        super(write);
    }

    @Override
    protected void doTask() {
        if (write) {
            long stamp = locker.writeLock();
            try {
                counter++;
                hashMap.put(counter, "Data" + counter);
            } finally {
                locker.unlockWrite(stamp);
            }
        } else {
            long stamp = locker.tryOptimisticRead();
            long value = counter;

            if (!locker.validate(stamp)) {
                stamp = locker.readLock();
                try {
                    value = counter;
                } finally {
                    locker.unlockRead(stamp);
                }
            }
            hashMap.get(value);
        }
    }
}

這裡同樣區分讀寫鎖,只是讀鎖我們先嚐試進行樂觀讀,拿到一個戳後讀取我們需要保護的資料,隨後校驗一下這個戳如果沒問題的話說明資料沒有改變,樂觀鎖生效,如果有問題升級為悲觀鎖再讀取一次。因為StampedLock很複雜很容易用錯,真的打算用的話務必研讀官網的各種鎖升級的例子(樂觀讀到讀,樂觀讀到寫,讀到寫)。

效能測試和分析

同樣我們定義效能測試的型別:

@ToString
@RequiredArgsConstructor
class TestCase {
    final Class lockTaskClass;
    final int writerThreadCount;
    final int readerThreadCount;
    long duration;
}

每一種測試可以靈活選擇:

  • 測試的鎖型別
  • 寫執行緒數量
  • 讀執行緒數量
  • 最後測試結果回寫到duration

下面是效能測試的場景定義:

 @Test
public void test() throws Exception {
    List<TestCase> testCases = new ArrayList<>();

    Arrays.asList(SyncTask.class,
            ReentrantLockTask.class,
            FairReentrantLockTask.class,
            ReentrantReadWriteLockTask.class,
            FairReentrantReadWriteLockTask.class,
            StampedLockTask.class
    ).forEach(syncTaskClass -> {
        testCases.add(new TestCase(syncTaskClass, 1, 0));
        testCases.add(new TestCase(syncTaskClass, 10, 0));
        testCases.add(new TestCase(syncTaskClass, 0, 1));
        testCases.add(new TestCase(syncTaskClass, 0, 10));

        testCases.add(new TestCase(syncTaskClass, 1, 1));
        testCases.add(new TestCase(syncTaskClass, 10, 10));
        testCases.add(new TestCase(syncTaskClass, 50, 50));
        testCases.add(new TestCase(syncTaskClass, 100, 100));
        testCases.add(new TestCase(syncTaskClass, 500, 500));
        testCases.add(new TestCase(syncTaskClass, 1000, 1000));

        testCases.add(new TestCase(syncTaskClass, 1, 10));
        testCases.add(new TestCase(syncTaskClass, 10, 100));
        testCases.add(new TestCase(syncTaskClass, 10, 200));
        testCases.add(new TestCase(syncTaskClass, 10, 500));
        testCases.add(new TestCase(syncTaskClass, 10, 1000));

        testCases.add(new TestCase(syncTaskClass, 10, 1));
        testCases.add(new TestCase(syncTaskClass, 100, 10));
        testCases.add(new TestCase(syncTaskClass, 200, 10));
        testCases.add(new TestCase(syncTaskClass, 500, 10));
        testCases.add(new TestCase(syncTaskClass, 1000, 10));

    });

    testCases.forEach(testCase -> {
        System.gc();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        try {
            benchmark(testCase);
        } catch (Exception e) {
            e.printStackTrace();
        }
    });

    StringBuilder stringBuilder = new StringBuilder();
    int index = 0;
    for (TestCase testCase : testCases) {
        if (index % 20 == 0)
            stringBuilder.append("\r\n");
        stringBuilder.append(testCase.duration);
        stringBuilder.append(",");
        index++;
    }
    System.out.println(stringBuilder.toString());
}

在這裡可以看到,我們為這6個鎖定義了20種測試場景,覆蓋幾大類:

  • 只有讀的情況
  • 只有寫的情況
  • 讀寫併發的情況,併發數漸漸增多
  • 讀比寫多的情況(這個最常見吧)
  • 寫比讀多的情況

每一次測試之間強制觸發gc後休眠1秒,每20次結果換行一次輸出。
測試類如下:

private void benchmark(TestCase testCase) throws Exception {
    LockTask.counter = 0;
    log.info("Start benchmark:{}", testCase);
    CountDownLatch start = new CountDownLatch(1);
    CountDownLatch finish = new CountDownLatch(testCase.readerThreadCount + testCase.writerThreadCount);
    if (testCase.readerThreadCount > 0) {
        LockTask readerTask = (LockTask) testCase.lockTaskClass.getDeclaredConstructor(Boolean.class).newInstance(false);
        readerTask.start = start;
        readerTask.finish = finish;
        readerTask.loopCount = LOOP_COUNT / testCase.readerThreadCount;
        if (testCase.lockTaskClass.getSimpleName().startsWith("Fair")) readerTask.loopCount /= 100;
        IntStream.rangeClosed(1, testCase.readerThreadCount)
                .mapToObj(__ -> new Thread(readerTask))
                .forEach(Thread::start);
    }
    if (testCase.writerThreadCount > 0) {
        LockTask writerTask = (LockTask) testCase.lockTaskClass.getDeclaredConstructor(Boolean.class).newInstance(true);
        writerTask.start = start;
        writerTask.finish = finish;
        writerTask.loopCount = LOOP_COUNT / testCase.writerThreadCount;
        if (testCase.lockTaskClass.getSimpleName().startsWith("Fair")) writerTask.loopCount /= 100;
        IntStream.rangeClosed(1, testCase.writerThreadCount)
                .mapToObj(__ -> new Thread(writerTask))
                .forEach(Thread::start);
    }

    start.countDown();
    long begin = System.currentTimeMillis();
    finish.await();
    if (testCase.writerThreadCount > 0) {
        if (testCase.lockTaskClass.getSimpleName().startsWith("Fair")) {
            Assert.assertEquals(LOOP_COUNT / 100, LockTask.counter);
        } else {
            Assert.assertEquals(LOOP_COUNT, LockTask.counter);
        }
    }
    testCase.duration = System.currentTimeMillis() - begin;
    log.info("Finish benchmark:{}", testCase);
}

程式碼主要乾了幾件事情:

  • 根據測試用例的讀寫執行緒數,開啟一定量的執行緒,根據類名和讀寫型別動態建立型別
  • 每一個執行緒執行的迴圈次數是按比例均勻分配的,公平型別的兩次測試數/100,因為實在是太慢了,等不了幾小時
  • 使用兩個CountDownLatch來控制所有執行緒開啟,等待所有執行緒完成,最後校驗一下counter的總數

在這裡,我們把迴圈次數設定為1000萬次,在阿里雲12核12G機器JDK8環境下執行得到的結果如下:

這裡,我們進行兩次測試,其實一開始我的測試程式碼裡沒有HashMap的讀寫操作,只有counter的讀寫操作(這個時候迴圈次數是1億次),所有第一次測試是僅僅只有counter的讀寫操作的,後一次測試是這裡貼的程式碼的版本。

所以這個表格中的資料不能直接來對比因為混雜了三種迴圈次數,上面那個表是1億從迴圈的時間,下面那個是1000萬次,黃色的兩條分別是100萬次和10萬次迴圈。

這個測試資訊量很大,這裡說一下我看到的幾個結論,或者你還可以從這個測試中品味出其它結論:

  • synchronized關鍵字經過各種優化進行簡單鎖的操作效能已經相當好了,如果用不到ReentrantLock高階功能的話,使用synchronized不會有什麼太多效能問題
  • 在任務非常輕的時候可重入鎖比synchronized還是快那麼一點,一般場景下不可能只是++操作,這個時候兩者差不多
  • 併發上來之後各種鎖的執行耗時稍微增多點,沒有增多太厲害,併發不足的時候反而效能還不好
  • 在任務很輕的時候StampedLock效能碾壓群雄,在只有讀操作的時候因為只是樂觀鎖,所以效能好的誇張
  • 在任務沒有那麼輕的時候讀寫鎖的效能幾乎都比普通鎖好,看下面那個表格,在任務實在是太輕的時候讀寫鎖因為複雜的鎖實現開銷的問題不如普通的可重入鎖
  • 公平版本的鎖非常非常慢,可以說比非公平版本的慢100倍還不止,而且執行的時候CPU打滿,其它版本的鎖執行的時候CPU利用在12核的20%左右,其實想想也對,不管是多少執行緒,大部分時候都阻塞了

所以說對於這些鎖的選擇也很明確:

  • 如果用不到ReentrantLock的什麼高階特性,synchronized就可以
  • 一般而言ReentrantLock完全可以替代synchronized,如果你不嫌麻煩的話
  • ReentrantReadWriteLock用於相對比較複雜的任務的讀寫併發的情況
  • StampedLock用於相對比較輕量級任務的高併發的情況,用起來也比較複雜,能夠實現極致的效能
  • 只有有特殊需求的話才去開啟ReentrantLock或ReentrantReadWriteLock的公平特性

再來看看ReentrantLock

之前也提到了可重入鎖相對synchronized有一些高階特性,我們寫一些測試程式碼:

  • 我們先在主執行緒鎖10次
  • 輸出一下鎖的一些資訊
  • 迴圈10次開啟10個執行緒嘗試獲取鎖,等待時間是1秒到10秒,顯然主執行緒釋放鎖之前是獲取不到鎖的
  • 1秒一次定時輸出鎖的一些資訊
  • 5秒後主執行緒釋放鎖
  • 休眠一下觀察子執行緒是否拿到鎖了
@Test
public void test() throws InterruptedException {

    ReentrantLock reentrantLock = new ReentrantLock(true);
    IntStream.rangeClosed(1, 10).forEach(i -> reentrantLock.lock());
    log.info("getHoldCount:{},isHeldByCurrentThread:{},isLocked:{}",
            reentrantLock.getHoldCount(),
            reentrantLock.isHeldByCurrentThread(),
            reentrantLock.isLocked());

    List<Thread> threads = IntStream.rangeClosed(1, 10).mapToObj(i -> new Thread(() -> {
        try {
            if (reentrantLock.tryLock(i, TimeUnit.SECONDS)) {
                try {
                    log.debug("Got lock");
                } finally {
                    reentrantLock.unlock();
                }
            } else {
                log.debug("Cannot get lock");
            }
        } catch (InterruptedException e) {
            log.debug("InterruptedException Cannot get lock");
            e.printStackTrace();
        }
    })).collect(Collectors.toList());

    Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(() -> log.info("getHoldCount:{}, getQueueLength:{}, hasQueuedThreads:{}, waitThreads:{}",
            reentrantLock.getHoldCount(),
            reentrantLock.getQueueLength(),
            reentrantLock.hasQueuedThreads(),
            threads.stream().filter(reentrantLock::hasQueuedThread).count()), 0, 1, TimeUnit.SECONDS);

    threads.forEach(Thread::start);

    TimeUnit.SECONDS.sleep(5);
    IntStream.rangeClosed(1, 10).forEach(i -> reentrantLock.unlock());
    TimeUnit.SECONDS.sleep(1);
}

輸出如下:

08:14:50.834 [main] INFO me.josephzhu.javaconcurrenttest.lock.ReentrantLockTest - getHoldCount:10,isHeldByCurrentThread:true,isLocked:true
08:14:50.849 [pool-1-thread-1] INFO me.josephzhu.javaconcurrenttest.lock.ReentrantLockTest - getHoldCount:0, getQueueLength:10, hasQueuedThreads:true, waitThreads:10
08:14:51.849 [Thread-0] DEBUG me.josephzhu.javaconcurrenttest.lock.ReentrantLockTest - Cannot get lock
08:14:51.848 [pool-1-thread-1] INFO me.josephzhu.javaconcurrenttest.lock.ReentrantLockTest - getHoldCount:0, getQueueLength:9, hasQueuedThreads:true, waitThreads:9
08:14:52.849 [Thread-1] DEBUG me.josephzhu.javaconcurrenttest.lock.ReentrantLockTest - Cannot get lock
08:14:52.849 [pool-1-thread-1] INFO me.josephzhu.javaconcurrenttest.lock.ReentrantLockTest - getHoldCount:0, getQueueLength:8, hasQueuedThreads:true, waitThreads:8
08:14:53.846 [pool-1-thread-1] INFO me.josephzhu.javaconcurrenttest.lock.ReentrantLockTest - getHoldCount:0, getQueueLength:8, hasQueuedThreads:true, waitThreads:8
08:14:53.847 [Thread-2] DEBUG me.josephzhu.javaconcurrenttest.lock.ReentrantLockTest - Cannot get lock
08:14:54.847 [pool-1-thread-1] INFO me.josephzhu.javaconcurrenttest.lock.ReentrantLockTest - getHoldCount:0, getQueueLength:7, hasQueuedThreads:true, waitThreads:7
08:14:54.849 [Thread-3] DEBUG me.josephzhu.javaconcurrenttest.lock.ReentrantLockTest - Cannot get lock
08:14:55.847 [pool-1-thread-1] INFO me.josephzhu.javaconcurrenttest.lock.ReentrantLockTest - getHoldCount:0, getQueueLength:6, hasQueuedThreads:true, waitThreads:6
08:14:55.850 [Thread-4] DEBUG me.josephzhu.javaconcurrenttest.lock.ReentrantLockTest - Cannot get lock
08:14:55.850 [Thread-5] DEBUG me.josephzhu.javaconcurrenttest.lock.ReentrantLockTest - Got lock
08:14:55.851 [Thread-6] DEBUG me.josephzhu.javaconcurrenttest.lock.ReentrantLockTest - Got lock
08:14:55.852 [Thread-7] DEBUG me.josephzhu.javaconcurrenttest.lock.ReentrantLockTest - Got lock
08:14:55.852 [Thread-8] DEBUG me.josephzhu.javaconcurrenttest.lock.ReentrantLockTest - Got lock
08:14:55.852 [Thread-9] DEBUG me.josephzhu.javaconcurrenttest.lock.ReentrantLockTest - Got lock
08:14:56.849 [pool-1-thread-1] INFO me.josephzhu.javaconcurrenttest.lock.ReentrantLockTest - getHoldCount:0, getQueueLength:0, hasQueuedThreads:false, waitThreads:0

從這個輸出可以看到:

  • 一開始顯示鎖被主執行緒鎖了10次
  • 隨著時間的推移等待鎖的執行緒數量在增加
  • 5個執行緒因為超時無法獲取到鎖
  • 5秒後還有5個執行緒拿到了鎖

這也可以看到可重入鎖相比synchronized功能更強大點:

  • 可以超時等待獲取鎖
  • 可以檢視到鎖的一些資訊
  • 可以中斷鎖(這裡沒有演示)
  • 之前提到的公平性
  • 可重入特性並不是它特有的功能,synchronized也能重入

提到了可重入,我們進行一個無聊的實驗看看可以重入多少次:

@Test
public void test2() {
    ReentrantLock reentrantLock = new ReentrantLock(true);
    int i = 0;
    try {
        while (true) {
            reentrantLock.lock();
            i++;
        }
    } catch (Error error) {
        log.error("count:{}", i, error);
    }
}

結果如下:

鎖誤用的例子

最後再提下最簡單的鎖誤用的例子,雖然沒有那麼高大上,但是這種因為鎖範圍和鎖保護物件的範圍不一致導致誤用的問題在業務程式碼中到處都是,比如:

@Slf4j
public class LockMisuse {

    @Test
    public void test1() throws InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        IntStream.rangeClosed(1, 100000).forEach(i -> executorService.submit(new Container()::test));
        executorService.shutdown();
        executorService.awaitTermination(1, TimeUnit.HOURS);
        log.info("{}", Container.counter);
    }
}

class Container {
    static int counter = 0;
    Object locker = new Object();

    void test() {
        synchronized (locker) {
            counter++;
        }
    }
}

在程式碼裡我們要保護的資源是靜態的,但是鎖卻是物件級別的,不同的例項持有不同的鎖,完全起不到保護作用:

小結

本文我們簡單測試了一下各種鎖的效能,我感覺這個測試可能還無法100%模擬真實的場景,真實情況下不僅僅是讀寫執行緒數量的不一致,更多是操作頻次的不一致,不過這個測試基本看到了我們猜測的結果。在日常程式碼開發過程中,大家可以根據實際功能和場景需要來選擇合適的鎖型別。

有的時候高大上的一些鎖因為使用複雜容易導致誤用、錯用、死鎖、活鎖等問題,我反而建議在沒有明顯問題的情況下先從簡單的『悲觀』鎖開始使用。還有就是像最後的例子,使用鎖的話務必需要認證檢查程式碼,思考鎖和保護物件的關係,避免鎖不產產生效果導致隱藏的Bug。

同樣,程式碼見我的Github,歡迎clone後自己把玩,歡迎點贊。

歡迎關注我的微信公眾號:隨緣主人的園子

相關推薦

一起Java併發執行緒池

和我之前的Spring系列文章一樣,我們會以做一些Demo做實驗的方式來複習一些知識點。 本文我們先從Java併發中最最常用的執行緒池開始。 從一個執行緒池實驗開始 首先我們寫一個方法來每秒一次定時輸出執行緒池的基本資訊: private void printStats(ThreadPoolExecutor

一起Java併發佇列

和朱曄一起復習Java併發(二):佇列 老樣子,我們還是從一些例子開始慢慢熟悉各種併發佇列。以看小說看故事的心態來學習不會顯得那麼枯燥而且更容易記憶深刻。 阻塞佇列的等待? 阻塞佇列最適合做的事情就是做為生產消費者的中間儲存,以抵抗生產者消費者速率不匹配的問題,不但是在速率不匹配的時候能夠有地方暫存任務,而且

一起Java併發效能測試

這個專題我發現怎麼慢慢演化為效能測試了,遇到任何東西我就忍不住去測一把。本文我們會大概看一下各種鎖資料結構的簡單用法,順便也會來比拼一下效能。 各種併發鎖 首先,我們定一個抽象基類,用於各種鎖測試的一些公共程式碼: 我們需要使用鎖來保護counter和hashMap這2個資源 write欄位表示這個執行緒是

一起Java併發Atomic

本節我們來研究下併發包中的Atomic型別。 AtomicXXX和XXXAdder以及XXXAccumulator效能測試 先來一把效能測試,對比一下AtomicLong(1.5出來的)、LongAdder(1.8出來的)和LongAccumulator(1.8出來的)用於簡單累加的效能。 程式邏輯比較簡單,

一起Java併發併發容器同步器

本節我們先會來複習一下java.util.concurrent下面的一些併發容器,然後再會來簡單看一下各種同步器。 ConcurrentHashMap和ConcurrentSkipListMap的效能 首先,我們來測試一下ConcurrentHashMap和ConcurrentSkipListMap的效能。

Java基礎

har ring cas jdk7 jdk def 位置 jdk5 n+1 1. switch語句格式: switch(表達式){   case 值1:    語句體1;    break;//break可以省略,但會出現case穿透現象,建議不省略   case

2017第34周Java總結

利用 序列 生成 java 學習總結 java基礎 mave 相關 cglib動態代理 從上周日開始對工作中遇到的Java相關的知識進行總結整理。先是回顧了Java關鍵字,重點說了static關鍵字的用法:修飾變量、程序塊、內部類、方法、還靜態導包;重點說了final關鍵字

基於JVM原理、JMM模型CPU快取模型深入理解Java併發程式設計

許多以Java多執行緒開發為主題的技術書籍,都會把對Java虛擬機器和Java記憶體模型的講解,作為講授Java併發程式設計開發的主要內容,有的還深入到計算機系統的記憶體、CPU、快取等予以說明。實際上,在實際的Java開發工作中,僅僅瞭解併發程式設計的建立、啟動、管理和通訊等基本知識還是不夠的。一

基於JVM原理JMM模型CPU快取模型深入理解Java併發程式設計

許多以Java多執行緒開發為主題的技術書籍,都會把對Java虛擬機器和Java記憶體模型的講解,作為講授Java併發程式設計開發的主要內容,有的還深入到計算機系統的記憶體、CPU、快取等予以說明。實際上,在實際的Java開發工作中,僅僅瞭解併發程式設計的建立、啟動、管理和通訊等基本知識還是不夠的。一方面,如果

Java併發程式設計基礎//程序每個程序都有獨立的程式碼資料空間程序上下文,程序間的切換開銷比較大,一個程序包含1-n個執行緒 //執行緒同一類執行緒共享程式碼資料空間,每個執行緒擁有獨立的執行棧程式計

1.實現多執行緒的兩種方式: (1)繼承Thread類; (2)實現Runnable介面 //程序:每個程序都有獨立的程式碼和資料空間(程序上下文),程序間的切換開銷比較大,一個程序包含1-n個執行緒 //執行緒:同一類執行緒共享程式碼和資料空間,每個執行緒擁有獨立的執行

Java併發程式設計從入門到精通》顯示LockReentrantLock

作者:張振華    購買連結:天貓商城  JD商城  噹噹書店 顯示鎖Lock和ReentrantLock Lock是一個介面提供了無條件的、可輪詢的、定時的、可中斷的鎖獲取操作,所有加鎖和解鎖的方法都是顯式的。包路徑是:java.util.concurrent.locks.Lock。核心方

算法——有源匯上下界可行流bzoj2396

取消 meeting greate algorithm ctype main names traints n) 題目: Description We are supposed to make a budget proposal for this multi-site com

java併發之----實現生產者/消費者模式操作值&一對一交替列印

一、實現生產者/消費者模式 1、一生產與一消費:操作值 利用synchronized 實現,程式碼如下: public class Producer { private String lock; public Producer(String lock){ this.loc

java併發機制的底層實現原理volatile深入分析

     java程式碼最終會被類載入器載入到JVM中,然後轉化為彙編指令在CPU上執行。java中所使用的併發機制依賴於JVM的實現和CPU的指令。 1.volatile的應用 volatile是一個輕量級的synchronize,它保證了共享變數的可見性,確保了所有執

Java併發程式設計原理與實戰一執行緒狀態及建立執行緒的多種方式

一、為什麼要學習併發程式設計 1.發揮多處理的強大能力 2.建模的簡單性 3.非同步事件的簡化處理 4.響應更加靈敏的使用者介面 二、併發的缺點 1.安全性問題 多執行緒環境下 多個執行緒共享一個資源 對資源進行非原子性操作 2.活躍

Java併發——執行緒安全的集合

1.對併發雜湊對映的批操作: Java SE 8為併發雜湊對映提供了批操作,即使有其他執行緒在處理對映,這些操作也能安全地執行。批操作會遍歷對映,處理遍歷過程中找到的元素。無須凍結當前對映的快照。 有三種不同的批操作:搜尋、歸約、forEach。 每個操作都有四個版本:operation

Java併發程式設計的藝術——讀書筆記 併發程式設計的挑戰

第一章 併發程式設計的挑戰 因為最近找工作,準備筆試/面試,開始嘗試閱讀這本書,我不常寫部落格,距上一次寫已經過去大概一年時間了,連CSDN密碼都忘了/衰,所以這次新開一個賬號重新開始,希望我能堅持下去。 第一章沒什麼內容,我認為其目的主要是給出足夠多的閱讀這本書的理

Java併發程式設計】之一可重入內建

    每個Java物件都可以用做一個實現同步的鎖,這些鎖被稱為內建鎖或監視器鎖。執行緒在進入同步程式碼塊之前會自動獲取鎖,並且在退出同步程式碼塊時會自動釋放鎖。獲得內建鎖的唯一途徑就是進入由這個鎖保護

java併發包學習系列jdk併發容器

同步容器 同步容器可以簡單地理解為通過synchronized來實現同步的容器,如果有多個執行緒呼叫同步容器的方法,它們將會序列執行。 同步容器將它們的狀態封裝起來,並對每一個公有方法進行同步。主要包括: Vector Stack HashTable C

Java併發程式設計併發Concurrent與並行Parallel的區別

併發(Concurrent)與並行(Parallel)是一個大家比較容易混淆的概念。大家在解釋併發與並行的時候一般這樣說: 多執行緒是併發執行的; 多核CPU是並行執行的,單核CPU是不可以不行執行的; 以上說法也是可以理解的,大家都是基於場景來描述的。