首页 > 其他分享 >【并发】sychronized、volatile

【并发】sychronized、volatile

时间:2023-01-05 21:13:47浏览次数:42  
标签:同步 monitor synchronized 对象 并发 线程 内存 sychronized volatile

介绍

数据不一致问题,究其原因是多个线程对同一对象的成员变量同时操作引起的。synchronize关键字提供排他机制,在同一时间点只有一个线程执行。

1. 使用方式

  1. 同步方法
    解决了数据不一致问题,但仍存在两个问题:1.只有一个线程在执行;2.不够快。
  2. 同步代码块
    将需要同步的代码,用sychronized做同步代码块
  3. sychronized虽然都是加锁,但加锁的对象不同:
    • 指定加锁对象:给指定对象加锁,进入同步代码块要获取该对象的锁;
    • 作用于实例方法:相当于对当前实例加锁,进入同步代码前要获取到该实例的锁;
    • 作用于静态方法:相当于对当前类加锁,进入同步代码前要获取该类的锁。

使用注意

  • synchronize锁的对象不能为空
class Ticket implements Runnable {
    private Object lock = null;
    @Override
    public void run() {
        synchronized (lock) {
           //....
        }
    }
}
  • synchronize作用域太大:不推荐
    synchronize有排他性,所有线程必须串行经过synchronize保护区域,如果过大,会降低效率。
  • synchronize锁了不同的对象:操作不同对象,不用加锁。
    并发问题一定是指多个线程访问同一个实例,否则是绝不会出现并发问题的,锁是为了解决并发问题才存在的。假设每个线程操作的是不同对象,对象的锁也不是同一个锁,实际上还是并行代码。
  • println方法是同步的
  • 尽量不要使用String类型作为锁
    字符串会从常量池查找,如果没有,则创建新对象。如果使用字符串常量作为锁,很有可能导致不同业务使用同一把锁,严重可以导致业务代码无法执行。锁一般全用Object关键字,最小化的对象。

2.底层原理

2.1前置知识

2.1.1ObjectMonitor

  1. 什么是monitor?
    操作系统在解决线程同步问题时,会经过一系列的原语操作,程序员在使用这些原语时稍不小心就会引发问题,为了更好地实现并发编程,在操作系统支持的同步原语之上,又提出了更高层次的同步原语 monitor,它本质上是jvm用c语言定义的一个数据类型。值得注意的是,操作系统本身并不支持 monitor 机制,monitor是属于编程语言级别的,也就是当你想用monitor解决线程同步问题时,你得先看下你所使用的语言是否支持monitor原语。总的来说,monitor的出现是为了解决操作系统级别关于线程同步原语的使用复杂性,对复杂操作进行封装。而java则基于monitor机制实现了它自己的线程同步机制,就是synchronized内置锁
  2. monitor的作用
    monitor的作用就是限制同一时刻,只有一个线程能进入monitor框定的临界区,达到线程互斥,保护临界区中临界资源的安全,这称为线程同步使得程序线程安全。同时作为同步工具,它也提供了管理 进程/线程 状态的机制,比如monitor能管理因为线程竞争未能第一时间进入临界区的其他线程,并提供适时唤醒的功能。
  3. monitor的组成
    • monitor对象:使用monitor机制的目的主要是为了互斥进入临界区,为了做到能够阻塞无法进入临界区的 进程/线程,还需要一个monitor对象来协助。monitor对象是monitor机制的核心,这个monitor对象内部会有相应的数据结构,例如列表,来保存被阻塞的线程,它本质上是jvm用c语言定义的一个数据类型。同时由于monitor机制是基于mutex这种基本原语的,所以monitor对象还必须维护一个基于mutex的锁,monitor的线程互斥就是通过mutex互斥锁实现的。
    • 临界区:多个线程对共享资源读写操作的那段代码,其实就是synchronized包裹起来的那段代码。
    • 条件变量:条件变量的使用与下面 wait() 和 signal() 方法的使用密切相关,它是为了在适当的时候阻塞或者唤醒一个进程/线程。在线程获取锁进入临界区之后,如果发现条件变量不满足,monitor使用 wait() 使线程阻塞,条件变量满足后使用 signal() 唤醒被阻塞线程。
    • 定义在monitor对象上的wait() signal() signalAll()操作。

