这个专题我发现怎么慢慢演化为性能测试了,遇到任何东西我就忍不住去测一把。本文我们会大概看一下各种锁数据结构的简单用法,顺便也会来比拼一下性能。

各种并发锁

首先,我们定一个抽象基类,用于各种锁测试的一些公共代码:

  • 我们需要使用锁来保护counter和hashMap这2个资源
  • write字段表示这个线程是执行写操作还是读操作
  • 每一个线程都会执行loopCount次读或写操作
  • start的CountDownLatch用于等待所有线程一起执行
  • finish的CountDownLatch用于让主线程等待所有线程都完成
  1. @Slf4j
  2. abstract class LockTask implements Runnable {
  3. protected volatile static long counter;
  4. protected boolean write;
  5. protected static HashMap<Long, String> hashMap = new HashMap<>();
  6. int loopCount;
  7. CountDownLatch start;
  8. CountDownLatch finish;
  9. public LockTask(Boolean write) {
  10. this.write = write;
  11. }
  12. @Override
  13. public void run() {
  14. try {
  15. start.await();
  16. } catch (InterruptedException e) {
  17. e.printStackTrace();
  18. }
  19. for (int i = 0; i < loopCount; i++) {
  20. doTask();
  21. }
  22. finish.countDown();
  23. }
  24. abstract protected void doTask();
  25. }

下面我们实现最简单的使用synchronized来实现的锁,拿到锁后我们针对hashMap和counter做一下最简单的操作:

  1. @Slf4j
  2. class SyncTask extends LockTask {
  3. private static Object locker = new Object();
  4. public SyncTask(Boolean write) {
  5. super(write);
  6. }
  7. @Override
  8. protected void doTask() {
  9. synchronized (locker) {
  10. if (write) {
  11. counter++;
  12. hashMap.put(counter, "Data" + counter);
  13. } else {
  14. hashMap.get(counter);
  15. //log.debug("{}, {}", this.getClass().getSimpleName(), value);
  16. }
  17. }
  18. }
  19. }

然后是ReentrantLock,使用也是很简单,需要在finally中释放锁:

  1. @Slf4j
  2. class ReentrantLockTask extends LockTask {
  3. private static ReentrantLock locker = new ReentrantLock();
  4. public ReentrantLockTask(Boolean write) {
  5. super(write);
  6. }
  7. @Override
  8. protected void doTask() {
  9. locker.lock();
  10. try {
  11. if (write) {
  12. counter++;
  13. hashMap.put(counter, "Data" + counter);
  14. } else {
  15. hashMap.get(counter);
  16. }
  17. } finally {
  18. locker.unlock();
  19. }
  20. }
  21. }

然后是ReentrantReadWriteLock,可重入的读写锁,这屋里我们需要区分读操作还是写操作来获得不同类型的锁:

  1. @Slf4j
  2. class ReentrantReadWriteLockTask extends LockTask {
  3. private static ReentrantReadWriteLock locker = new ReentrantReadWriteLock();
  4. public ReentrantReadWriteLockTask(Boolean write) {
  5. super(write);
  6. }
  7. @Override
  8. protected void doTask() {
  9. if (write) {
  10. locker.writeLock().lock();
  11. try {
  12. counter++;
  13. hashMap.put(counter, "Data" + counter);
  14. } finally {
  15. locker.writeLock().unlock();
  16. }
  17. } else {
  18. locker.readLock().lock();
  19. try {
  20. hashMap.get(counter);
  21. } finally {
  22. locker.readLock().unlock();
  23. }
  24. }
  25. }
  26. }

