Java 高并发九:锁的优化和注意事项详解

摘要

本系列基于炼数成金课程,为了更好的学习,做了系列的记录。 本文主要介绍: 1. 锁优化的思路和方法 2. 虚拟机内的锁优化 3. 一个错误使用锁的案例 4. ThreadLocal及其源码分析

1. 锁优化的思路和方法

在[高并发Java 一] 前言中有提到并发的级别。

一旦用到锁,就说明这是阻塞式的,所以在并发度上一般来说都会比无锁的情况低一点。

这里提到的锁优化,是指在阻塞式的情况下,如何让性能不要变得太差。但是再怎么优化,一般来说性能都会比无锁的情况差一点。

这里要注意的是,在[高并发Java 五] JDK并发包1中提到的ReentrantLock中的tryLock,偏向于一种无锁的方式,因为在tryLock判断时,并不会把自己挂起。

锁优化的思路和方法总结一下,有以下几种。

  1. 减少锁持有时间
  2. 减小锁粒度
  3. 锁分离
  4. 锁粗化
  5. 锁消除

1.1 减少锁持有时间

public synchronized void syncMethod(){
 othercode1();
 mutextMethod();
 othercode2();
 }

像上述代码这样,在进入方法前就要得到锁,其他线程就要在外面等待。

这里优化的一点在于,要减少其他线程等待的时间,所以,只用在有线程安全要求的程序上加锁

public void syncMethod(){
 othercode1();
 synchronized(this)
 {
 mutextMethod();
 }
 othercode2();
 }

1.2 减小锁粒度

将大对象(这个对象可能会被很多线程访问),拆成小对象,大大增加并行度,降低锁竞争。降低了锁的竞争,偏向锁,轻量级锁成功率才会提高。

最最典型的减小锁粒度的案例就是ConcurrentHashMap。这个在[高并发Java 五] JDK并发包1有提到。

1.3 锁分离

最常见的锁分离就是读写锁ReadWriteLock,根据功能进行分离成读锁和写锁,这样读读不互斥,读写互斥,写写互斥,即保证了线程安全,又提高了性能,具体也请查看[高并发Java 五] JDK并发包1。

读写分离思想可以延伸,只要操作互不影响,锁就可以分离。

比如LinkedBlockingQueue

从头部取出,从尾部放数据。当然也类似于[高并发Java 六] JDK并发包2中提到的ForkJoinPool中的工作窃取。

1.4 锁粗化

通常情况下,为了保证多线程间的有效并发,会要求每个线程持有锁的时间尽量短,即在使用完公共资源后,应该立即释放锁。只有这样,等待在这个锁上的其他线程才能尽早的获得资源执行任务。但是,凡事都有一个度,如果对同一个锁不停的进行请求、同步和释放,其本身也会消耗系统宝贵的资源,反而不利于性能的优化 。

举个例子:

public void demoMethod(){
 synchronized(lock){
 //do sth.
 }
 //做其他不需要的同步的工作,但能很快执行完毕
 synchronized(lock){
 //do sth.
 }
 }

这种情况,根据锁粗化的思想,应该合并

public void demoMethod(){
 //整合成一次锁请求
 synchronized(lock){
 //do sth.
 //做其他不需要的同步的工作,但能很快执行完毕
 }
 }

当然这是有前提的,前提就是中间的那些不需要同步的工作是很快执行完成的。
再举一个极端的例子:

for(int i=0;i<CIRCLE;i++){
 synchronized(lock){ 

 }
 }

在一个循环内不同得获得锁。虽然JDK内部会对这个代码做些优化,但是还不如直接写成

synchronized(lock){
 for(int i=0;i<CIRCLE;i++){ 

 }
 }

当然如果有需求说,这样的循环太久,需要给其他线程不要等待太久,那只能写成上面那种。如果没有这样类似的需求,还是直接写成下面那种比较好。

1.5 锁消除

锁消除是在编译器级别的事情。

在即时编译器时,如果发现不可能被共享的对象,则可以消除这些对象的锁操作。

也许你会觉得奇怪,既然有些对象不可能被多线程访问,那为什么要加锁呢?写代码时直接不加锁不就好了。

但是有时,这些锁并不是程序员所写的,有的是JDK实现中就有锁的,比如Vector和StringBuffer这样的类,它们中的很多方法都是有锁的。当我们在一些不会有线程安全的情况下使用这些类的方法时,达到某些条件时,编译器会将锁消除来提高性能。