image
当使用对象锁时,
WaitSet等待池:假设线程 A 调用了某个对象的 wait() 方法,线程 A 就会释放该对象的锁,同时线程 A 就进入到了该对象的等待池中,进入到等待池中的线程不会去竞争该对象的锁。
EntryList锁池:保存因没争抢到锁而进入堵塞的线程。假设线程 A 已经拥有了某个对象(不是类)的锁,而其它线程 B、C 想要调用这个对象的某个 synchronized 方法(或者块),由于 B、C 线程在进入对象的 synchronized 方法(或者块)之前必须获得该对象锁的拥有权,而恰巧该对象的锁目前正在被线程 A 所占用,此时 B、C 线程就会被阻塞,进入一个地方去等待锁的释放,这个地方便是该对象的锁池。
notify 和 notifyAll 的区别

  • notify 只会随机选取一个处于等待池中的线程进入锁池去竞争获取锁的机会
  • notifyAll 会让所有处于等待池中的线程全部进入锁池去竞争获取锁的机会

Owner:指向持有该对象锁的线程的指针,指向线程count+1,若线程调用wait()方法,该线程会进入等待池等待唤醒,owner指向null,count-1。

2.1.2Java对象头

java对象保存在内存中时,由以下三部分组成:

  1. 对象头
  2. 实例数据
  3. 对齐填充字节

而java的对象头由以下三个部分组成

  1. Mark Word(重点)
    录了对象和锁有关的信息,当这个对象被synchronized关键字当成同步锁时,围绕这个锁的一系列操作都和Mark Word有关。
    Mark Word在32位JVM中的长度是32bit,在64位JVM中长度是64bit。
    Mark Word在不同的锁状态下存储的内容不同,在32位JVM中是这么存的:
    image
    epoch: 保存偏向时间戳。
  2. 指向类的指针
    该指针在32位JVM中的长度是32bit,在64位JVM中长度是64bit。
    Java对象的类数据保存在方法区。
  3. 只有数组对象保存了这部分数据。
    该数据在32位和64位JVM中长度都是32bit。

2.2互斥锁Synchronized

【复习1】
Synchronized锁分为了三个等级
无锁 → 偏向锁 → 轻量级锁 → 重量级锁
锁只能膨胀不能降级。

  1. 偏向锁
    很多时候是同一个线程获取锁,然后每次都是释放锁、获取锁会导致很大的消耗,因此在一个线程获取锁之后,锁升级为偏向锁,此时Java对象头中的Mark Word的结构就会变成偏向锁结构。当该线程再次申请锁时,如果当前线程ID和Mark Word的ThreadID相同,则无需再做任何同步操作,省去大量大量锁申请有关的操作。
  2. 轻量级锁
    当偏向锁运行在一个线程进入同步块的情况下,当第二个线程加入锁的争抢,偏向锁就会膨胀为轻量级锁。线程交替执行,并没有出现争抢锁的情况。
  3. 重量级锁
    如果同一时间访问同一个锁,这时就不是交替执行,而是争抢锁,这时轻量级就会膨胀为重量级锁。所有参与争抢没有抢到锁的线程,就会进入堵塞状态,直到抢到锁的线程执行完毕。

【复习2】

  1. 互斥锁
    从等待到解锁的过程,线程会从sleep变为running状态,过程中有线程上下文的切换,抢占CPU等开销
  2. 自旋锁
    从等待到解锁的过程,线程一直处于running状态,没有上下文的切换,也就不会有重新唤醒线程的代价,自旋锁对象:SimpleSpinningLock