然后是可重入锁和可重入读写锁的公平版本:

  1. @Slf4j
  2. class FairReentrantLockTask extends LockTask {
  3. private static ReentrantLock locker = new ReentrantLock(true);
  4. public FairReentrantLockTask(Boolean write) {
  5. super(write);
  6. }
  7. @Override
  8. protected void doTask() {
  9. locker.lock();
  10. try {
  11. if (write) {
  12. counter++;
  13. hashMap.put(counter, "Data" + counter);
  14. } else {
  15. hashMap.get(counter);
  16. }
  17. } finally {
  18. locker.unlock();
  19. }
  20. }
  21. }
  22. @Slf4j
  23. class FairReentrantReadWriteLockTask extends LockTask {
  24. private static ReentrantReadWriteLock locker = new ReentrantReadWriteLock(true);
  25. public FairReentrantReadWriteLockTask(Boolean write) {
  26. super(write);
  27. }
  28. @Override
  29. protected void doTask() {
  30. if (write) {
  31. locker.writeLock().lock();
  32. try {
  33. counter++;
  34. hashMap.put(counter, "Data" + counter);
  35. } finally {
  36. locker.writeLock().unlock();
  37. }
  38. } else {
  39. locker.readLock().lock();
  40. try {
  41. hashMap.get(counter);
  42. } finally {
  43. locker.readLock().unlock();
  44. }
  45. }
  46. }
  47. }

最后是1.8推出的StampedLock:

  1. @Slf4j
  2. class StampedLockTask extends LockTask {
  3. private static StampedLock locker = new StampedLock();
  4. public StampedLockTask(Boolean write) {
  5. super(write);
  6. }
  7. @Override
  8. protected void doTask() {
  9. if (write) {
  10. long stamp = locker.writeLock();
  11. try {
  12. counter++;
  13. hashMap.put(counter, "Data" + counter);
  14. } finally {
  15. locker.unlockWrite(stamp);
  16. }
  17. } else {
  18. long stamp = locker.tryOptimisticRead();
  19. long value = counter;
  20. if (!locker.validate(stamp)) {
  21. stamp = locker.readLock();
  22. try {
  23. value = counter;
  24. } finally {
  25. locker.unlockRead(stamp);
  26. }
  27. }
  28. hashMap.get(value);
  29. }
  30. }
  31. }

这里同样区分读写锁,只是读锁我们先尝试进行乐观读,拿到一个戳后读取我们需要保护的数据,随后校验一下这个戳如果没问题的话说明数据没有改变,乐观锁生效,如果有问题升级为悲观锁再读取一次。因为StampedLock很复杂很容易用错,真的打算用的话务必研读官网的各种锁升级的例子(乐观读到读,乐观读到写,读到写)。

性能测试和分析

同样我们定义性能测试的类型:

  1. @ToString
  2. @RequiredArgsConstructor
  3. class TestCase {
  4. final Class lockTaskClass;
  5. final int writerThreadCount;
  6. final int readerThreadCount;
  7. long duration;
  8. }

每一种测试可以灵活选择:

  • 测试的锁类型
  • 写线程数量
  • 读线程数量
  • 最后测试结果回写到duration

下面是性能测试的场景定义:

  1. @Test
  2. public void test() throws Exception {
  3. List<TestCase> testCases = new ArrayList<>();
  4. Arrays.asList(SyncTask.class,
  5. ReentrantLockTask.class,
  6. FairReentrantLockTask.class,
  7. ReentrantReadWriteLockTask.class,
  8. FairReentrantReadWriteLockTask.class,
  9. StampedLockTask.class
  10. ).forEach(syncTaskClass -> {
  11. testCases.add(new TestCase(syncTaskClass, 1, 0));
  12. testCases.add(new TestCase(syncTaskClass, 10, 0));
  13. testCases.add(new TestCase(syncTaskClass, 0, 1));
  14. testCases.add(new TestCase(syncTaskClass, 0, 10));
  15. testCases.add(new TestCase(syncTaskClass, 1, 1));
  16. testCases.add(new TestCase(syncTaskClass, 10, 10));
  17. testCases.add(new TestCase(syncTaskClass, 50, 50));
  18. testCases.add(new TestCase(syncTaskClass, 100, 100));
  19. testCases.add(new TestCase(syncTaskClass, 500, 500));
  20. testCases.add(new TestCase(syncTaskClass, 1000, 1000));
  21. testCases.add(new TestCase(syncTaskClass, 1, 10));
  22. testCases.add(new TestCase(syncTaskClass, 10, 100));
  23. testCases.add(new TestCase(syncTaskClass, 10, 200));
  24. testCases.add(new TestCase(syncTaskClass, 10, 500));
  25. testCases.add(new TestCase(syncTaskClass, 10, 1000));
  26. testCases.add(new TestCase(syncTaskClass, 10, 1));
  27. testCases.add(new TestCase(syncTaskClass, 100, 10));
  28. testCases.add(new TestCase(syncTaskClass, 200, 10));
  29. testCases.add(new TestCase(syncTaskClass, 500, 10));
  30. testCases.add(new TestCase(syncTaskClass, 1000, 10));
  31. });
  32. testCases.forEach(testCase -> {
  33. System.gc();
  34. try {
  35. TimeUnit.SECONDS.sleep(1);
  36. } catch (InterruptedException e) {
  37. e.printStackTrace();
  38. }
  39. try {
  40. benchmark(testCase);
  41. } catch (Exception e) {
  42. e.printStackTrace();
  43. }
  44. });
  45. StringBuilder stringBuilder = new StringBuilder();
  46. int index = 0;
  47. for (TestCase testCase : testCases) {
  48. if (index % 20 == 0)
  49. stringBuilder.append("\r\n");
  50. stringBuilder.append(testCase.duration);
  51. stringBuilder.append(",");
  52. index++;
  53. }
  54. System.out.println(stringBuilder.toString());
  55. }