比如:

public static void main(String args[]) throws InterruptedException {
 long start = System.currentTimeMillis();
 for (int i = 0; i < 2000000; i++) {
 createStringBuffer("JVM", "Diagnosis");
 }
 long bufferCost = System.currentTimeMillis() - start;
 System.out.println("craeteStringBuffer: " + bufferCost + " ms");
 }

 public static String createStringBuffer(String s1, String s2) {
 StringBuffer sb = new StringBuffer();
 sb.append(s1);
 sb.append(s2);
 return sb.toString();
 }

上述代码中的StringBuffer.append是一个同步操作,但是StringBuffer却是一个局部变量,并且方法也并没有把StringBuffer返回,所以不可能会有多线程去访问它。
那么此时StringBuffer中的同步操作就是没有意义的。

开启锁消除是在JVM参数上设置的,当然需要在server模式下:

-server -XX:+DoEscapeAnalysis -XX:+EliminateLocks

并且要开启逃逸分析。 逃逸分析的作用呢,就是看看变量是否有可能逃出作用域的范围。
比如上述的StringBuffer,上述代码中craeteStringBuffer的返回是一个String,所以这个局部变量StringBuffer在其他地方都不会被使用。如果将craeteStringBuffer改成

public static StringBuffer craeteStringBuffer(String s1, String s2) {
 StringBuffer sb = new StringBuffer();
 sb.append(s1);
 sb.append(s2);
 return sb;
 }

那么这个 StringBuffer被返回后,是有可能被任何其他地方所使用的(譬如被主函数将返回结果put进map啊等等)。那么JVM的逃逸分析可以分析出,这个局部变量 StringBuffer逃出了它的作用域。

所以基于逃逸分析,JVM可以判断,如果这个局部变量StringBuffer并没有逃出它的作用域,那么可以确定这个StringBuffer并不会被多线程所访问,那么就可以把这些多余的锁给去掉来提高性能。

当JVM参数为:

-server -XX:+DoEscapeAnalysis -XX:+EliminateLocks

输出:

craeteStringBuffer: 302 ms

JVM参数为:

-server -XX:+DoEscapeAnalysis -XX:-EliminateLocks

输出:

craeteStringBuffer: 660 ms

显然,锁消除的效果还是很明显的。

2. 虚拟机内的锁优化

首先要介绍下对象头,在JVM中,每个对象都有一个对象头。

Mark Word,对象头的标记,32位(32位系统)。

描述对象的hash、锁信息,垃圾回收标记,年龄

还会保存指向锁记录的指针,指向monitor的指针,偏向锁线程ID等。

简单来说,对象头就是要保存一些系统性的信息。

2.1 偏向锁

所谓的偏向,就是偏心,即锁会偏向于当前已经占有锁的线程 。

大部分情况是没有竞争的(某个同步块大多数情况都不会出现多线程同时竞争锁),所以可以通过偏向来提高性能。即在无竞争时,之前获得锁的线程再次获得锁时,会判断是否偏向锁指向我,那么该线程将不用再次获得锁,直接就可以进入同步块。

偏向锁的实施就是将对象头Mark的标记设置为偏向,并将线程ID写入对象头Mark

当其他线程请求相同的锁时,偏向模式结束

JVM默认启用偏向锁 -XX:+UseBiasedLocking

在竞争激烈的场合,偏向锁会增加系统负担(每次都要加一次是否偏向的判断)

偏向锁的例子:

package test;

import java.util.List;
import java.util.Vector;

public class Test {
 public static List<Integer> numberList = new Vector<Integer>();

 public static void main(String[] args) throws InterruptedException {
 long begin = System.currentTimeMillis();
 int count = 0;
 int startnum = 0;
 while (count < 10000000) {
 numberList.add(startnum);
 startnum += 2;
 count++;
 }
 long end = System.currentTimeMillis();
 System.out.println(end - begin);
 }

}

Vector是一个线程安全的类,内部使用了锁机制。每次add都会进行锁请求。上述代码只有main一个线程再反复add请求锁。
使用如下的JVM参数来设置偏向锁:

-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0
BiasedLockingStartupDelay表示系统启动几秒钟后启用偏向锁。默认为4秒,原因在于,系统刚启动时,一般数据竞争是比较激烈的,此时启用偏向锁会降低性能。
由于这里为了测试偏向锁的性能,所以把延迟偏向锁的时间设置为0。