2.3加锁的膨胀与MarkWord的关系

  1. 当A线程执行synchronized临界区代码,如果锁对象是无锁状态(无锁状态的锁标志位为 "01",轻量级锁的锁标志位为 "00"),该线程会在自己的栈帧中创建一个锁记录Lock Record对象(含Displace Mark Word【存储锁对象的MarkWord】和Owner指针【用来记录锁对象的地址】)。每个线程的栈帧中都有一个锁记录结构。
  2. 拷贝同步对像的Mark Word,并将其赋值给锁记录对象的Displaced Mard Word
    image
  3. 如果上面的更新操作成功了,那么这个线程就拥有了该同步对象的锁,并且同步对象Mark Word的锁标志位会设置为 “00”,即表示此对象处于轻量级锁定状态。
  4. 如果上面的更新操作失败了,那么虚拟机首先会检查同步对象的Mark Word是否指向当前线程的栈帧,如果是就说明当前线程已经拥有了这个对象的锁,那就可以直接进入同步块继续执行(这种情况被称作锁重入,看下面下面代码)。否则说明多个线程竞争锁,轻量级锁就要膨胀为重量级锁,锁标志的状态值变为“10”,Mark Word中存储的就是指向重量级锁(互斥量)的指针,后面等待锁的线程也要进入阻塞状态。
  5. 当一个线程A以轻量级锁的方式获得一个锁对象后,另一个线程B也想获取该锁对象,线程B开始会以轻量级锁的方式尝试获取,尝试获取失败后发现有其他线程持有该锁对象,就会用重量级锁的方式获取该锁对象,即轻量级锁膨胀为重量级锁。这时候线程B会进入Monitor对象的Entry List中,并且锁对象的Mark Word的值会复制给Monitor对象的Owner指针,即Monitor对象的Owner指针会指向当前持有锁对象的线程A中的锁记录对象,然后把锁对象的Mark Word修改为Monitor对象的地址,并把锁状态设置为 "10"。当线程A执行完需要重置锁对象的Mark Word时,发现锁状态为 "10"从而更新失败,此时线程A就会进入重量级锁流程,根据锁对象的Mark Word找到Monitor对象,设置Monitor对象中的Owner指针为null,然后唤醒Entry List中的其他线程。至此线程A会释放锁,并且Entry List中的线程B会获得锁并执行,直到执行结束释放锁。当线程B释放锁后,其他线程又可以用轻量级锁的方式获取该锁对象。
    image

加锁与解锁的内存语义

  1. 当线程释放锁时,java内存模型会把该线程对应的本地内存中的贡献变量刷新到主内存中;
  2. 而当线程获取锁时,Java内存模型会把该线程对应的本地内存置为无效,从而使得被监视器保护的临界区代码必须从主内存中读取共享变量。

2.4

2.4.1同步代码块

synchronized 同步语句块的实现使用的是 monitorenter 和 monitorexit 指令,其中monitorenter 指令指向同步代码块的开始位置,monitorexit 指令则指明同步代码块的结束位置。 当执行 monitorenter 指令时,线程试图获取锁也就是获取 monitor(monitor对象存在于每个Java对象的对象头中,synchronized 锁便是通过这种方式获取锁的,也是为什么Java中任意对象可以作为锁的原因 ) 的持有权.当计数器为0则可以成功获取,获取后将锁计数器设为1也就是加1。相应的在执行monitorexit 指令后,将锁计数器设为0,表明锁被释放。如果获取对象锁失败,那当前线程就要阻塞等待,直到锁被另外一个线程释放为止。

2.4.2同步方法

image
写法一修饰的是一个方法,写法二修饰的是一个代码块,但写法一与写法二是等价的,都是锁定了整个方法时的内容。