在这里可以看到,我们为这6个锁定义了20种测试场景,覆盖几大类:

  • 只有读的情况
  • 只有写的情况
  • 读写并发的情况,并发数渐渐增多
  • 读比写多的情况(这个最常见吧)
  • 写比读多的情况

每一次测试之间强制触发gc后休眠1秒,每20次结果换行一次输出。

测试类如下:

  1. private void benchmark(TestCase testCase) throws Exception {
  2. LockTask.counter = 0;
  3. log.info("Start benchmark:{}", testCase);
  4. CountDownLatch start = new CountDownLatch(1);
  5. CountDownLatch finish = new CountDownLatch(testCase.readerThreadCount + testCase.writerThreadCount);
  6. if (testCase.readerThreadCount > 0) {
  7. LockTask readerTask = (LockTask) testCase.lockTaskClass.getDeclaredConstructor(Boolean.class).newInstance(false);
  8. readerTask.start = start;
  9. readerTask.finish = finish;
  10. readerTask.loopCount = LOOP_COUNT / testCase.readerThreadCount;
  11. if (testCase.lockTaskClass.getSimpleName().startsWith("Fair")) readerTask.loopCount /= 100;
  12. IntStream.rangeClosed(1, testCase.readerThreadCount)
  13. .mapToObj(__ -> new Thread(readerTask))
  14. .forEach(Thread::start);
  15. }
  16. if (testCase.writerThreadCount > 0) {
  17. LockTask writerTask = (LockTask) testCase.lockTaskClass.getDeclaredConstructor(Boolean.class).newInstance(true);
  18. writerTask.start = start;
  19. writerTask.finish = finish;
  20. writerTask.loopCount = LOOP_COUNT / testCase.writerThreadCount;
  21. if (testCase.lockTaskClass.getSimpleName().startsWith("Fair")) writerTask.loopCount /= 100;
  22. IntStream.rangeClosed(1, testCase.writerThreadCount)
  23. .mapToObj(__ -> new Thread(writerTask))
  24. .forEach(Thread::start);
  25. }
  26. start.countDown();
  27. long begin = System.currentTimeMillis();
  28. finish.await();
  29. if (testCase.writerThreadCount > 0) {
  30. if (testCase.lockTaskClass.getSimpleName().startsWith("Fair")) {
  31. Assert.assertEquals(LOOP_COUNT / 100, LockTask.counter);
  32. } else {
  33. Assert.assertEquals(LOOP_COUNT, LockTask.counter);
  34. }
  35. }
  36. testCase.duration = System.currentTimeMillis() - begin;
  37. log.info("Finish benchmark:{}", testCase);
  38. }

