详谈ThreadLocal-单例模式下高并发线程安全

目录
  • ThreadLocal-单例模式下高并发线程安全
    • 为了解决线程安全的问题,我们有3个思路:
  • 多线程中的ThreadLocal
    • 1.ThreadLocal概述
    • 2. ThreadLocal简单实用
    • 3.ThreadLocal的内部原理
      • 3.1 get方法
      • 3.2 set方法
      • 3.3 remove方法
      • 3.4 initialValue方法
    • 4. 总结
    • 5. ThreadLocalMap引发的内存泄漏

ThreadLocal-单例模式下高并发线程安全

在多例的情况下,每个对象在堆中声明内存空间,多线程对应的Java栈中的句柄或指针指向堆中不同的对象,对象各自变量的变更只会印象到对应的栈,也就是对应的线程中,不会影响到其它线程。所以多例的情况下不需要考虑线程安全的问题,因为一定是安全的。

而在单例的情况下却完全不一样了,在堆中只有一个对象,多线程对应的Java栈中的句柄或指针指向同一个对象,方法的参数变量和方法内变量是线程安全的,因为每执行一个方法,都会在独立的空间创建局部变量,它不是共享的资源。但是成员变量只有一份,所有指向堆中该对象的句柄或指针都可以随时修改和读取它,所以是非线程安全的。

为了解决线程安全的问题,我们有3个思路:

  • 第一每个线程独享自己的操作对象,也就是多例,多例势必会带来堆内存占用、频繁GC、对象初始化性能开销等待等一些列问题。
  • 第二单例模式枷锁,典型的案例是HashTable和HashMap,对读取和变更的操作用synchronized限制起来,保证同一时间只有一个线程可以操作该对象。虽然解决了内存、回收、构造、初始化等问题,但是势必会因为锁竞争带来高并发下性能的下降。
  • 第三个思路就是今天重点推出的ThreadLocal。单例模式下通过某种机制维护成员变量不同线程的版本。
  • 假设三个人想从镜子中看自己,第一个方案就是每人发一个镜子互不干扰,第二个方案就是只有一个镜子,一个人站在镜子前其他人要排队等候,第三个方案就是我这里发明了一种“魔镜”,所有人站在镜子前可以并且只能看到自己!!!

主程序:

public static void main(String[] args) {
		//Mirror是个单例的,只构建了一个对象
		Mirror mirror = new Mirror("魔镜");

		//三个线程都在用这面镜子
		MirrorThread thread1 = new MirrorThread(mirror,"张三");
		MirrorThread thread2 = new MirrorThread(mirror,"李四");
		MirrorThread thread3 = new MirrorThread(mirror,"王二");

		thread1.start();
		thread2.start();
		thread3.start();
	}

很好理解,创建了一面镜子,3个人一起照镜子。

MirrorThread:

public class MirrorThread extends Thread{
	private Mirror mirror;
	private String threadName;
	public MirrorThread(Mirror mirror, String threadName){
		this.mirror = mirror;
		this.threadName = threadName;
	}

	//照镜子
	public String lookMirror() {
		return threadName+" looks like "+ mirror.getNowLookLike().get();
	}

	//化妆
	public void makeup(String makeupString) {
		mirror.getNowLookLike().set(makeupString);
	}

	@Override
    public void run() {
		int i = 1;//阈值
		while(i<5) {
			try {
				long nowFace = (long)(Math.random()*5000);
				sleep(nowFace);
				StringBuffer sb = new StringBuffer();
				sb.append("第"+i+"轮从");
				sb.append(lookMirror());
				makeup(String.valueOf(nowFace));
				sb.append("变为");
				sb.append(lookMirror());
				System.out.println(sb);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			i++;
		}
	}
}

也很好理解,就是不断的更新自己的外貌同时从镜子里读取自己的外貌。

重点是Mirror:

public class Mirror {
	private String mirrorName;
	//每个人要看到自己的样子,所以这里要用ThreadLocal
	private ThreadLocal<String> nowLookLike;
	public Mirror(String mirrorName){
		this.mirrorName=mirrorName;
		nowLookLike = new ThreadLocal<String>();
	}

	public String getMirrorName() {
		return mirrorName;
	}

