Java内存屏障

硬件层的内存屏障分为两种:Load BarrierStore Barrier即读屏障和写屏障.

内存屏障有两个作用:

  • 组织屏障两侧指令重排序
  • 强制把写缓冲区/高速缓存中的数据写回主内存,让缓存中相应的数据失效

对于Load Barrier来说,在指令前插入Load Barrier,可以让高速缓存中的数据失效,强制从新从主内存加载数据

对于Store Barrier来说,在指令后插入Store Barrier,能让写入缓存中的最新数据更新写入主内存,让其他线程可见

Java内存屏障

Java的内存屏障通常所谓的四种即LoadLoad,StoreStore,LoadStore,StoreLoad实际上也是Load BarrierStore Barrier两种的组合,完成一系列的屏障和数据同步功能

Read More

顺序一致性

当程序未正确同步时,就可能会存在数据竞争

Java内存模型规范对数据竞争的定义如下

  1. 在一个线程中写一个变量
  2. 在另一个线程中读同一个变量
  3. 写和读操作没有通过同步来排序

JMM对正确同步的多线程程序的内存一致性做了如下保证. 如果程序是正确同步的,程序的执行将具有顺序一致性(Sequentially Consistent)——即程 序的执行结果与该程序在顺序一致性内存模型中的执行结果相同. 这里的同步是指广义上的同步,包括对常用同步原语 (synchronized、volatile和final)的正确使用

顺序一致性内存模型

顺序一致性内存模型是一个被计算机科学家理想化了的理论参考模型. 顺序一致性内存模型有两大特性

  • 一个线程中的所有操作必须按照程序的顺序来执行
  • (不管程序是否同步)所有线程都只能看到一个单一的操作执行顺序. 在顺序一致性内存模型中,每个操作都必须原子执行且立刻对所有线程可见

Read More

指令重排

重排序是指编译器和处理器为了优化程序性能而对指令序列进行重新排序的一种手段

数据依赖

如果两个操作访问同一变量,且存在一个写操作,这两个操作就存在数据依赖

数据依赖的三种类型:

名称 代码示例
写后读 a=1;b=a;
写后写 a=1;a=2;
读后写 a=b;b=1;

存在数据依赖的操作进行指令重排,程序的执行结果可能就会被改变. 编译器和处理器在重排序时,会遵守数据依赖性,编译器和处理器不会改变存在数据依赖关系的两个操作的执行顺序

这里所说的数据依赖性仅针对单个处理器中执行的指令序列和单个线程中执行的操作, 不同处理器之间和不同线程之间的数据依赖性不被编译器和处理器考虑

as-if-serial 语义

as-if-serial语义的意思是:不管怎么重排序(编译器和处理器为了提高并行度),(单线程)程序的执行结果不能被改变

Read More

Java内存模型

并发编程中,需要处理两个关键问题:线程之间如何通信及线程之间如何同步(这里的 线程是指并发执行的活动实体).通信是指线程之间以何种机制来交换信息.线程之间的通信机制有两种:共享内存和消息传递

共享内存的并发模型里,线程之间共享程序的公共状态,通过写-读内存中的公共状态 进行隐式通信.消息传递的并发模型里,线程之间没有公共状态,线程之间必须通过发送消 息来显式进行通信

同步是指程序中用于控制不同线程间操作发生相对顺序的机制.在共享内存并发模型 里,同步是显式进行的.程序员必须显式指定某个方法或某段代码需要在线程之间互斥执行.在消息传递的并发模型里,由于消息的发送必须在消息的接收之前,因此同步是隐式进行的

Java的并发采用的是共享内存模型,Java线程之间的通信总是隐式进行,整个通信过程对 程序员完全透明

Java内存模型的抽象结构

Java中,所有实例域、静态域和数组元素都存储在堆内存中,堆内存在线程之间共享. 局部变量(Local Variables),方 法定义参数(Java语言规范称之为Formal Method Parameters)和异常处理器参数(Exception Handler Parameters)不会在线程之间共享,它们不会有内存可见性问题,也不受内存模型的影响