代码主要干了几件事情:

  • 根据测试用例的读写线程数,开启一定量的线程,根据类名和读写类型动态创建类型
  • 每一个线程执行的循环次数是按比例均匀分配的,公平类型的两次测试数/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秒后主线程释放锁
  • 休眠一下观察子线程是否拿到锁了
  1. @Test
  2. public void test() throws InterruptedException {
  3. ReentrantLock reentrantLock = new ReentrantLock(true);
  4. IntStream.rangeClosed(1, 10).forEach(i -> reentrantLock.lock());
  5. log.info("getHoldCount:{},isHeldByCurrentThread:{},isLocked:{}",
  6. reentrantLock.getHoldCount(),
  7. reentrantLock.isHeldByCurrentThread(),
  8. reentrantLock.isLocked());
  9. List<Thread> threads = IntStream.rangeClosed(1, 10).mapToObj(i -> new Thread(() -> {
  10. try {
  11. if (reentrantLock.tryLock(i, TimeUnit.SECONDS)) {
  12. try {
  13. log.debug("Got lock");
  14. } finally {
  15. reentrantLock.unlock();
  16. }
  17. } else {
  18. log.debug("Cannot get lock");
  19. }
  20. } catch (InterruptedException e) {
  21. log.debug("InterruptedException Cannot get lock");
  22. e.printStackTrace();
  23. }
  24. })).collect(Collectors.toList());
  25. Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(() -> log.info("getHoldCount:{}, getQueueLength:{}, hasQueuedThreads:{}, waitThreads:{}",
  26. reentrantLock.getHoldCount(),
  27. reentrantLock.getQueueLength(),
  28. reentrantLock.hasQueuedThreads(),
  29. threads.stream().filter(reentrantLock::hasQueuedThread).count()), 0, 1, TimeUnit.SECONDS);
  30. threads.forEach(Thread::start);
  31. TimeUnit.SECONDS.sleep(5);
  32. IntStream.rangeClosed(1, 10).forEach(i -> reentrantLock.unlock());
  33. TimeUnit.SECONDS.sleep(1);
  34. }

输出如下:

  1. 08:14:50.834 [main] INFO me.josephzhu.javaconcurrenttest.lock.ReentrantLockTest - getHoldCount:10,isHeldByCurrentThread:true,isLocked:true
  2. 08:14:50.849 [pool-1-thread-1] INFO me.josephzhu.javaconcurrenttest.lock.ReentrantLockTest - getHoldCount:0, getQueueLength:10, hasQueuedThreads:true, waitThreads:10
  3. 08:14:51.849 [Thread-0] DEBUG me.josephzhu.javaconcurrenttest.lock.ReentrantLockTest - Cannot get lock
  4. 08:14:51.848 [pool-1-thread-1] INFO me.josephzhu.javaconcurrenttest.lock.ReentrantLockTest - getHoldCount:0, getQueueLength:9, hasQueuedThreads:true, waitThreads:9
  5. 08:14:52.849 [Thread-1] DEBUG me.josephzhu.javaconcurrenttest.lock.ReentrantLockTest - Cannot get lock
  6. 08:14:52.849 [pool-1-thread-1] INFO me.josephzhu.javaconcurrenttest.lock.ReentrantLockTest - getHoldCount:0, getQueueLength:8, hasQueuedThreads:true, waitThreads:8
  7. 08:14:53.846 [pool-1-thread-1] INFO me.josephzhu.javaconcurrenttest.lock.ReentrantLockTest - getHoldCount:0, getQueueLength:8, hasQueuedThreads:true, waitThreads:8
  8. 08:14:53.847 [Thread-2] DEBUG me.josephzhu.javaconcurrenttest.lock.ReentrantLockTest - Cannot get lock
  9. 08:14:54.847 [pool-1-thread-1] INFO me.josephzhu.javaconcurrenttest.lock.ReentrantLockTest - getHoldCount:0, getQueueLength:7, hasQueuedThreads:true, waitThreads:7
  10. 08:14:54.849 [Thread-3] DEBUG me.josephzhu.javaconcurrenttest.lock.ReentrantLockTest - Cannot get lock
  11. 08:14:55.847 [pool-1-thread-1] INFO me.josephzhu.javaconcurrenttest.lock.ReentrantLockTest - getHoldCount:0, getQueueLength:6, hasQueuedThreads:true, waitThreads:6
  12. 08:14:55.850 [Thread-4] DEBUG me.josephzhu.javaconcurrenttest.lock.ReentrantLockTest - Cannot get lock
  13. 08:14:55.850 [Thread-5] DEBUG me.josephzhu.javaconcurrenttest.lock.ReentrantLockTest - Got lock
  14. 08:14:55.851 [Thread-6] DEBUG me.josephzhu.javaconcurrenttest.lock.ReentrantLockTest - Got lock
  15. 08:14:55.852 [Thread-7] DEBUG me.josephzhu.javaconcurrenttest.lock.ReentrantLockTest - Got lock
  16. 08:14:55.852 [Thread-8] DEBUG me.josephzhu.javaconcurrenttest.lock.ReentrantLockTest - Got lock
  17. 08:14:55.852 [Thread-9] DEBUG me.josephzhu.javaconcurrenttest.lock.ReentrantLockTest - Got lock
  18. 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也能重入