	public ThreadLocal<String> getNowLookLike() {
		return nowLookLike;
	}
}

对每个人长的样子用ThreadLocal类型来表示。

先看测试结果:

第1轮从张三 looks like null变为张三 looks like 3008

第2轮从张三 looks like 3008变为张三 looks like 490

第1轮从王二 looks like null变为王二 looks like 3982

第1轮从李四 looks like null变为李四 looks like 4390

第2轮从王二 looks like 3982变为王二 looks like 1415

第2轮从李四 looks like 4390变为李四 looks like 1255

第3轮从王二 looks like 1415变为王二 looks like 758

第3轮从张三 looks like 490变为张三 looks like 2746

第3轮从李四 looks like 1255变为李四 looks like 845

第4轮从李四 looks like 845变为李四 looks like 1123

第4轮从张三 looks like 2746变为张三 looks like 2126

第4轮从王二 looks like 758变为王二 looks like 4516

OK,一面镜子所有人一起照,而且每个人都只能看的到自己的变化,这就达成了单例线程安全的目的。

我们来细看下它是怎么实现的。

先来看Thread:

Thread中维护了一个ThreadLocal.ThreadLocalMapthreadLocals = null; ThreadLocalMap这个Map的key是ThreadLocal,value是维护的成员变量。现在的跟踪链是Thread->ThreadLocalMap-><ThreadLocal,Object>,那么我们只要搞明白Thread怎么跟ThreadLocal关联的,从线程里找到自己关心的成员变量的快照这条线就通了。

ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
            table = new Entry[INITIAL_CAPACITY];
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }

再来看ThreadLocal:它里面核心方法两个get()和set(T)

public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }
public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

方法里通过Thread.currentThread()的方法得到当前线程,然后做为key存储到当前线程对象的threadLocals中,也就是TreadLocalMap中。

OK,这样整个关系链已经建立,真正要去访问的成员变量在一个map中,key是线程号,值是属于该线程的快照。

ThreadLocal里还有map的创建createMap(t, value)、取值时对象的初始值setInitialValue()、线程结束时对象的释放remove()等细节,有兴趣的可以继续跟进了解下。

ThreadLocal应用其实很多,例如Spring容器中实例默认是单例的,transactionManager也一样,那么事务在处理时单例的manager是如何控制每个线程的事务要如何处理呢,这里面就应用了大量的ThreadLocal。

多线程中的ThreadLocal

1.ThreadLocal概述

多线程的并发问题主要存在于多个线程对于同一个变量进行修改产生的数据不一致的问题,同一个变量指的值同一个对象的成员变量或者是同一个类的静态变量。之前我们常听过尽量不要使用静态变量,会引起并发问题,那么随着Spring框架的深入人心,单例中的成员变量也出现了多线程并发问题。Struts2接受参数采用成员变量自动封装,为此在Spring的配置采用多例模式,而SpringMVC将Spring的容器化发挥到极致,将接受的参数放到了注解和方法的参数中,从而避免了单例出现的线程问题。今天,我们讨论的是JDK从1.2就出现的一个并发工具类ThreadLocal,他除了加锁这种同步方式之外的另一种保证一种规避多线程访问出现线程不安全的方法,当我们在创建一个变量后,如果每个线程对其进行访问的时候访问的都是线程自己的变量这样就不会存在线程不安全问题。我们先看一下官方是怎么解释这个变量的?

大致意思是:此类提供了局部变量表。这些变量与普通变量不同不同之处是,每一个通过get或者set方法访问一个线程都是他自己的,将变量的副本独立初始化。ThreadLocal实例通常作用于希望将状态与线程关联的类中的私有静态字段(例如,用户ID或交易ID)。

只要线程是活动的并且可以访问{@code ThreadLocal}实例, 每个线程都会对其线程局部变量的副本保留隐式引用。 线程消失后,其线程本地实例的所有副本都将进行垃圾回收(除非存在对这些副本的其他引用)。也就是说,如果创建一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个副本,在实际多线程操作的时候,操作的是自己本地内存中的变量,从而规避了线程安全问题。而每个线程的副本全部放到ThreadLocalMap中。

2. ThreadLocal简单实用

public class ThreadLocalExample {
    public static class MyRunnable implements Runnable {
        private ThreadLocal<Double> threadLocal = new ThreadLocal();
        private Double variable;

        @Override
        public void run() {
            threadLocal.set(Math.floor(Math.random() * 100D));
            variable = Math.floor(Math.random() * 100D);
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
            }