此时输出为9209

下面关闭偏向锁:

-XX:-UseBiasedLocking

输出为9627

一般在无竞争时,启用偏向锁性能会提高5%左右。

2.2 轻量级锁

Java的多线程安全是基于Lock机制实现的,而Lock的性能往往不如人意。

原因是,monitorenter与monitorexit这两个控制多线程同步的bytecode原语,是JVM依赖操作系统互斥(mutex)来实现的。

互斥是一种会导致线程挂起,并在较短的时间内又需要重新调度回原线程的,较为消耗资源的操作。

为了优化Java的Lock机制,从Java6开始引入了轻量级锁的概念。

轻量级锁(Lightweight Locking)本意是为了减少多线程进入互斥的几率,并不是要替代互斥。

它利用了CPU原语Compare-And-Swap(CAS,汇编指令CMPXCHG),尝试在进入互斥前,进行补救。

如果偏向锁失败,那么系统会进行轻量级锁的操作。它存在的目的是尽可能不用动用操作系统层面的互斥,因为那个性能会比较差。因为JVM本身就是一个应用,所以希望在应用层面上就解决线程同步问题。

总结一下就是轻量级锁是一种快速的锁定方法,在进入互斥之前,使用CAS操作来尝试加锁,尽量不要用操作系统层面的互斥,提高了性能。

那么当偏向锁失败时,轻量级锁的步骤:

1.将对象头的Mark指针保存到锁对象中(这里的对象指的就是锁住的对象,比如synchronized (this){},this就是这里的对象)。

lock->set_displaced_header(mark);

2.将对象头设置为指向锁的指针(在线程栈空间中)。

if (mark == (markOop) Atomic::cmpxchg_ptr(lock, obj()->mark_addr(),mark))
 {
 TEVENT (slow_enter: release stacklock) ;
 return ;
 }

lock位于线程栈中。所以判断一个线程是否持有这把锁,只要判断这个对象头指向的空间是否在这个线程栈的地址空间当中。
如果轻量级锁失败,表示存在竞争,升级为重量级锁(常规锁),就是操作系统层面的同步方法。在没有锁竞争的情况,轻量级锁减少传统锁使用OS互斥量产生的性能损耗。在竞争非常激烈时(轻量级锁总是失败),轻量级锁会多做很多额外操作,导致性能下降。

2.3 自旋锁

当竞争存在时,因为轻量级锁尝试失败,之后有可能会直接升级成重量级锁动用操作系统层面的互斥。也有可能再尝试一下自旋锁。

如果线程可以很快获得锁,那么可以不在OS层挂起线程,让线程做几个空操作(自旋),并且不停地尝试拿到这个锁(类似tryLock),当然循环的次数是有限制的,当循环次数达到以后,仍然升级成重量级锁。所以在每个线程对于锁的持有时间很少时,自旋锁能够尽量避免线程在OS层被挂起。

JDK1.6中-XX:+UseSpinning开启

JDK1.7中,去掉此参数,改为内置实现

如果同步块很长,自旋失败,会降低系统性能。如果同步块很短,自旋成功,节省线程挂起切换时间,提升系统性能。

2.4 偏向锁,轻量级锁,自旋锁总结

上述的锁不是Java语言层面的锁优化方法,是内置在JVM当中的。

首先偏向锁是为了避免某个线程反复获得/释放同一把锁时的性能消耗,如果仍然是同个线程去获得这个锁,尝试偏向锁时会直接进入同步块,不需要再次获得锁。

而轻量级锁和自旋锁都是为了避免直接调用操作系统层面的互斥操作,因为挂起线程是一个很耗资源的操作。

为了尽量避免使用重量级锁(操作系统层面的互斥),首先会尝试轻量级锁,轻量级锁会尝试使用CAS操作来获得锁,如果轻量级锁获得失败,说明存在竞争。但是也许很快就能获得锁,就会尝试自旋锁,将线程做几个空循环,每次循环时都不断尝试获得锁。如果自旋锁也失败,那么只能升级成重量级锁。

可见偏向锁,轻量级锁,自旋锁都是乐观锁。

3. 一个错误使用锁的案例

public class IntegerLock {
 static Integer i = 0;

 public static class AddThread extends Thread {
 public void run() {
 for (int k = 0; k < 100000; k++) {
 synchronized (i) {
  i++;
 }
 }
 }
 }