在继承方面:
ynchronized并不属于方法定义的一部分,因此,synchronized关键字不能被继承。如果在父类中的某个方法使用了synchronized关键字,而在子类中覆盖了这个方法,在子类中的这个方法默认情况下并不是同步的,而必须显式地在子类的这个方法中加上synchronized关键字才可以。当然,还可以在子类方法中调用父类中相应的方法,这样虽然子类中的方法不是同步的,但子类调用了父类的同步方法,因此,子类的方法也就相当于同步了。这两种方式的例子代码如下:
image

  1. 在定义接口方法时不能使用synchronized关键字。
  2. 构造方法不能使用synchronized关键字,但可以使用synchronized代码块来进行同步。

3Synchronized与Reentrantlock的区别

3.1原理区别

  • 锁的实现原理基本是未来达到一个目的,就是让所有线程都能识别到某种标记
  • Synchronized通过在对象头中设置标记实现了这一目的,是一种JMM原生的锁实现方式
  • ReentrantLock以及所有的基于Lock接口的实现类,都是通过用一个volitile修饰的int型变量,并保证每个线程都能拥有对该it的可见性和原子修改,其本质是基于所谓的AQS框架。
    ReentrantLock 类实现了Lock ,它拥有与synchronized 相同的并发性和内存语义,但是添加了类似锁投票、定时锁等候和可中断锁等候的一些特性。此外,它还提供了在激烈争用情况下更佳的性能。(换句话说,当许多线程都想访问共享资源时,JVM可以花更少的时候来调度线程,把更多时间用在执行线程上。)

3.2区别

  1. 本质区别:Synchronized是关键字,Reentrantlock是类
  2. 既然ReentrantLock是类,那么它就提供了比synchronized更多更灵活的特性,可以被继承、可以有方法、可以有各种各样的类变量,ReentrantLock比synchronized的扩展性体现在几点上:
    1. ReentrantLock可以对获取锁的等待时间进行设置,这样就避免了死锁
    2. ReentrantLock可以获取各种锁的信息
    3. ReentrantLock可以灵活地实现多路通知
  3. 与synchronized 会被JVM 自动解锁机制不同,ReentrantLock 加锁后需要手动进行解锁。为了避免程序出现异常而无法正常解锁的情况,使用ReentrantLock 必须在finally 控制块中进行解锁操作。

4.Reentrantlock使用

  1. 创建锁:ReentrantLock lock = new ReentrantLock();
  2. 获取锁:lock.lock();
  3. 释放锁:lock.unlock();

上面代码需要注意lock.unlock()一定要放在finally中,否则,若程序出现了异常,锁没有释放,那么其他线程就再也没有机会获取这个锁了。

4.1必须在finally中解锁的原因

  1. ReentrankLock中必须使用实例方法lockInterruptibly() 获取锁时,在线程调用interrupt()方法之后,才会引发InterruptedException 异常
  2. 线程调用interrupt()之后,线程的中断标志会被置为true
  3. 触发InterruptedException异常之后,线程的中断标志会被清空,即置为false
  4. 所以当线程调用interrupt()引发InterruptedException异常,中断标志的变化是:false->true->->false
  5. 实例方法tryLock()会尝试获取锁,会立即返回,返回值表示是否获取成功
  6. 实例方法tryLock(long timeout, TimeUnit unit)会在指定的时间内尝试获取锁,指定的时间内是否能够获取锁,都会返回,返回值表示是否获取锁成功,该方法会响应线程的中断

5除了原子性,synchronized可见性,有序性,可重入性怎么实现?

5.1原子性

对number++;增加同步代码块后,保证同一时间只有一个线程操作number++;。就不会出现安全问题。
synchronized保证原子性的原理,synchronized保证只有一个线程拿到锁,能够进入同步代码块

5.2可见性

问题:当一个线程对共享变量进行了修改,另外的线程并没有立即看到修改后的最新值。
synchronized解决:

public class Test01 {

  private static Boolean flag = true;

  public static void main (String[] args) {
    new Thread(() -> {
      while (flag) {
        //System.out.println(flag);  用这个也可以实现,因为底层用了synchronized
        synchronized (Test01.class){  //lock: flag要从主内存中拿最新的值

        }
      }
    }).start();

    try {
      TimeUnit.SECONDS.sleep(1);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }

    new Thread(() -> {
      flag = false;
      System.out.println("flag更新为false");
    }).start();
  }
}