            System.out.println("ThreadValue==>"+threadLocal.get());
            System.out.println("Variable==>"+variable);
        }
    }

    public static void main(String[] args) {
        MyRunnable sharedRunnableInstance = new MyRunnable();
        Thread thread1 = new Thread(sharedRunnableInstance);
        Thread thread2 = new Thread(sharedRunnableInstance);
        thread1.start();
        thread2.start();
    }
}

通过上面的例子,我们发现将Double放入ThreadLocal中,不会出现多线程并发问题,而成员变量variable却发生了多线程并发问题。

3.ThreadLocal的内部原理

通过源码我们发现ThreadLocal主要提供了下面五个方法:

/**
  * Returns the value in the current thread's copy of this
  * thread-local variable.  If the variable has no value for the
  * current thread, it is first initialized to the value returned
  * by an invocation of the {@link #initialValue} method.
  *
  * 返回此线程局部变量的当前线程副本中的值。
  * 如果该变量没有当前线程的值,则首先将其初始化为调用{@link #initialValue}方法返回的值。
  * @return the current thread's value of this thread-local
  */
public T get() { }

/**
  * Sets the current thread's copy of this thread-local variable
  * to the specified value.  Most subclasses will have no need to
  * override this method, relying solely on the {@link #initialValue}
  * method to set the values of thread-locals.
  *
  * 将此线程局部变量的当前线程副本设置为指定值。
  * 大多数子类将不需要重写此方法,而仅依靠{@link #initialValue}方法来设置线程局部变量的值。
  *
  * @param value the value to be stored in the current thread's copy of
  *              this thread-local.
  *              要存储在此本地线程的当前线程副本中的值。
  */
public void set(T value) { }

/**
  * Removes the current thread's value for this thread-local
  * variable.  If this thread-local variable is subsequently
  * {@linkplain #get read} by the current thread, its value will be
  * reinitialized by invoking its {@link #initialValue} method,
  * unless its value is {@linkplain #set set} by the current thread
  * in the interim.  This may result in multiple invocations of the
  * {@code initialValue} method in the current thread.
  * 删除此线程局部变量的当前线程值。
  * 如果此线程局部变量随后被当前线程{@linkplain #get read}调用,
  * 则其值将通过调用其{@link #initialValue}方法来重新初始化,
  * 除非当前值是在此期间被设置{@linkplain #set set}。
  * 这可能会导致在当前线程中多次调用{@code initialValue}方法。
  * @since 1.5
  */
public void remove() { }

/**
  * Returns the current thread's "initial value" for this
  * thread-local variable.  This method will be invoked the first
  * time a thread accesses the variable with the {@link #get}
  * method, unless the thread previously invoked the {@link #set}
  * method, in which case the {@code initialValue} method will not
  * be invoked for the thread.  Normally, this method is invoked at
  * most once per thread, but it may be invoked again in case of
  * subsequent invocations of {@link #remove} followed by {@link #get}.
  * 返回此线程局部变量的当前线程的“初始值”。
  * 除非线程先前调用了{@link #set}方法,
  * 否则线程第一次使用{@link #get}方法访问该变量时将调用此方法,
  * 在这种情况下,{@ code initialValue}方法将不会为线程被调用。
  * 通常,每个线程最多调用一次此方法,
  * 但是在随后调用{@link #remove}之后再调用{@link #get}的情况下,可以再次调用此方法。
  *
  * <p>This implementation simply returns {@code null}; if the
  * programmer desires thread-local variables to have an initial
  * value other than {@code null}, {@code ThreadLocal} must be
  * subclassed, and this method overridden.  Typically, an
  * anonymous inner class will be used.
  * 此实现仅返回{@code null};如果程序员希望线程局部变量的初始值不是{@code null},
  * 则必须将{@code ThreadLocal}子类化,并重写此方法。通常,将使用匿名内部类。
  *
  * @return the initial value for this thread-local
  */
protected T initialValue(){ }

3.1 get方法

 public T get() {
    //获取当前线程
    Thread t = Thread.currentThread();
    //通过当前线程获取ThreadLocalMap
    //Thread类中包含一个ThreadLocalMap的成员变量
    ThreadLocalMap map = getMap(t);
    //如果不为空,则通过ThreadLocalMap中获取对应value值
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    //如果为空,需要初始化值
    return setInitialValue();
}
private T setInitialValue() {
    T value = initialValue();
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        //如果为空,则创建
        createMap(t, value);
    return value;
}