Java线程之间的通信由Java内存模型(JMM)控制,JMM决定一个线程对共享 变量的写入何时对另一个线程可见. JMM定义了线程和主内存之间的抽 象关系:线程之间的共享变量存储在主内存(Main Memory)中,每个线程都有一个私有的本地 内存(Local Memory),本地内存中存储了该线程以读/写共享变量的副本。本地内存是JMM的 一个抽象概念,并不真实存在

如果线程A与线程B之间要通信的话,必须要经历下面2个步骤

  • 线程A把本地内存A中更新过的共享变量刷新到主内存中去
  • 线程B到主内存中去读取线程A之前已更新过的共享变量

JMM通过控制主内存与每个线程的本地内存之间的交互,来为Java程序员提供 内存可见性保证

指令重排

执行程序时,为了提高性能,编译器和处理器常常会对指令做重排序.重排序分3种类型

Read More

原子操作

原子操作(atomic operation)意 为“不可被中断的一个或一系列操作

处理器如何实现原子操作

总线锁定

使用处理器提供的一个 LOCK#信号,当一个处理器在总线上输出此信号时,其他处理器的请求将被阻塞住,那么该 处理器可以独占共享内存

总线锁定把CPU和内存之间的通信锁住了,这使得锁定期间,其他处 理器不能操作其他内存地址的数据,所以总线锁定的开销比较大

缓存锁定

同一时刻,我们通常仅需要保证对某个内存地址的操作是原子性即可,由于总线锁定开销大,某些场合下,处理器会使用缓存锁定替代总线锁定

缓存锁定是指内存区域如果被缓存在处理器的缓存行中,并且在Lock操作期间被锁定,那么当它执行锁操作回写到内存时,处理器不在总线上声明LOCK#信号,而是修改内部的内存地址,并允许它的缓存一致性机制来保证操作的原子性

Read More

synchronized

Java中的每一个对象都可以作为锁。具体表现 为以下3种形式

  • 对于普通同步方法,锁是当前实例对象
  • 对于静态同步方法,锁是当前类的Class对象
  • 对于同步方法块,锁是Synchonized括号里配置的对象

当一个线程试图访问同步代码块时,它首先必须得到锁,退出或抛出异常时必须释放锁

JVM基于进入和退出Monitor对象来实现方法同步和代码块同步,代码块同步是使用monitorenter 和monitorexit指令实现的

monitorenter指令是在编译后插入到同步代码块的开始位置,而monitorexit是插入到方法结 束处和异常处,JVM要保证每个monitorenter必须有对应的monitorexit与之配对.任何对象都有 一个monitor与之关联,当且一个monitor被持有后,它将处于锁定状态.线程执行到monitorenter 指令时,将会尝试获取对象所对应的monitor的所有权,即尝试获得对象的锁.

Java对象头

synchronized用的锁是存在Java对象头里的,Java对象头里的Mark Word里默认存储对象的HashCode、分代年龄和锁标记位.

锁的升级与对比

锁一共有4种状态,级别从低到高依次是:无锁状态、偏向锁状态、轻量级锁状 态和重量级锁状态,这几个状态会随着竞争情况逐渐升级.

锁可以升级但不能降级,意味着偏 向锁升级成轻量级锁后不能降级成偏向锁.这种锁升级却不能降级的策略,目的是为了提高 获得锁和释放锁的效率

Read More

volatile

volatile是轻量级的synchronized,它在多处理器开发中保证了共享变量的’可见性’.可见性的意思是当一个线程修改一个共享变量时,另一个线程能读取到这个修改的值.

volatile变量修饰符使用恰当 的话,它比synchronized的使用和执行成本更低,因为它不会引起线程上下文的切换和调度.

把对volatile变量的单个读/写,看成是使用同一个锁对这些单个读/写操作做了同步. 锁的happens-before规则保证释放锁和获取锁的两个线程之间的内存可见性,这意味着对 一个volatile变量的读,总是能看到(任意线程)对这个volatile变量最后的写入