5.3有序性

问题:线程2先执行actor2发生了重排序,先执行 ready = true,还没执行num = 2;线程1执行,num 这时候事默认值0,结果为0。
synchronized解决:线程2先执行actor2发生了重排序,先执行 ready = true,还没执行num = 2;线程1执行,这时候线程1拿不到锁,所以只能等线程2执行完了num=2,释放了锁,线程1才能执行,因为num不会再出现=0的情况了。
原理:synchronized保证有序性的原理,我们加synchronized后,依然会发生重排序,只不过,我们有同步代码块,可以保证只有一个线程执行同步代码中的代码。保证有序性。

public class Test{
	private Object obj = new Object();
	int num = 0;
	boolean ready = false

	//线程1执行代码
	@Actor
	public void actor1(I_Result r){
		synchronized(obj){
			r.r1 = num + num;
		}else{
			r.r1 = 1;
		}
	}

	//线程2执行代码
	@Actor
	public void actor2(I_Result r){
	synchronized(obj){
		num = 2;
		ready = true;
		}
	}
}

5.4 可重入性

原理:synchronized是可重入锁,内部锁对象中会有一个计数器记录线程获取几次锁啦,在执行完同步代码块时,计数器的数量会-1,直到计数器的数量为0,就释放这个锁。

public class Test02 {

  public static void main (String[] args) {
    Runnable sellTicket = new Runnable() {
      @Override
      public void run () {
        synchronized (Test02.class) {
          System.out.println("我是run");
          test01();
        }
      }

      public void test01 () {
        synchronized (Test02.class) {
          System.out.println("我是test01");
        }
      }
    };
    new Thread(sellTicket).start();
    new Thread(sellTicket).start();
  }
}

5.5不可中断性

不可中断是指,当一个线程获得锁后,另一个线程一直处于阻塞或等待状态,前一个线程不释放锁,后一个线程会一直阻塞或等待,不可被中断。

  • synchronized属于不可被中断
  • Lock的lock方法是不可中断的
  • Lock的tryLock方法是可中断的

6 volatile(CPU缓存模型、Java内存模型)

6.1 Java内存模型 - JMM模型(Java Memory Model)

6.1.1同步的规定

  1. 线程解锁前,必须把共享变量的值刷新回主内存;
  2. 线程加锁前,必须读取主内存的最新值到自己的工作内存;
  3. 加锁解锁是同一把锁;

6.1.2简单介绍

Java内存模型规定所有变量都存储在主内存,主内存是共享内存区域,所有线程都可以访问。
但线程对变量的操作(读取、赋值等)必须在工作内存(每个线程的私有数据区域)中进行。因此首先要将变量从主内存拷贝到自己的工作内存,然后对变量进行操作,操作完成后再将变量写会主内存中。
【volatile的两大作用】

  1. 实现线程之间的可见性
  2. 防止编译时期的指令重排

6.1.3volatile不保证原子性的解决方法

  1. 使用synchronized
    保证每一时刻只能有一个线程能执行,保证了原子性但降低了效率。

  2. 使用JUC包下的AtomicInteger
    AtomicInteger类是系统底层保护的int类型,通过对int类型的数据进行封装,提供执行方法的控制进行值的原子操作。AtomicInteger它不能当作Integer来使用
    【原子方式执行加法和减法操作的方法】

    • addAndGet()- 以原子方式将给定值添加到当前值,并在添加后返回新值。
    • getAndAdd() - 以原子方式将给定值添加到当前值并返回旧值。
    • incrementAndGet()- 以原子方式将当前值递增1并在递增后返回新值。它相当于i ++操作。
    • getAndIncrement() - 以原子方式递增当前值并返回旧值。它相当于++ i操作。
    • decrementAndGet()- 原子地将当前值减1并在减量后返回新值。它等同于i-操作。
    • getAndDecrement() - 以原子方式递减当前值并返回旧值。它相当于-i操作。