提到了可重入,我们进行一个无聊的实验看看可以重入多少次:

  1. @Test
  2. public void test2() {
  3. ReentrantLock reentrantLock = new ReentrantLock(true);
  4. int i = 0;
  5. try {
  6. while (true) {
  7. reentrantLock.lock();
  8. i++;
  9. }
  10. } catch (Error error) {
  11. log.error("count:{}", i, error);
  12. }
  13. }

结果如下:

锁误用的例子

最后再提下最简单的锁误用的例子,虽然没有那么高大上,但是这种因为锁范围和锁保护对象的范围不一致导致误用的问题在业务代码中到处都是,比如:

  1. @Slf4j
  2. public class LockMisuse {
  3. @Test
  4. public void test1() throws InterruptedException {
  5. ExecutorService executorService = Executors.newFixedThreadPool(10);
  6. IntStream.rangeClosed(1, 100000).forEach(i -> executorService.submit(new Container()::test));
  7. executorService.shutdown();
  8. executorService.awaitTermination(1, TimeUnit.HOURS);
  9. log.info("{}", Container.counter);
  10. }
  11. }
  12. class Container {
  13. static int counter = 0;
  14. Object locker = new Object();
  15. void test() {
  16. synchronized (locker) {
  17. counter++;
  18. }
  19. }
  20. }

在代码里我们要保护的资源是静态的,但是锁却是对象级别的,不同的实例持有不同的锁,完全起不到保护作用:

小结

本文我们简单测试了一下各种锁的性能,我感觉这个测试可能还无法100%模拟真实的场景,真实情况下不仅仅是读写线程数量的不一致,更多是操作频次的不一致,不过这个测试基本看到了我们猜测的结果。在日常代码开发过程中,大家可以根据实际功能和场景需要来选择合适的锁类型。

有的时候高大上的一些锁因为使用复杂容易导致误用、错用、死锁、活锁等问题,我反而建议在没有明显问题的情况下先从简单的『悲观』锁开始使用。还有就是像最后的例子,使用锁的话务必需要认证检查代码,思考锁和保护对象的关系,避免锁不产产生效果导致隐藏的Bug。

同样,代码见我的Github,欢迎clone后自己把玩,欢迎点赞。

欢迎关注我的微信公众号:随缘主人的园子