volatile变量具有以下特性

  • 可见性: 对一个volatile变量的读,总是能看到(任意线程)对这个volatile变量最后的写入
  • 原子性:对任意单个volatile变量的读/写具有原子性,但类似于volatile++这种复合操作不具有原子性

volatile的定义与实现原理

Java编程语言允许线程访问共享变量,为了 确保共享变量能被准确和一致地更新,线程应该确保通过排他锁单独获得这个变量。Java语言 提供了volatile,在某些情况下比锁要更加方便。如果一个字段被声明成volatile,Java线程内存 模型确保所有线程看到这个变量的值是一致的 – Java官方定义

有volatile变量修饰的共享变量进行写操作时,处理器会额外进行如下操作

  1. 将当前处理器缓存行的数据写回到系统内存
  2. 写回内存的操作会使在其他CPU里缓存了该内存地址的数据无效

volatile写的内存语义: 写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值刷新到主内存. 线程A写一个volatile变量,实质上是线程A向接下来将要读这个volatile变量的某个线程发出了(其对共享变量所做修改的)消息

Read More

便利店

行业现状

  • 门店数量达到 13.2 万家
  • 日均销售额为 5,297元
  • 二线城市典型便利店净利润率水平约 5%
  • 本土便利店和夫妻店对即食商品的涉足较少
  • 便利店企业的平均平效为69元/平方米/天,企业运营效率远低于国际领先企业水平
  • 建立会员体系的企业比例持续增加

典型便利店品牌在二线城市的成本结构测算:

成本项 GMV占比
销货成本 65
租金 7
人工 5.8
水电 3.5
其它 4
仓储 0.9
后台部门 2.2
物流成本 3.2
各种税 4.5
净利润 4.9

一线城市中,北京单店覆盖人口数最多,便利店密度最低

二线城市中,重庆、昆明、南宁、青岛等地的单店覆盖人口数远高于同等经济水平城市,发展潜力大

城市名 城市等级 便利店数量 单店覆盖人口数
广州 一线城市 5,317 2,803
深圳 一线城市 7,524 1,731
上海 一线城市 6,430 3,769
北京 一线城市 2,250 8,889
东莞 二线城市 6,843 1,242
长沙 二线城市 5,000 1,409
太原 二线城市 2,650 1,586
中山 二线城市 1,650 1,976
惠州 二线城市 2,000 2,378
成都 二线城市 5,000 3,266
杭州 二线城市 2,453 3,547
天津 二线城市 2,625 5,941
西安 二线城市 1,520 6,581
青岛 二线城市 1,150 7,578
南宁 二线城市 800 8,327
昆明 二线城市 600 10,720
重庆 二线城市 1,845 16,812
乌鲁木齐 三线城市 1,000 3,110
呼和浩特 三线城市 656 4,370
银川 三线城市 370 5,387
莆田 三线城市 500 5,700
宜昌 三线城市 219 18,790
西宁 四线城市 220 10,040
临汾 四线城市 266 16,918

Read More

通过Logstash解析Logs

原文链接

上一节中,通过创建一个基本的Logstash pipline测试你的Logstash安装成功.在真实场景中,一个Logstash往往更复杂些,典型的Logstash pipline往往有一个或多个input,filter,和output插件.  

在本节中,你将创建一个使用Filebeat来采集Apache web logs作为输入,从这些日志中创建具体的,重命名过的属性,并将这些解析过的数据写入到Elasticsearch集群中.你将在一个配置文件中定义这个pipline,而不是在一个命令行中.

本例中,你可以从这里下载数据集,解压这个文件.

配置Filebeat发送日志到Logstash

在你创建Logstash pipline前,你需要配置Filebeat发送日志到Logstash.

Filebeat客户端是一个轻量的开源工具,它可以完成从你的服务器文件收集日志并将它们转发到你的Logstash实例的过程.专为可靠性和低延迟设计,Filebeat只在主机上占用少量资源,Beats input 插件将Logstash 实例资源需求最小化.

Read More