首先是取得当前线程,然后通过getMap(t)方法获取到一个map,map的类型为ThreadLocalMap。然后接着下面获取到<key,value>键值对,注意这里获取键值对传进去的是 this,而不是当前线程t。 如果获取成功,则返回value值。如果map为空,则调用setInitialValue方法返回value。

在setInitialValue方法中,首先执行了initialValue方法(我们上面提到的最后一个方法),接着通过当前线程获取ThreadLocalMap,如果不存在则创建。创建的代码很简单,只是通过ThreadLocal对象和设置的Value值创建ThreadLocalMap对象。

3.2 set方法

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

这个方法和setInitialValue方法的业务逻辑基本相同,只不过setInitialValue调用了initialValue()的钩子方法。这里代码简单,我们就不做过多解释。

3.3 remove方法

public void remove() {
    ThreadLocalMap m = getMap(Thread.currentThread());
    if (m != null)
        m.remove(this);
}

这个方法是从jdk1.5才出现的。处理逻辑也很很简单。通过当前线程获取到ThreadLocalMap对象,然后移除此ThreadLocal。

3.4 initialValue方法

protected T initialValue() {
    return null;
}

是不是感觉简单了,什么也没有处理,直接返回一个null,那么何必如此设计呢?当我们发现他的修饰符就会发现,他应该是一个钩子方法,主要用于提供子类实现的。追溯到源码中我们发现,Supplier的影子,这就是和jdk8的lamda表达式关联上了。

static final class SuppliedThreadLocal<T> extends ThreadLocal<T> {
    private final Supplier<? extends T> supplier;
    SuppliedThreadLocal(Supplier<? extends T> supplier) {
        this.supplier = Objects.requireNonNull(supplier);
    }

    @Override
    protected T initialValue() {
        return supplier.get();
    }
}

4. 总结

在每个线程Thread内部有一个ThreadLocal.ThreadLocalMap类型的成员变量threadLocals,这个threadLocals就是用来存储实际的变量副本的,键值为当前ThreadLocal变量,value为变量副本(即T类型的变量)。之所以这里是一个map,是因为通过线程会存在多个类中定义ThreadLocal的成员变量。初始时,在Thread里面,threadLocals为空,当通过ThreadLocal变量调用get()方法或者set()方法,就会对Thread类中的threadLocals进行初始化,并且以当前ThreadLocal变量为键值,以ThreadLocal要保存的副本变量为value,存到threadLocals; 然后在当前线程里面,如果要使用副本变量,就可以通过get方法在threadLocals里面查找。

5. ThreadLocalMap引发的内存泄漏

ThreadLocal属于一个工具类,他为用户提供get、set、remove接口操作实际存放本地变量的threadLocals(调用线程的成员变量),也知道threadLocals是一个ThreadLocalMap类型的变量。下面我们来看看ThreadLocalMap这个类的一个entry:

static class Entry extends WeakReference<ThreadLocal<?>> {
    /** The value associated with this ThreadLocal. */
    Object val
    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
    }
}

public WeakReference(T referent) {
    super(referent); //referent:ThreadLocal的引用
}

//Reference构造方法
Reference(T referent) {
    this(referent, null);//referent:ThreadLocal的引用
}

Reference(T referent, ReferenceQueue<? super T> queue) {
    this.referent = referent;
    this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
}

在上面的代码中,我们可以看出,当前ThreadLocal的引用k被传递给WeakReference的构造函数,所以ThreadLocalMap中的key为ThreadLocal的弱引用。当一个线程调用ThreadLocal的set方法设置变量的时候,当前线程的ThreadLocalMap就会存放一个记录,这个记录的key值为ThreadLocal的弱引用,value就是通过set设置的值。如果当前线程一直存在且没有调用该ThreadLocal的remove方法,如果这个时候别的地方还有对ThreadLocal的引用,那么当前线程中的ThreadLocalMap中会存在对ThreadLocal变量的引用和value对象的引用,是不会释放的,就会造成内存泄漏。