和朱晔一起复习Java并发(三):锁(含锁性能测试)的更多相关文章

  1. 和朱晔一起复习Java并发(二):队列

    和朱晔一起复习Java并发(二):队列 老样子,我们还是从一些例子开始慢慢熟悉各种并发队列.以看小说看故事的心态来学习不会显得那么枯燥而且更容易记忆深刻. 阻塞队列的等待? 阻塞队列最适合做的事情就是 ...

  2. 和朱晔一起复习Java并发(五):并发容器和同步器

    本节我们先会来复习一下java.util.concurrent下面的一些并发容器,然后再会来简单看一下各种同步器. ConcurrentHashMap和ConcurrentSkipListMap的性能 ...

  3. 和朱晔一起复习Java并发(一):线程池

    和我之前的Spring系列文章一样,我们会以做一些Demo做实验的方式来复习一些知识点. 本文我们先从Java并发中最最常用的线程池开始. 从一个线程池实验开始 首先我们写一个方法来每秒一次定时输出线 ...

  4. 和朱晔一起复习Java并发(四):Atomic

    本节我们来研究下并发包中的Atomic类型. AtomicXXX和XXXAdder以及XXXAccumulator性能测试 先来一把性能测试,对比一下AtomicLong(1.5出来的).LongAd ...

  5. java并发多线程显式锁Condition条件简介分析与监视器 多线程下篇(四)

    Lock接口提供了方法Condition newCondition();用于获取对应锁的条件,可以在这个条件对象上调用监视器方法 可以理解为,原本借助于synchronized关键字以及锁对象,配备了 ...

  6. Java并发编程:Concurrent锁机制解析

    Java并发编程:Concurrent锁机制解析 */--> code {color: #FF0000} pre.src {background-color: #002b36; color: # ...

  7. java 并发多线程显式锁概念简介 什么是显式锁 多线程下篇(一)

    目前对于同步,仅仅介绍了一个关键字synchronized,可以用于保证线程同步的原子性.可见性.有序性 对于synchronized关键字,对于静态方法默认是以该类的class对象作为锁,对于实例方 ...

  8. Java 并发:内置锁 Synchronized

    摘要: 在多线程编程中,线程安全问题是一个最为关键的问题,其核心概念就在于正确性,即当多个线程訪问某一共享.可变数据时,始终都不会导致数据破坏以及其它不该出现的结果. 而全部的并发模式在解决问题时,採 ...

  9. java并发笔记之四synchronized 锁的膨胀过程(锁的升级过程)深入剖析

    警告⚠️:本文耗时很长,先做好心理准备,建议PC端浏览器浏览效果更佳. 本篇我们讲通过大量实例代码及hotspot源码分析偏向锁(批量重偏向.批量撤销).轻量级锁.重量级锁及锁的膨胀过程(也就是锁的升 ...

随机推荐

  1. SMC状态机笔记

    %class 状态机所作用的类 %package 类所在的包 %fsmclass 生成类的类名 %fsmfile 生成类的文件名 %access 生成类的可访问级别 %start 指定状态机的开始状态 ...

  2. Windows10 使用Virtual Box一启动虚拟机就蓝屏(错误代码SYSTEM_SERVICE_EXCEPTION)解决方案

    原文:Windows10 使用Virtual Box一启动虚拟机就蓝屏(错误代码SYSTEM_SERVICE_EXCEPTION)解决方案 一打开虚拟机电脑就立马蓝屏重启,新建虚拟机也没用,然后就开始 ...

  3. 关于jquery.fileupload结合PHP上传图片的开发用法流程

    这阵子做了一个项目,涉及到了图片上传,以往用的都是uploadify这个插件,感觉它在PC上的使用还是很强大的, 不过最近这个项目涉及到了移动端的上传,其实uploadify也可以,但是他有一个 up ...

  4. 想让一个Widget成为模态,我们只需要对其设置setAttribute(Qt::WA_ShowModal, true);

    想让一个Widget成为模态,我们只需要对其设置: setAttribute(Qt::WA_ShowModal, true); 注意:这是QWidget的成员函数 ,也就是说,QWidget可以显示为 ...

  5. Java Date Calendar DateFormat Details

    From https://www.ntu.edu.sg/home/ehchua/programming/java/DateTimeCalendar.html Date and Time - Creat ...

  6. Tinyhttpd for Windows(500多行)

    TinyHTTPd forWindows 前言 TinyHTTPd是一个开源的简易学习型的HTTP服务器,项目主页在:http://tinyhttpd.sourceforge.NET/,源代码下载:h ...

  7. LVS-DR模式部署流程

    情景一 一.环境介绍 1)RIP.VIP.DIP为同一物理网络 2)LVS Hostname:lvs eth0:DIP-192.168.3.31 eth0:0:VIP-192.168.3.10 3)R ...

  8. Android开发之旅(1) 之 Android 开发环境搭建

    工作室原创出品,欢迎转载,欢迎交流. 转载请注明原文:http://www.cnblogs.com/wangleiblog/p/6019063.html Android开发之旅目录 1 前言 很多朋友 ...

  9. Anaconada安装

    目录 Anaconda介绍 Anaconda下载 安装Anaconda 配置环境变量 管理虚拟环境 activate 切换环境 卸载环境 关于环境总结 安装第三方包 卸载第三方包 查看环境包信息 导入 ...

  10. Junit4使用详解二:Junit4运行流程

    1.新建一个测试用例,把下面的四个方法勾选以便查看效果 2.我们在各个方法里面写上输出语句 3.运行之后我们可以发现,它的执行顺序是这样的 注:junit4中的运行流程 1.@BeforeClass修 ...