6.1.4传统计算机硬件内存架构

首先解释【什么是重排序?】
假设我们写了一个JAVA程序,我们会主观地期望我们写的语句顺序就是实际的运行顺序。但是,实际上,编译器、JVM或者CPU处理器都有可能出于优化的目的,对实际指令执行顺序进行重新调整排序,以此达到提高处理速度【重排序的好处】。
【重排序的三种情况】

  1. 编译器(JVM、JIT 编译器(JAVA的即时编译器,会做指令重排序)等)出于优化的目的。
    例如有多个对数据a的操作,分布在单线程的多个位置,考虑多次读取a的时间开销,此时编译过程中,在不影响单线程内语义的情况下,会进行一定的重排序。
  2. CPU重排序
    CPU和编译器优化类似,通过乱序执行的基础,提高整体的执行效率。因此即使编译器不发生重排序,CPU也有可能进行重排序。
  3. 内存的“重排序”
    内存中,并不存在真正的重排序,只是有着重排序一样的效果。由于处理器使用缓存和读写缓冲区,这使得加载和存储操作看上去可能是在乱序执行。

image
【传统计算机硬件内存架构】
image
注意:

  • 缓冲主要是CPU向内存写,CPU写速度快,硬盘慢,中间加上缓冲;
    比如你每秒要写100次硬盘,对系统冲击很大,大量时间在忙着处理开始写和结束写这两件事,而开始读写与终止读写所需要的时间很长。所以用buffer暂存起来,变成每10秒写一次硬盘,对系统的冲击就很小了,写入效率也高了。
  • 缓存主要是CPU从内存读,加快cpu的读速度。
    比如你一个很复杂的计算做完了,下次可能还要用到这个结果,就把结果放手边一个好拿的地方存着,不用就算了。
  • 对于CPU来说,它的目标存储器是物理内存,使用高速缓存做物理内存的缓存
  • 对于虚拟内存来说,它的目标存储器是磁盘空间,使用物理内存做磁盘的缓存

6.1.5JMM模型

image
【规范】

  • 所有的变量都存储在主内存(Main Memory)中。
  • 每个线程都有一个私有的本地内存(Local Memory),本地内存中存储了该线程以读/写共享变量的拷贝副本。
  • 线程对变量的所有操作都必须在本地内存中进行,而不能直接读写主内存。
  • 不同的线程之间无法直接访问对方本地内存中的变量。

【Java 内存模型定义了八种操作来实现】

  1. lock:锁定。作用于主内存的变量,把一个变量标识为一条线程独占状态。
  2. unlock:解锁。作用于主内存变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。
  3. read:读取。作用于主内存变量,把一个变量值从主内存传输到线程的工作内存中,以便随后的load动作使用
  4. load:载入。作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中。
  5. use:使用。作用于工作内存的变量,把工作内存中的一个变量值传递给执行引擎,每当虚拟机遇到一个需要使用变量的值的字节码指令时将会执行这个操作。
  6. assign:赋值。作用于工作内存的变量,它把一个从执行引擎接收到的值赋值给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。
  7. store:存储。作用于工作内存的变量,把工作内存中的一个变量的值传送到主内存中,以便随后的write的操作。
  8. write:写入。作用于主内存的变量,它把store操作从工作内存中一个变量的值传送到主内存的变量中。

6.1.6总结

由于CPU 和主内存间存在数量级的速率差,想到了引入了多级高速缓存的传统硬件内存架构来解决,多级高速缓存作为 CPU 和主内间的缓冲提升了整体性能。解决了速率差的问题,却又带来了缓存一致性问题。

数据同时存在于高速缓存和主内存中,如果不加以规范势必造成灾难,因此在传统机器上又抽象出了内存模型。