 public static void main(String[] args) throws InterruptedException {
 AddThread t1 = new AddThread();
 AddThread t2 = new AddThread();
 t1.start();
 t2.start();
 t1.join();
 t2.join();
 System.out.println(i);
 }
}

一个很初级的错误在于,在 [高并发Java 七] 并发设计模式提到,Interger是final不变的,每次++后,会产生一个新的 Interger再赋给i,所以两个线程争夺的锁是不同的。所以并不是线程安全的。

4. ThreadLocal及其源码分析

这里来提ThreadLocal可能有点不合适,但是ThreadLocal是可以把锁代替的方式。所以还是有必要提一下。

基本的思想就是,在一个多线程当中需要把有数据冲突的数据加锁,使用ThreadLocal的话,为每一个线程都提供一个对象实例。不同的线程只访问自己的对象,而不访问其他的对象。这样锁就没有必要存在了。

package test;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Test {
 private static final SimpleDateFormat sdf = new SimpleDateFormat(
 "yyyy-MM-dd HH:mm:ss");

 public static class ParseDate implements Runnable {
 int i = 0;

 public ParseDate(int i) {
 this.i = i;
 }

 public void run() {
 try {
 Date t = sdf.parse("2016-02-16 17:00:" + i % 60);
 System.out.println(i + ":" + t);
 } catch (ParseException e) {
 e.printStackTrace();
 }
 }
 }

 public static void main(String[] args) {
 ExecutorService es = Executors.newFixedThreadPool(10);
 for (int i = 0; i < 1000; i++) {
 es.execute(new ParseDate(i));
 }
 }

}

由于SimpleDateFormat并不线程安全的,所以上述代码是错误的使用。最简单的方式就是,自己定义一个类去用synchronized包装(类似于Collections.synchronizedMap)。这样做在高并发时会有问题,对 synchronized的争用导致每一次只能进去一个线程,并发量很低。

这里使用ThreadLocal去封装SimpleDateFormat就解决了这个问题

package test;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Test {
 static ThreadLocal<SimpleDateFormat> tl = new ThreadLocal<SimpleDateFormat>();

 public static class ParseDate implements Runnable {
 int i = 0;

 public ParseDate(int i) {
 this.i = i;
 }

 public void run() {
 try {
 if (tl.get() == null) {
  tl.set(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
 }
 Date t = tl.get().parse("2016-02-16 17:00:" + i % 60);
 System.out.println(i + ":" + t);
 } catch (ParseException e) {
 e.printStackTrace();
 }
 }
 }

 public static void main(String[] args) {
 ExecutorService es = Executors.newFixedThreadPool(10);
 for (int i = 0; i < 1000; i++) {
 es.execute(new ParseDate(i));
 }
 }

}

每个线程在运行时,会判断是否当前线程有SimpleDateFormat对象

if (tl.get() == null)

如果没有的话,就new个 SimpleDateFormat与当前线程绑定

tl.set(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));

然后用当前线程的 SimpleDateFormat去解析

tl.get().parse("2016-02-16 17:00:" + i % 60);

一开始的代码中,只有一个 SimpleDateFormat,使用了 ThreadLocal,为每一个线程都new了一个 SimpleDateFormat。

需要注意的是,这里不要把公共的一个SimpleDateFormat设置给每一个ThreadLocal,这样是没用的。需要给每一个都new一个SimpleDateFormat。

在hibernate中,对ThreadLocal有典型的应用。

下面来看一下ThreadLocal的源码实现

首先Thread类中有一个成员变量:

ThreadLocal.ThreadLocalMap threadLocals = null;

而这个Map就是ThreadLocal的实现关键

public void set(T value) {
  Thread t = Thread.currentThread();
  ThreadLocalMap map = getMap(t);
  if (map != null)
   map.set(this, value);
  else
   createMap(t, value);
 }

根据 ThreadLocal可以set和get相对应的value。

这里的ThreadLocalMap实现和HashMap差不多,但是在hash冲突的处理上有区别。

ThreadLocalMap中发生hash冲突时,不是像HashMap这样用链表来解决冲突,而是是将索引++,放到下一个索引处来解决冲突。

(0)

