用户记录平时读源码、读blog或者看到一些东西之后,随便记录在此,以便复习。 是比较散乱不成体系的。
zyllt / project Goto Github PK
View Code? Open in Web Editor NEW随便写写
随便写写
基于spring的4.2.4版本
1、启用AOP的配置方式
<aop:aspectj-autoproxy proxy-target-class="false"/>
这个AOP标签在解析的时候做了什么呢?
@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
AopNamespaceUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(parserContext, element);
extendBeanDefinition(element, parserContext);
return null;
}
Spring中的Beanfactory就是简单工厂,主要是可以根据一个传入的参数来确定创建指定实例返回。
好像是6种
在这其中主要PROPAGATION_REQUIRES_NEW和PROPAGATION_NESTED的区别:
public class SingletonTest{
private static SingletonTest INSTANCE = new SingletonTest();
private SingletonTest(){
}
public static SingletonTest getInstance(){
return INSTANCE;
}
}
饿汉式的创建是线程安全的,当时没有达到lazy-loading的效果
先看第一种懒汉式的加载:
public class SingletonTest{
private volatile static SingletonTest INSTANCE;
private SingletonTest(){
}
public static SingletonTest getInstance(){
if(INSTANCE == null){
INSTANCE = new SingletonTest();
}
return INSTANCE;
}
}
很明显这个是线程不安全的,在多线程环境下很容易被创建多个实例。最简单的改造方式就是直接在方法上加Synchronized来达到线程安全的目的。当时这样会带来很大的性能损耗,因为INSTANCE在第一次调用的时候就已经被创建了,后续每个线程来获取还必须等待上一个线程释放锁,相当于读也加了锁。根据这种方式改造一下
public class SingletonTest{
private volatile static SingletonTest INSTANCE;
private SingletonTest(){
}
public static SingletonTest getInstance(){
if(INSTANCE == null){
synchronized(SingletonTest.class){
if(INSTANCE == null){
INSTANCE = new SingletonTest();
}
}
}
return INSTANCE;
}
}
这种方式其实就是双重校验法,而且在第一次创建实例之后的每次访问都是无锁的。注意需要加volatile
关键词,而且是jdk1.5之后才能是线程安全的
先看代码
public class SingletonTest{
private static class SingletonHolder{
public static SingletonTest INSTANCE = new SingletonTest();
}
private SingletonTest(){
}
public static SingletonTest getInstance(){
return SingletonHolder.INSTANCE;
}
}
public enum SingletonTest{
INSTANCE;
private String name;
public void setName(String name){
this.name = name;
}
public String getName(){
return name;
}
}
使用枚举除了线程安全和防止反射强行调用构造器之外,还提供了自动序列化机制,防止反序列化的时候创建新的对象。因此,Effective Java推荐尽可能地使用枚举来实现单例。
Spring在创建完BeanFactory之后就会进行加载BeanDefinitions,如果是用的XML形式来启动的Spring,首先要做的就是解析XML文件。如果是注解式配置,在初始化AnnotationConfigApplication的时候就已经根本配置的scan path来加载和注册BeanDefinitions了
ApplicationContext context = new ClassPathXmlApplicationContext("application-context.xml")
此处主要用到的核心类是XmlBeanDefinitionReader
根据传入的XML文件路径,解析成Resource数组:
//定位获取
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
在获取到XML文件的resource时候就可以进行读取工作,此项工作是交给DefaultDocumentLoader来完
成,默认使用DocumentBuilder来解析xml文件。
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
getValidationModeForResource(resource), isNamespaceAware());
}
1.2中返回的Document,就可以交给DefaultBeanDefinitionDocumentReader来循环处理Element了。
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
/**
* 实例化 {@link DefaultBeanDefinitionDocumentReader}
*/
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
/**
* 获取之前已经注册的beanDefinition的数量
* {@link DefaultListableBeanFactory#getBeanDefinitionCount()}
*/
int countBefore = getRegistry().getBeanDefinitionCount();
/**
* 委托给 {@link DefaultBeanDefinitionDocumentReader}进行实际的解析和注册
*/
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
return getRegistry().getBeanDefinitionCount() - countBefore;
}
@Override
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
logger.debug("Loading bean definitions");
Element root = doc.getDocumentElement();
/**
* 真正的解析注册逻辑
*/
doRegisterBeanDefinitions(root);
}
protected void doRegisterBeanDefinitions(Element root) {
// Any nested <beans> elements will cause recursion in this method. In
// order to propagate and preserve <beans> default-* attributes correctly,
// keep track of the current (parent) delegate, which may be null. Create
// the new (child) delegate with a reference to the parent for fallback purposes,
// then ultimately reset this.delegate back to its original (parent) reference.
// this behavior emulates a stack of delegates without actually necessitating one.
BeanDefinitionParserDelegate parent = this.delegate;
/**
* 创建一个 {@link BeanDefinitionParserDelegate},把解析任务委托给它
* 如果有多个beans标签会导致此解析递归调用,为防止出现错误,需要创建新的并且传递parent
*/
this.delegate = createDelegate(getReaderContext(), root, parent);
/**
* 判断是否默认的spring nameSpace {@link BeanDefinitionParserDelegate#BEANS_NAMESPACE_URI}
* 如果uri为空也返回true
*/
if (this.delegate.isDefaultNamespace(root)) {
String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
if (StringUtils.hasText(profileSpec)) {
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
return;
}
}
}
/**
* 准备方法,可以被重写
*/
preProcessXml(root);
/**
* 解析 element、注册bean definitions
*/
parseBeanDefinitions(root, this.delegate);
postProcessXml(root);
this.delegate = parent;
}
1、直接插入排序
稳定的
选择一个数字然后
2、冒泡排序
稳定的
轮询把最大的沉下去
3、快速排序
不稳定的,最优时是最快的,和堆排序一样
选定一个基准书,进行递归
O(nlog2n)
4、堆排序
5、选择排序
6、shell排序
7、归并排序
稳定的
简单说就是根据BeanDifinition,根据反射生成一个对象实例。在调用实例前后会调用InstantiationAwareBeanPostProcessor的两个方法。
主要有三种模式
此处如果有其他bean注入进来,会先去初始化其他的bean,有可能造成循环依赖不建议使用构造参数注入
环境:
macos 10.14.2
xcode version9.2
freetype 2.9.1
Mercurial Distributed SCM (version 4.5)
此处以mysql的为准,oracle其他数据库不做讨论。
mysql 的innodb
基于spring boot 2.0.2
多任务和高并发是衡量一台计算机处理器的能力重要指标之一。一般衡量一个服务器性能的高低好坏,使用每秒事务处理数(Transactions Per Second,TPS)这个指标比较能说明问题,它代表着一秒内服务器平均能响应的请求数,而TPS值与程序的并发能力有着非常密切的关系。物理机的并发问题与虚拟机中的情况有很多相似之处,物理机对并发的处理方案对于虚拟机的实现也有相当大的参考意义。
由于计算机的存储设备与处理器的运算能力之间有几个数量级的差距,所以现代计算机系统都不得不加入一层读写速度尽可能接近处理器运算速度的高速缓存(cache)来作为内存与处理器之间的缓冲:将运算需要使用到的数据复制到缓存中,让运算能快速进行,当运算结束后再从缓存同步回内存之中,这样处理器就无需等待缓慢的内存读写了。
基于高速缓存的存储交互很好地解决了处理器与内存的速度矛盾,但是引入了一个新的问题:缓存一致性(Cache Coherence)。在多处理器系统中,每个处理器都有自己的高速缓存,而他们又共享同一主存,如下图所示:多个处理器运算任务都涉及同一块主存,需要一种协议可以保障数据的一致性,这类协议有MSI、MESI、MOSI及Dragon Protocol等。
除此之外,为了使得处理器内部的运算单元能尽可能被充分利用,处理器可能会对输入代码进行乱序执行(Out-Of-Order Execution)优化,处理器会在计算之后将对乱序执行的代码进行结果重组,保证结果准确性。与处理器的乱序执行优化类似,Java虚拟机的即时编译器中也有类似的指令重排序(Instruction Recorder)优化。
内存模型可以理解为在特定的操作协议下,对特定的内存或者高速缓存进行读写访问的过程抽象,不同架构下的物理机拥有不一样的内存模型,Java虚拟机也有自己的内存模型,即Java内存模型(Java Memory Model, JMM)。在C/C++语言中直接使用物理硬件和操作系统内存模型,导致不同平台下并发访问出错。而JMM的出现,能够屏蔽掉各种硬件和操作系统的内存访问差异,实现平台一致性,是的Java程序能够“一次编写,到处运行”。
Java内存模型的主要目标是定义程序中各个变量的访问规则,即在虚拟机中将变量存储到内存和从内存中取出变量这样底层细节。此处的变量与Java编程时所说的变量不一样,指包括了实例字段、静态字段和构成数组对象的元素,但是不包括局部变量与方法参数,后者是线程私有的,不会被共享。
Java内存模型中规定了所有的变量都存储在主内存中,每条线程还有自己的工作内存(可以与前面讲的处理器的高速缓存类比),线程的工作内存中保存了该线程使用到的变量到主内存副本拷贝,线程对变量的所有操作(读取、赋值)都必须在工作内存中进行,而不能直接读写主内存中的变量。不同线程之间无法直接访问对方工作内存中的变量,线程间变量值的传递均需要在主内存来完成,线程、主内存和工作内存的交互关系如下图所示,和上图很类似。
注意:这里的主内存、工作内存与Java内存区域的Java堆、栈、方法区不是同一层次内存划分,这两者基本上没有关系。
由上面的交互关系可知,关于主内存与工作内存之间的具体交互协议,即一个变量如何从主内存拷贝到工作内存、如何从工作内存同步到主内存之间的实现细节,Java内存模型定义了以下八种操作来完成:
如果要把一个变量从主内存中复制到工作内存,就需要按顺寻地执行read和load操作,如果把变量从工作内存中同步回主内存中,就要按顺序地执行store和write操作。Java内存模型只要求上述两个操作必须按顺序执行,而没有保证必须是连续执行。也就是read和load之间,store和write之间是可以插入其他指令的,如对主内存中的变量a、b进行访问时,可能的顺序是read a,read b,load b, load a。Java内存模型还规定了在执行上述八种基本操作时,必须满足如下规则:
这8种内存访问操作很繁琐,后文会使用一个等效判断原则,即先行发生(happens-before)原则来确定一个内存访问在并发环境下是否安全。
关键字volatile是JVM中最轻量的同步机制。volatile变量具有2种特性:
由于volatile只能保证变量的可见性和屏蔽指令重排序,只有满足下面2条规则时,才能使用volatile来保证并发安全,否则就需要加锁(使用synchronized、lock或者java.util.concurrent中的Atomic原子类)来保证并发中的原子性。
因为需要在本地代码中插入许多内存屏蔽指令在屏蔽特定条件下的重排序,volatile变量的写操作与读操作相比慢一些,但是其性能开销比锁低很多。
JMM要求lock、unlock、read、load、assign、use、store、write这8个操作都必须具有原子性,但对于64为的数据类型(long和double,具有非原子协定:允许虚拟机将没有被volatile修饰的64位数据的读写操作划分为2次32位操作进行。(与此类似的是,在栈帧结构的局部变量表中,long和double类型的局部变量可以使用2个能存储32位变量的变量槽(Variable Slot)来存储的,关于这一部分的详细分析,详见详见周志明著《深入理解Java虚拟机》8.2.1节)
如果多个线程共享一个没有声明为volatile的long或double变量,并且同时读取和修改,某些线程可能会读取到一个既非原值,也不是其他线程修改值的代表了“半个变量”的数值。不过这种情况十分罕见。因为非原子协议换句话说,同样允许long和double的读写操作实现为原子操作,并且目前绝大多数的虚拟机都是这样做的。
JMM保证的原子性变量操作包括read、load、assign、use、store、write,而long、double非原子协定导致的非原子性操作基本可以忽略。如果需要对更大范围的代码实行原子性操作,则需要JMM提供的lock、unlock、synchronized等来保证。
前面分析volatile语义时已经提到,可见性是指当一个线程修改了共享变量的值,其他线程能够立即得知这个修改。JMM在变量修改后将新值同步回主内存,依赖主内存作为媒介,在变量被线程读取前从内存刷新变量新值,保证变量的可见性。普通变量和volatile变量都是如此,只不过volatile的特殊规则保证了这种可见性是立即得知的,而普通变量并不具备这种严格的可见性。除了volatile外,synchronized和final也能保证可见性。
JMM的有序性表现为:如果在本线程内观察,所有的操作都是有序的;如果在一个线程中观察另一个线程,所有的操作都是无序的。前半句指“线程内表现为串行的语义”(as-if-serial),后半句值“指令重排序”和普通变量的”工作内存与主内存同步延迟“的现象。
在执行程序时为了提高性能,编译器和处理器经常会对指令进行重排序。从硬件架构上来说,指令重排序是指CPU采用了允许将多条指令不按照程序规定的顺序,分开发送给各个相应电路单元处理,而不是指令任意重排。重排序分成三种类型:
从Java源代码到最终实际执行的指令序列,会经过三种重排序。但是,为了保证内存的可见性,Java编译器在生成指令序列的适当位置会插入内存屏障指令来禁止特定类型的处理器重排序。对于编译器的重排序,JMM会根据重排序规则禁止特定类型的编译器重排序;对于处理器重排序,JMM会插入特定类型的内存屏障,通过内存的屏障指令禁止特定类型的处理器重排序。这里讨论JMM对处理器的重排序,为了更深理解JMM对处理器重排序的处理,先来认识一下常见处理器的重排序规则:
其中的N标识处理器不允许两个操作进行重排序,Y表示允许。其中Load-Load表示读-读操作、Load-Store表示读-写操作、Store-Store表示写-写操作、Store-Load表示写-读操作。可以看出:常见处理器对写-读操作都是允许重排序的,并且常见的处理器都不允许对存在数据依赖的操作进行重排序(对应上面数据转换那一列,都是N,所以处理器不允许这种重排序)。
那么这个结论对我们有什么作用呢?比如第一点:处理器允许写-读操作两者之间的重排序,那么在并发编程中读线程读到可能是一个未被初始化或者是一个NULL等,出现不可预知的错误,基于这点,JMM会在适当的位置插入内存屏障指令来禁止特定类型的处理器的重排序。内存屏障指令一共有4类:
根据上面的表格,处理器不会对存在数据依赖的操作进行重排序。这里数据依赖的准确定义是:如果两个操作同时访问一个变量,其中一个操作是写操作,此时这两个操作就构成了数据依赖。常见的具有这个特性的如i++、i—。如果改变了具有数据依赖的两个操作的执行顺序,那么最后的执行结果就会被改变。这也是不能进行重排序的原因。例如:
重排序遵守数据依赖性,编译器和处理器不会改变存在数据依赖关系的两个操作的执行顺序。但是这里所说的数据依赖性仅针对单个处理器中执行的指令序列和单个线程中执行的操作,不同处理器之间和不同线程之间的数据依赖性不被编译器和处理器考虑。
as-if-serial语义的意思指:管怎么重排序(编译器和处理器为了提高并行度),(单线程)程序的执行结果不能被改变。编译器,runtime 和处理器都必须遵守as-if-serial语义。
as-if-serial语义把单线程程序保护了起来,遵守as-if-serial语义的编译器,runtime 和处理器共同为编写单线程程序的程序员创建了一个幻觉:单线程程序是按程序的顺序来执行的。as-if-serial语义使单线程程序员无需担心重排序会干扰他们,也无需担心内存可见性问题。
如果代码中存在控制依赖的时候,会影响指令序列执行的并行度(因为高效)。也是为此,编译器和处理器会采用猜测(Speculation)执行来克服控制的相关性。所以重排序破坏了程序顺序规则(该规则是说指令执行顺序与实际代码的执行顺序是一致的,但是处理器和编译器会进行重排序,只要最后的结果不会改变,该重排序就是合理的)。
在单线程程序中,由于as-ifserial语义的存在,对存在控制依赖的操作重排序,不会改变执行结果;但在多线程程序中,对存在控制依赖的操作重排序,可能会改变程序的执行结果。
前面所述的内存交互操作必须要满足一定的规则,而happens-before就是定义这些规则的一个等效判断原则。happens-before是JMM定义的2个操作之间的偏序关系:如果操作A线性发生于操作B,则A产生的影响能被操作B观察到,“影响”包括修改了内存**享变量的值、发送了消息、调用了方法等。如果两个操作满足happens-before原则,那么不需要进行同步操作,JVM能够保证操作具有顺序性,此时不能够随意的重排序。否则,无法保证顺序性,就能进行指令的重排序。
happens-before原则主要包括:
示例代码1:
private int value = 0;
public void setValue(int value) {
this.value = value;
}
public int getValue() {
return this.value;
}
对于上面的代码,假设线程A在时间上先调用setValue(1),然后线程B调用getValue()方法,那么线程B收到的返回值一定是1吗?
按照happens-before原则,两个操作不在同一个线程、没有通道锁同步、线程的相关启动、终止和中断以及对象终结和传递性等规则都与此处没有关系,因此这两个操作是不符合happens-before原则的,这里的并发操作是不安全的,返回值并不一定是1。
对于该问题的修复,可以使用lock或者synchronized套用“管程锁定规则”实现先行发生关系;或者将value定义为volatile变量(两个方法的调用都不存在数据依赖性),套用“volatile变量规则”实现先行发生关系。如此一来,就能保证并发安全性。
示例代码2
// 以下操作在同一个线程中
int i = 1;
int j = 2;
上面的代码符合“程序次序规则”,满足先行发生关系,但是第2条语句完全可能由于重排序而被处理器先执行,时间上先于第1条语句。
参考
1、周志明,深入理解Java虚拟机:JVM高级特性与最佳实践,机械工业出版社
2、AlphaWang博客,http://blog.csdn.net/vking_wang/article/details/8574376
1、XA
2、TCC
3、结合MQ消息中间件实现的可靠消息的最终一致性
4、最大通知性
CPU中每个缓存行(caceh line)使用4种状态进行标记(使用额外的两位(bit)表示):
MESI协议中的状态
CPU中每个缓存行(caceh line)使用4种状态进行标记(使用额外的两位(bit)表示):
M: 被修改(Modified)
该缓存行只被缓存在该CPU的缓存中,并且是被修改过的(dirty),即与主存中的数据不一致,该缓存行中的内存需要在未来的某个时间点(允许其它CPU读取请主存中相应内存之前)写回(write back)主存。
当被写回主存之后,该缓存行的状态会变成独享(exclusive)状态。
E: 独享的(Exclusive)
该缓存行只被缓存在该CPU的缓存中,它是未被修改过的(clean),与主存中数据一致。该状态可以在任何时刻当有其它CPU读取该内存时变成共享状态(shared)。
同样地,当CPU修改该缓存行中内容时,该状态可以变成Modified状态。
S:共享的(Shared)
该状态意味着该缓存行可能被多个CPU缓存,并且各个缓存中的数据与主存数据一致(clean),当有一个CPU修改该缓存行中,
其它CPU中该缓存行可以被作废(变成无效状态(Invalid))。
I: 无效的(Invalid)
该缓存是无效的(可能有其它CPU修改了该缓存行)。
虽然BeanFactory才是spring的最顶级接口,但是实际在使用中我们都是使用ApplicationContext来标识一个spring容器,ApplicationContext相对beanFactory来说增加了很多新的特性,它继承了MessageSource、ResourceLoader和ApplicaionEventPublisher接口.
以下是ApplicationContext的继承结构。
从上图可以看出ApplicationContext的实现类主要有FileSystemXmlApplicationContext和ClassPathXmlApplicationContext(XML文件配置形式)。FileSystemXmlApplicationContext是面向文件系统配置xml,而ClassPathXmlApplicationContext是读取配置在classpath目录下,我们用ClassPathXmlApplicationContext来实例,如下:
ApplicationContext context = new ClassPathXmlApplicationContext("spring-context-${contextPlaceHolder}.xml");
执行以上代码完成,可以说一个spring就启动成功了!
深入源码实现之前,先思考几个问题:
[Spring如何定位、加载、解析XML配置文件的 ]
[Spring如何实现IOC和AOP的 ]
从技术的源头寻找答案,记录paper地址
https://www.oracle.com/technetwork/java/biasedlocking-oopsla2006-wp-149958.pdf?utm_source=hacpai.com
实现了DeferredImportSelector接口,用于处理@EnableAutoConfiguration,如果需要做一些定制化操作,可以重写该类
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.