Java 语言在遵循内存模型的基础上推出了 JMM 规范,目的是解决由于多线程通过共享内存进行通信时,存在的本地内存数据不一致、编译器会对代码指令重排序、处理器会对代码乱序执行等带来的问题。

为了更精准控制工作内存和主内存间的交互,JMM 还定义了八种操作:lock, unlock, read, load,use,assign, store, write。

6.2CPU缓存模型

image
image
CPU缓存是位于CPU与内存之间的临时数据交换器,它的容量比内存小的多但是交换速度却比内存要快得多。
为了简化与内存之间的通信,高速缓存控制器是针对数据块,而不是字节进行操作的。高速缓存其实就是一组称之为缓存行(Cache Line)的固定大小的数据块组成的,典型的一行是64字节。
CPU缓存通常分成了三个级别:L1,L2,L3。级别越小越接近CPU,所以速度也更快,同时也代表着容量越小。
【缓存模型】
image

MESI协议(解决缓存一致性问题)

  • M:代表已修改(Modified)
  • E:代表独占(Exclusive)
  • S:代表共享(Shared)
  • I:代表已失效(Invalidated)

image

标签:同步,monitor,synchronized,对象,并发,线程,内存,sychronized,volatile
From: https://www.cnblogs.com/mlstudyjava/p/17015846.html

相关文章

  • JUC并发编程详解(通俗易懂)
    一、JUC简介在Java5.0提供了java.util.concurrent包,简称JUC,即Java并发编程工具包。JUC更好的支持高并发任务。具体的有以下三个包:java.util.concurrentjava.util.conc......
  • EMQX+阿里云飞天洛神云网络 NLB:MQTT 消息亿级并发、千万级吞吐性能达成
    随着物联网技术的发展与各行业数字化进程的推进,全球物联网设备连接规模与日俱增。一个可靠高效的物联网系统需要具备高并发、大吞吐、低时延的数据处理能力,支撑海量物联网数......
  • memcached并发CAS模式
    ​​http://hudeyong926.iteye.com/blog/1463992​​应用场景分析:​​http://hudeyong926.iteye.com/blog/1172189​​如原来MEMCACHED中的KES的内容为A,客户端C1和客户端C2......
  • 网络编程和并发编程
    `fromthreadingimportThreadimporttimen=100deftask():globalntmp=ntime.sleep(1)#进入IO,GIL锁会释放,则n为99,当没有IO则n为0n=tmp-1t_list=[]f......
  • 并发编程的场景中的三个bug源头:可见性、原子性、有序性
    1.可见性:多核系统每个cpu自带高速缓存,彼此间不交换信息case:两个线程对同一份实例变量count累加,结果可能不等于累加之和,因为线程将内存值载入各自的缓存中,之后的累加操作基......
  • 百万并发场景中倒排索引与位图计算的实践
    作者:京东物流郎元辉背景Promise时效控单系统作为时效域的控制系统,在用户下单前、下单后等多个节点均提供服务,是用户下单黄金链路上的重要节点;控单系统主要逻辑是针对用户请......
  • django 如何提升性能(高并发)
    django如何提升性能(高并发)对一个后端开发程序员来说,提升性能指标主要有两个一个是并发数,另一个是响应时间网站性能的优化一般包括web前端性能优化,应用服务器性能优化,存......
  • java虚拟机能并发的启动多少个线程
    新建一个类,导入如下的测试代码:1publicclassTestNativeOutOfMemoryError{2publicstaticvoidmain(String[]args){34for(inti=0;;i++......
  • 百万并发场景中倒排索引与位图计算的实践
    作者:京东物流郎元辉背景Promise时效控单系统作为时效域的控制系统,在用户下单前、下单后等多个节点均提供服务,是用户下单黄金链路上的重要节点;控单系统主要逻辑是针对用......
  • python并发
    并发方式线程(​​Thread​​)多线程几乎是每一个程序猿在使用每一种语言时都会首先想到用于解决并发的工具(JS程序员请回避),使用多线程可以有效的利用CPU资源(Python例外)。然而......