相关推荐

  • java 多线程-锁详解及示例代码

    自 Java 5 开始,java.util.concurrent.locks 包中包含了一些锁的实现,因此你不用去实现自己的锁了.但是你仍然需要去了解怎样使用这些锁. 一个简单的锁 让我们从 java 中的一个同步块开始: public class Counter{ private int count = 0; public int inc(){ synchronized(this){ return ++count; } } } 可以看到在 inc()方法中有一个 synchronized(th

  • Java分布式锁的三种实现方案

    方案一:数据库乐观锁 乐观锁通常实现基于数据版本(version)的记录机制实现的,比如有一张红包表(t_bonus),有一个字段(left_count)记录礼物的剩余个数,用户每领取一个奖品,对应的left_count减1,在并发的情况下如何要保证left_count不为负数,乐观锁的实现方式为在红包表上添加一个版本号字段(version),默认为0. 异常实现流程 -- 可能会发生的异常情况 -- 线程1查询,当前left_count为1,则有记录 select * from t_bonus

  • Java 锁的知识总结及实例代码

    java中有哪些锁 这个问题在我看了一遍<java并发编程>后尽然无法回答,说明自己对于锁的概念了解的不够.于是再次翻看了一下书里的内容,突然有点打开脑门的感觉.看来确实是要学习的最好方式是要带着问题去学,并且解决问题. 在java中锁主要两类:内部锁synchronized和显示锁java.util.concurrent.locks.Lock.但细细想这貌似总结的也不太对.应该是由java内置的锁和concurrent实现的一系列锁. 为什么这说,因为在java中一切都是对象,而java对每

  • Java 高并发四:无锁详细介绍

    在[高并发Java 一] 前言中已经提到了无锁的概念,由于在jdk源码中有大量的无锁应用,所以在这里介绍下无锁. 1 无锁类的原理详解 1.1 CAS CAS算法的过程是这样:它包含3个参数CAS(V,E,N).V表示要更新的变量,E表示预期值,N表示新值.仅当V 值等于E值时,才会将V的值设为N,如果V值和E值不同,则说明已经有其他线程做了更新,则当前线程什么 都不做.最后,CAS返回当前V的真实值.CAS操作是抱着乐观的态度进行的,它总是认为自己可以成功完成 操作.当多个线程同时使用CAS操

  • Java编程实现排他锁代码详解

    一 .前言 某年某月某天,同事说需要一个文件排他锁功能,需求如下: (1)写操作是排他属性 (2)适用于同一进程的多线程/也适用于多进程的排他操作 (3)容错性:获得锁的进程若Crash,不影响到后续进程的正常获取锁 二 .解决方案 1. 最初的构想 在Java领域,同进程的多线程排他实现还是较简易的.比如使用线程同步变量标示是否已锁状态便可.但不同进程的排他实现就比较繁琐.使用已有API,自然想到 java.nio.channels.FileLock:如下 /** * @param file

  • Java 高并发九:锁的优化和注意事项详解

    摘要 本系列基于炼数成金课程,为了更好的学习,做了系列的记录. 本文主要介绍: 1. 锁优化的思路和方法 2. 虚拟机内的锁优化 3. 一个错误使用锁的案例 4. ThreadLocal及其源码分析 1. 锁优化的思路和方法 在[高并发Java 一] 前言中有提到并发的级别. 一旦用到锁,就说明这是阻塞式的,所以在并发度上一般来说都会比无锁的情况低一点. 这里提到的锁优化,是指在阻塞式的情况下,如何让性能不要变得太差.但是再怎么优化,一般来说性能都会比无锁的情况差一点. 这里要注意的是,在[高并

  • java高并发的线程中断的几种方式详解

    目录 通过一个变量控制线程中断 通过线程自带的中断标志控制 线程阻塞状态中如何中断? 总结 通过一个变量控制线程中断 代码: package com.itsoku.chat05; import java.util.concurrent.TimeUnit; /** * 微信公众号:路人甲Java,专注于java技术分享(带你玩转 爬虫.分布式事务.异步消息服务.任务调度.分库分表.大数据等),喜欢请关注! */ public class Demo1 { public volatile static

  • Linux下高并发socket最大连接数所受的各种限制(详解)

    1.修改用户进程可打开文件数限制 在Linux平台上,无论编写客户端程序还是服务端程序,在进行高并发TCP连接处理时,最高的并发数量都要受到系统对用户单一进程同时可打开文件数量的限制(这是因为系统为每个TCP连接都要创建一个socket句柄,每个socket句柄同时也是一个文件句柄).可使用ulimit命令查看系统允许当前用户进程打开的文件数限制: [speng@as4 ~]$ ulimit -n 1024 这表示当前用户的每个进程最多允许同时打开1024个文件,这1024个文件中还得除去每个进

  • Java高版本Api在Android中的使用方法详解

    目录 Android插件开启对新Api的支持 常用的需要兼容处理的类: 1. LocalDate日期处理 2. Stream集合流操作 AGP7编译的问题 总结 Android插件开启对新Api的支持 这一天小王导入了一个库,上线之后直接崩了一大片? 找到其中的问题: 什么鬼哦?安卓8.0一下无法使用? 这样上线8.0以下的手机全部闪退了. 查一下才知道需要开启插件启动对Java Api的支持 android { defaultConfig { multiDexEnabled true } co

  • 详解Java高并发编程之AtomicReference

    目录 一.AtomicReference 基本使用 1.1.使用 synchronized 保证线程安全性 二.了解 AtomicReference 2.1.使用 AtomicReference 保证线程安全性 2.2.AtomicReference 源码解析 2.2.1.get and set 2.2.2.lazySet 方法 2.2.3.getAndSet 方法 2.2.4.compareAndSet 方法 2.2.5.weakCompareAndSet 方法 一.AtomicReferen

  • java 高并发中volatile的实现原理

    java 高并发中volatile的实现原理 摘要: 在多线程并发编程中synchronized和Volatile都扮演着重要的角色,Volatile是轻量级的synchronized,它在多处理器开发中保证了共享变量的"可见性".可见性的意思是当一个线程修改一个共享变量时,另外一个线程能读到这个修改的值.它在某些情况下比synchronized的开销更小 1. 定义: java编程语言允许线程访问共享变量,为了确保共享变量能被准确和一致的更新,线程应该确保通过排他锁单独获得这个变量.

  • Java高并发BlockingQueue重要的实现类详解

    ArrayBlockingQueue 有界的阻塞队列,内部是一个数组,有边界的意思是:容量是有限的,必须进行初始化,指定它的容量大小,以先进先出的方式存储数据,最新插入的在对尾,最先移除的对象在头部. public class ArrayBlockingQueue<E> extends AbstractQueue<E> implements BlockingQueue<E>, java.io.Serializable { /** 队列元素 */ final Object

  • Java 高并发的三种实现案例详解

    提到锁,大家肯定想到的是sychronized关键字.是用它可以解决一切并发问题,但是,对于系统吞吐量要求更高的话,我们这提供几个小技巧.帮助大家减小锁颗粒度,提高并发能力. 初级技巧-乐观锁 乐观锁使用的场景是,读不会冲突,写会冲突.同时读的频率远大于写.  悲观锁的实现: 悲观的认为所有代码执行都会有并发问题,所以将所有代码块都用sychronized锁住 乐观锁的实现: 乐观的认为在读的时候不会产生冲突为题,在写时添加锁.所以解决的应用场景是读远大于写时的场景. 中级技巧-String.i

  • 浅谈Redis高并发缓存架构性能优化实战

    目录 场景1: 中小型公司Redis缓存架构以及线上问题实战 场景2: 大厂线上大规模商品缓存数据冷热分离实战 场景3: 基于DCL机制解决热点缓存并发重建问题实战 场景4: 突发性热点缓存重建导致系统压力暴增 场景5: 解决大规模缓存击穿导致线上数据库压力暴增 场景6: 黑客工资导致缓存穿透线上数据库宕机 场景7: 大V直播带货导致线上商品系统崩溃原因分析 场景8: Redis分布式锁解决缓存与数据库双写不一致问题实战 场景9: 大促压力暴增导致分布式锁串行争用问题优化 场景10: 利用多级缓

  • Java高并发测试框架JCStress详解

    前言 如果要研究高并发,一般会借助高并发工具来进行测试.JCStress(Java Concurrency Stress)它是OpenJDK中的一个高并发测试工具,它可以帮助我们研究在高并发场景下JVM,类库以及硬件等状况. JCStress学起来很简单,而且官方也提供了许多高并发场景下的测试用例,只要引入一个jar包,即可运行研究. 如何使用JCStress 此演示用maven工程,首先需要引入jar包,核心包是必须要的,样例包非必须要,此是为了演示其中的例子. <dependencies>

随机推荐