考虑这个ThreadLocal变量没有其他强依赖,如果当前线程还存在,由于线程的ThreadLocalMap里面的key是弱引用,所以当前线程的ThreadLocalMap里面的ThreadLocal变量的弱引用在gc的时候就被回收,但是对应的value还是存在的这就可能造成内存泄漏(因为这个时候ThreadLocalMap会存在key为null但是value不为null的entry项)。

总结:THreadLocalMap中的Entry的key使用的是ThreadLocal对象的弱引用,在没有其他地方对ThreadLoca依赖,ThreadLocalMap中的ThreadLocal对象就会被回收掉,但是对应的不会被回收,这个时候Map中就可能存在key为null但是value不为null的项,这需要实际的时候使用完毕及时调用remove方法避免内存泄漏。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • Spring如何解决单例bean线程不安全的问题

    首先我们应该知道线程安全问题一般发生在成员变量上,这是为什么啦? 因为成员变量是存放在堆内存中,而堆内存又是线程共享的,这就造成了线程安全问题 因为Spring中的Bean默认是单例的,所以在定义成员变量时也有可能会发生线程安全问题.下面我们就来研究下如何解决Spring中单例Bean的线程安全问题 @RestController //@Scope("prototype") public class BeanController { private int content=0; //基

  • 浅谈Spring中单例Bean是线程安全的吗

    Spring容器中的Bean是否线程安全,容器本身并没有提供Bean的线程安全策略,因此可以说Spring容器中的Bean本身不具备线程安全的特性,但是具体还是要结合具体scope的Bean去研究. Spring 的 bean 作用域(scope)类型 1.singleton:单例,默认作用域. 2.prototype:原型,每次创建一个新对象. 3.request:请求,每次Http请求创建一个新对象,适用于WebApplicationContext环境下. 4.session:会话,同一个会

  • JAVA如何解决并发问题

    并发问题的根源在哪 首先,我们要知道并发要解决的是什么问题?并发要解决的是单进程情况下硬件资源无法充分利用的问题.而造成这一问题的主要原因是CPU-内存-磁盘三者之间速度差异实在太大.如果将CPU的速度比作火箭的速度,那么内存的速度就像火车,而最惨的磁盘,基本上就相当于人双腿走路. 这样造成的一个问题,就是CPU快速执行完它的任务的时候,很长时间都会在等待磁盘或是内存的读写. 计算机的发展有一部分就是如何重复利用资源,解决硬件资源之间效率的不平衡,而后就有了多进程,多线程的发展.并且演化出了各种

  • 分享40个Java多线程问题小结

    Java多线程是什么 Java提供的并发(同时.独立)处理多个任务的机制.多个线程共存于同一JVM进程里面,所以共用相同的内存空间,较之多进程,多线程之间的通信更轻量级.依我的理解,Java多线程完全就是为了提高CPU的利用率.Java的线程有4种状态,新建(New).运行(Runnable).阻塞(Blocked).结束(Dead),关键就在于阻塞(Blocked),阻塞意味着等待,阻塞的的线程不参与线程分派器(Thread Scheduler)的时间片分配,自然也就不会使用到CPU.多线程环

  • 详谈ThreadLocal-单例模式下高并发线程安全

    目录 ThreadLocal-单例模式下高并发线程安全 为了解决线程安全的问题,我们有3个思路: 多线程中的ThreadLocal 1.ThreadLocal概述 2. ThreadLocal简单实用 3.ThreadLocal的内部原理 3.1 get方法 3.2 set方法 3.3 remove方法 3.4 initialValue方法 4. 总结 5. ThreadLocalMap引发的内存泄漏 ThreadLocal-单例模式下高并发线程安全 在多例的情况下,每个对象在堆中声明内存空间,

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

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

  • C++高并发内存池的整体设计和实现思路

    目录 一.整体设计 1.需求分析 2.总体设计思路 3.申请内存流程图 二.详细设计 1.各个模块内部结构详细剖析 2.设计细节 三.测试 一.整体设计 1.需求分析 池化技术是计算机中的一种设计模式,内存池是常见的池化技术之一,它能够有效的提高内存的申请和释放效率以及内存碎片等问题,但是传统的内存池也存在一定的缺陷,高并发内存池相对于普通的内存池它有自己的独特之处,解决了传统内存池存在的一些问题. 附:实现一个内存池管理的类方法 1)直接使用new/delete.malloc/free存在的问

  • java高并发情况下高效的随机数生成器

    前言 在代码中生成随机数,是一个非常常用的功能,并且JDK已经提供了一个现成的Random类来实现它,并且Random类是线程安全的. 下面是Random.next()生成一个随机整数的实现: protected int next(int bits) { long oldseed, nextseed; AtomicLong seed = this.seed; do { oldseed = seed.get(); nextseed = (oldseed * multiplier + addend)

  • 数据库高并发情况下重复值写入的避免 字段组合约束

    10线程同时操作,频繁出现插入同样数据的问题.虽然在插入数据的时候使用了: insert inti tablename(fields....) select @t1,@t2,@t3 from tablename where not exists (select id from tablename where t1=@t1,t2=@t2,t3=@t3) 当时还是在高并发的情况下无效.此语句也包含在存储过程中.(之前也尝试线判断有无记录再看是否写入,无效). 因此,对于此类情况还是需要从数据库的根本

  • java web在高并发和分布式下实现订单号生成唯一的解决方案

    方案一: 如果没有并发,订单号只在一个线程内产生,那么由于程序是顺序执行的,不同订单的生成时间戳正常不同,因此用时间戳+随机数(或自增数)就可以区分各个订单.如果存在并发,且订单号是由一个进程中的多个线程产生的,那么只要把线程ID添加到序列号中就可以保证订单号唯一.如果存在并发,且订单号是由同一台主机中的多个进程产生的,那么只要把进程ID添加到序列号中就可以保证订单号唯一.如果存在并发,且订单号是由不同台主机产生的,那么MAC地址.IP地址或CPU序列号等能够区分主机的号码添加到序列号中就可以保

  • 详解nginx高并发场景下的优化

    在日常的运维工作中,经常会用到nginx服务,也时常会碰到nginx因高并发导致的性能瓶颈问题.今天这里简单梳理下nginx性能优化的配置(仅仅依据本人的实战经验而述,如有不妥,敬请指出~) 一.这里的优化主要是指对nginx的配置优化,一般来说nginx配置文件中对优化比较有作用的主要有以下几项: 1)nginx进程数,建议按照cpu数目来指定,一般跟cpu核数相同或为它的倍数. worker_processes 8; 2)为每个进程分配cpu,上例中将8个进程分配到8个cpu,当然可以写多个

  • Java Semaphore实现高并发场景下的流量控制

    目录 前言 Semaphore介绍 代码演示 补充 独占锁与共享锁 公平锁与非公平锁 可重入锁 前言 在java开发的工作中是否会出现这样的场景,你需要实现一些异步运行的任务,该任务可能存在消耗大量内存的情况,所以需要对任务进行并发控制.如何优雅的实现并发控制呢?下面我会给大家介绍一个类--Semaphore,能很优雅的实现并发控制,继续往下看吧. Semaphore介绍 首先我们看一下Semaphore类的构造函数是如何实现的. public Semaphore(int permits, bo

  • 浅谈C++高并发场景下读多写少的优化方案

    目录 概述 分析 双缓冲 工程实现上需要攻克的难点 核心代码实现 简单说说golang中双缓冲的实现 相关文献 概述 一谈到高并发的优化方案,往往能想到模块水平拆分.数据库读写分离.分库分表,加缓存.加mq等,这些都是从系统架构上解决.单模块作为系统的组成单元,其性能好坏也能很大的影响整体性能,本文从单模块下读多写少的场景出发,探讨其解决方案,以其更好的实现高并发.不同的业务场景,读和写的频率各有侧重,有两种常见的业务场景: 读多写少:典型场景如广告检索端.白名单更新维护.loadbalance

  • Redis高并发场景下秒杀超卖解决方案(秒杀场景)

    目录 1 什么是秒杀 2 为什么要防止超卖 3 单体架构常规秒杀 3.1 常规减库存代码 3.2 模拟高并发 3.3 超卖现象 3.4 分析原因 4 简单实现悲观乐观锁解决单体架构超卖 4.1 悲观锁 4.2 乐观锁 4.3 redis锁setnx 4.4 使用Redision 5 分布式锁的解决方案 6 采用缓存队列防止超卖 1 什么是秒杀 秒杀最直观的定义:在高并发场景下而下单某一个商品,这个过程就叫秒杀 [秒杀场景] 火车票抢票 双十一限购商品 热度高的明星演唱会门票 … 2 为什么要防止

随机推荐