额 16 年的问题,也回答下吧。
不确定在 window 下的 sourceTree 是否和 MacOS 下相同。
在 MacOS 下的设置如图。
菜单 -> 仓库 -> 仓库设置 -> 弹窗(提交模板) -> 自定义 -> 输入自定义的内容格式。
没有足够的数据
(゚∀゚ )
暂时没有任何数据
疯狂小兵 回答了问题 · 2019-11-12
额 16 年的问题,也回答下吧。
不确定在 window 下的 sourceTree 是否和 MacOS 下相同。
在 MacOS 下的设置如图。
菜单 -> 仓库 -> 仓库设置 -> 弹窗(提交模板) -> 自定义 -> 输入自定义的内容格式。
额 16 年的问题,也回答下吧。 不确定在 window 下的 sourceTree 是否和 MacOS 下相同。在 MacOS 下的设置如图。 菜单 -> 仓库 -> 仓库设置 -> 弹窗(提交模板) -> 自定义 -> 输入自定义的内容格式。
关注 2 回答 1
疯狂小兵 发布了文章 · 2019-10-14
Java 语言规范第三版中对 volatile 的定义如下: java 编程语言允许线程访问共享变量,为了确保共享变量能被准确和一致的更新,线程应该确保通过排他锁单独获得这个变量。Java 语言提供了 volatile,在某些情况下比锁更加方便。如果一个字段被声明成 volatile,java 线程内存模型确保所有线程看到这个变量的值是一致的。
并发编程中的三个特性:原子性,有序性和可见性。volatile就作用了其中的两个。
恰当的使用,它的使用和执行成本比synchronized更低,因为不会引起线程上下文的切换和调度。
volatile 是依赖于硬件层面的支持,即需要 CPU 的指定来实现。
对于volatile修饰的变量,在汇编语言层面会多一行指令 0x01a3de24: lock addl $0x0,(%esp);
。而该lock
指令通过查IA-32架构可知主要做两件事 :
详细原理如下:
处理器为了提高处理速度,不直接和内存进行通讯,而是先将系统内存的数据读到内部缓存(L1,L2 或其他)后再进行操作,但操作完之后不知道何时会写到内存,如果对声明了 Volatile 变量进行写操作,JVM 就会向处理器发送一条 Lock 前缀的指令,将这个变量所在缓存行的数据写回到系统内存。
但是就算写回到内存,如果其他处理器缓存的值还是旧的,再执行计算操作就会有问题,所以在多处理器下,为了保证各个处理器的缓存是一致的,就会实现缓存一致性协议,每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了,当处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置成无效状态,当处理器要对这个数据进行修改操作的时候,会强制重新从系统内存里把数据读到处理器缓存里。
Lock 前缀指令会引起处理器缓存回写到内存。 Lock 前缀指令导致在执行指令期间,声言处理器的 LOCK# 信号,在该信号期间,会独占使用任何共享内存
第一阶段:
优化后
缓存一致性机制会阻止同时修改被两个以上处理器缓存的内存区域数据。一个处理器的缓存回写到内存会导致其他处理器的缓存无效。
新的 CPU 会使用 MESI(修改,独占,共享,无效)控制协议来维护内部缓存和其他处理器的缓存的一致性。
可以看出硬件技术的进步对于软件的性能提升有质的飞越。
并发编程大师 Doug lea在 JDK1.7 中新增了队列集合类 LinkedTransferQueue,在使用Volatile时用追价字节的方式优化队列出栈和入栈的性能。
因为对于英特尔酷睿 i7,酷睿, Atom 和 NetBurst, Core Solo 和 Pentium M 处理器的 L1,L2 或 L3 缓存的高速缓存行是 64 个字节宽,不支持部分填充缓存行,这意味着如果队列的头节点和尾节点都不足 64 字节的话,处理器会将它们都读到同一个高速缓存行中,在多处理器下每个处理器都会缓存同样的头尾节点,当一个处理器试图修改头接点时会将整个缓存行锁定,那么在缓存一致性机制的作用下,会导致其他处理器不能访问自己高速缓存中的尾节点,而队列的入队和出队操作是需要不停修改头接点和尾节点,所以在多处理器的情况下将会严重影响到队列的入队和出队效率。Doug lea 使用追加到 64 字节的方式来填满高速缓冲区的缓存行,避免头接点和尾节点加载到同一个缓存行,使得头尾节点在修改时不会互相锁定。
上一段话核心意思是: 队列A的尾节点和队列B的头节点 在同一缓存行,队列 B 修改头节点时会锁住整个缓存行,导致队列A 不能访问自己的尾节点。因此需要补全 64 字节,让尾节点独占一个缓存行。
该方式是对空间和性能的一个折中和取巧方案。如果并发较大,修改比较频繁,可以使用该方式。主要是为了避免相互锁定
那什么情况下不适合呢?
越底层的知识越基础越重要。CPU 和内存,磁盘的交互机制不了解,就不能很好的在软件层面利用硬件能力进行性能提升。
https://www.infoq.cn/article/...查看原文
Java 语言规范第三版中对 volatile 的定义如下: java 编程语言允许线程访问共享变量,为了确保共享变量能被准确和一致的更新,线程应该确保通过排他锁单独获得这个变量。Java 语言提供了 volatile,在某些情况下比锁更加方便。如果一个字段被声明成 volatile,java ...
赞 1 收藏 1 评论 0
疯狂小兵 发布了文章 · 2019-06-25
原文:https://www.oreilly.com/ideas...
Storm的作者Nathan Marz提出了 lambda 架构,该架构是在 MapReduce 上和 Storm 上构建流式处理的应用。lambda 架构是捕获不可变的数据序列并将其并行的发送给批处理系统和流式处理系统。但是你需要分别在批处理系统和流式处理系统中实现一次数据处理逻辑。而在查询的时候需要将两个系统计算的结果合并在一起,以完成查询返回给请求端。
对于两种处理系统,你可以灵活替换实现系统,比如使用 kafka+storm 实现流出处理,使用 hadoop 实现批式处理,输出结果通常在分开的两个数据库中,一个是为了流式而优化,另一个是为了批式更新而优化。
但是对于Jay Kreps【原文作者】因为一直在从事实时数据管道的建设,虽然其中有一些风格就是 lambda 架构,但是他更喜欢一个新的替换的方案。
因为那些试图构建流式处理系统的人并没有过多的考虑数据重计算的问题,最终造成系统没有便利的方法来处理数据重计算。
lambda 架构强调保持输入的原始数据不可变并且显示的将数据重新计算的问题给展现了出来。通过 lambda 架构可以比较好的解决了流式数据和历史数据的计算问题。
因为随着时间的推移,代码可能会改变。改变的原因可能是你想在输出结果中新加一个字段,或者是因为代码有 bug 需要修复。不管是什么原因,要使得历史数据得到新的预期结果,就需要将数据重新计算。
因为 lambda 架构提出其中一个观点认为流式系统是近似的,不准确的,准确度不如批式处理。
Jay Kreps对此观点不敢苟同,他认为现存的流式处理的框架不如 MapReduce成熟,但不代表流式系统不能如批式系统那样提供强大的语义保证。而且 lambda 架构提出的标题是"beats the CAP theorem",即干掉 CAP 理论,但是实际上尽管在流处理上对延时和可用性间存在权衡,但因为这是一种异步处理架构。所以异步计算的结果也不能立即保持与输入的数据一致,因此 CAP 理论仍然没有被打破。
lambda 架构需要维护在两个复杂的分布式系统中输出相同结果的代码,就像看起来那么痛苦,而且Jay Kreps也不认为该问题是可以解决的。
因为 storm 和 Hadoop 分布式框架非常的复杂,因此不可避免的代码会针对其运行的框架进行设计。
Jay Kreps建议如果对延时性不敏感就仅使用如 MapReduce 这样的批处理系统。如果延迟敏感则使用流式处理框架,除非特别必须才同时使用这两种系统。
但是需求总是千奇百怪的,人们需要构建复杂的,低延时的处理系统,(而且在天朝 PM 都想要大而全的功能下,这样需求更盛)。
他们拥有的两件事情并不能解决他们的问题:一个可以处理历史数据的可扩展高延迟批处理系统和一个无法重新处理结果的低延迟流处理系统。但通过将两个东西连接在一起,实际上构成了一个可行的方案,也就是 lambda 架构。但尽管lambda 架构让人很痛苦,但确实也解决了重新计算这样通常让人忽略的问题。
但是Jay Kreps只认为 lambda 架构只是临时解决方案,它不是新的编程范例也不是大数据的未来方向。
因为在 linkedin 内部已经进行过多次的讨论和尝试。发现保持两个不同系统中编写的代码完全同步非常非常困难。用于隐藏底层框架的API 被证明是最 Low 的抽象,因为这样设计会需要深入的 Hadoop 知识和对实时层的深入了解,而且当你在调试或者因为性能问题排查原因时,还加上需要深入了解抽象层是怎么转换到底层的处理框架的。 也许简单的才是最有效的。
Jay Kreps在思考为什么不能改进流式处理系统来处理它的目标域中完整的问题集呢。
因此出现了两种思路
这种方式肯定使得事情变得更好些,但是不能解决问题。
即使这样可以避免编写两次代码,但是运行和调试两个系统的负担也会非常高,而且新的抽象只能提供两个系统特性的并集(但现在 Beam 不是在做这样的事情吗)。而且这样做与跨数据库 透明的 ORM一样的臭名昭著。
在原有系统之上提供相似的接口和界面化语言,在几乎不稳定的分布式系统上构建统一的抽象层比构建完全不同的编程范式要难得多。
该方式只在代码更改时需要重新计算。当然重新计算只是统一代码的改进版本,运行在相同的框架上,消费同样的输入数据。当然也可以提高 job 的并行度,以便快速完成。
而该架构被称为 Kappa架构
而且新旧两张表也可以同时存在,这样就可以通过将应用切换到旧表来恢复旧的逻辑。在特别重要的情况下,也可以使用AB 测试或 bandit 算法来确保无论是修复bug还是代码改进都不会意外降级。
同样的,数据还是可以存储在 HDFS 上的,但是数据的重新计算不会再在 HDFS 做了。
而对于 Kappa 系统,Linkedin 内部使用的Samza就正在使用。
两种方法之间的效率和资源权衡在某种程度上有所不同
在这两种情况下,再加工的额外负荷可能会平均。如果你有很多这样的工作,他们不会一次全部重新处理,所以在一个有几十个这样的工作的共享集群上,你可能会为在任何给定时间激活重新处理的少数Job提供额外的几个百分点的容量预算。
lambda 架构 | kappa 架构 | |
---|---|---|
数据处理能力 | 可处理超大规模的历史数据 | 历史数据处理能力有限 |
机器开销 | 批处理和实时计算需一直运行,机器开销大 | 必要时进行全量计算,机器开销相对较小 |
存储开销 | 只需要保存一份查询结果,存储开销较小 | 需要存储新老实例结果,存储开销相对较大。但如果是多 Job 共用的集群,则只需要预留出一小部分的存储即可 |
开发、测试难易程度 | 实现两套代码,开发、测试难度较大 | 只需面对一个框架,开发、测试难度相对较小 |
运维成本 | 维护两套系统,运维成本大 | 只需维护一个框架,运维成本小 |
对比表格参考自:http://bigdata.51cto.com/art/...
Kappa 的真正优势不是关于效率,而是关于允许人们在单个处理框架之上开发,测试,调试和操作他们的系统。因此,在简单性很重要的情况下,请将Kappa 架构视为Lambda架构的替代方案。
查看原文Storm的作者Nathan Marz提出了 lambda 架构,该架构是在 MapReduce 上和 Storm 上构建流式处理的应用。lambda 架构是捕获不可变的数据序列并将其并行的发送给批处理系统和流式处理系统。但是你需要分别在批处理系统和流式处理系统中实现一次数据处理逻辑。而在查询的...
赞 1 收藏 0 评论 0
疯狂小兵 收藏了文章 · 2019-05-24
IntelliJ IDEA 在 多窗口、多项目协作开发时,MacBook Pro的散热风扇凶猛地转动,相关配置如下:
MacBook Pro (Retina, 15-inch, Mid 2015)
型号名称: MacBook Pro
型号标识符: MacBookPro11,4
处理器名称: Intel Core i7
处理器速度: 2.2 GHz
处理器数目: 1
核总数: 4
L2 缓存(每个核): 256 KB
L3 缓存: 6 MB
内存: 16 GB
Boot ROM 版本: MBP114.0172.B16
SMC 版本(系统): 2.29f24
IntelliJ IDEA 2017.1.5
Build #IC-171.4694.70, built on July 4, 2017
JRE: 1.8.0_131-b11 x86_64
JVM: Java HotSpot(TM) 64-Bit Server VM by Oracle Corporation
Mac OS X 10.12.5
默认的IDEA JVM参数配置较低,其中配置存放在 /Applications/IntelliJ IDEA CE.app/Contents/bin/idea.vmoptions
文件中,该文件为IDEA 全局配置文件:
-Xms128m
-Xmx750m
-XX:ReservedCodeCacheSize=240m
Info.plist
文件Info.plist
文件该文件存放在/Applications/IntelliJ IDEA CE.app/Contents
目录下:
total 32
16 -rw-r--r-- 1 Mercy admin 4210 7 11 18:43 Info.plist
0 drwxr-xr-x@ 3 Mercy admin 102 7 11 16:21 MacOS
0 drwxr-xr-x@ 7 Mercy admin 238 7 5 14:06 Resources
0 drwxr-xr-x@ 3 Mercy admin 102 7 5 14:06 _CodeSignature
0 drwxr-xr-x@ 13 Mercy admin 442 7 11 18:00 bin
0 drwxr-xr-x@ 116 Mercy admin 3944 7 5 14:06 lib
0 drwxr-xr-x@ 34 Mercy admin 1156 4 25 15:49 license
0 drwxr-xr-x@ 33 Mercy admin 1122 4 25 15:49 plugins
0 drwxr-xr-x@ 3 Mercy admin 102 7 5 14:06 redist
VMOptions
用 vi 工具打开Info.plist
,其中存在一个 key 元素内容为VMOptions
的设置,如下所示:
<key>VMOptions</key>
<string>-Dfile.encoding=UTF-8 -XX:+UseConcMarkSweepGC -XX:SoftRefLRUPolicyMSPerMB=50 -ea -Dsun.io.useCanonCaches=false -Djava.net.preferIPv4Stack=true -XX:+HeapDumpOnOutOfMemoryError -XX:-OmitStackTraceInFastThrow -Xverify:none -XX:ErrorFile=$USER_HOME/java_error_in_idea_%p.log -XX:HeapDumpPath=$USER_HOME/java_error_in_idea.hprof -Xbootclasspath/a:../lib/boot.jar</string>
其中JVM 参数 -XX:+UseConcMarkSweepGC
为 IDEA 默认配置GC 算法,将其移除,修改为:
<key>VMOptions</key>
<string>-Dfile.encoding=UTF-8 -XX:SoftRefLRUPolicyMSPerMB=50 -ea -Dsun.io.useCanonCaches=false -Djava.net.preferIPv4Stack=true -XX:+HeapDumpOnOutOfMemoryError -XX:-OmitStackTraceInFastThrow -Xverify:none -XX:ErrorFile=$USER_HOME/java_error_in_idea_%p.log -XX:HeapDumpPath=$USER_HOME/java_error_in_idea.hprof -Xbootclasspath/a:../lib/boot.jar</string>
idea.vmoptions
文件通过命令行,cd到~/Library/Preferences/IntelliJIdeaXX/
目录下,如本人的机器路径:
/Users/Mercy/Library/Preferences/IdeaIC2017.1
idea.vmoptions
文件将新建或者待更新的idea.vmoptions
文件,更新以下JVM 配置项
-server
-XX:+UseG1GC
-XX:+UseNUMA
-Xms512m
-Xmn512m
-Xmx8g
-XX:MaxMetaspaceSize=512m
-XX:ReservedCodeCacheSize=240m
启动 IntelliJ IDEA 2017.1 后一小时有余,发现风扇狂转的问题基本上没有发生。
通过工具JConsole
连接 IDEA 进程,观察相关数据。
连接名称: pid: 9743 运行时间: 1 小时 39 分钟
虚拟机: Java HotSpot(TM) 64-Bit Server VM版本 25.131-b11 进程 CPU 时间: 6 分钟
VM 参数:-Dfile.encoding=UTF-8 -XX:SoftRefLRUPolicyMSPerMB=50 -ea -Dsun.io.useCanonCaches=false -Djava.net.preferIPv4Stack=true
-XX:+HeapDumpOnOutOfMemoryError -XX:-OmitStackTraceInFastThrow -Xverify:none
-XX:ErrorFile=/Users/Mercy/java_error_in_idea_%p.log -XX:HeapDumpPath=/Users/Mercy/java_error_in_idea.hprof -Xbootclasspath/a:../lib/boot.jar
-server -XX:+UseG1GC -XX:+UseNUMA -Xms512m -Xmx8g -XX:MaxMetaspaceSize=512m -XX:ReservedCodeCacheSize=240m
-Djb.vmOptionsFile=/Users/Mercy/Library/Preferences/IdeaIC2017.1/idea.vmoptions -Didea.java.redist=jdk-bundled
-Didea.home.path=/Applications/IntelliJ IDEA CE.app/Contents -Didea.executable=idea -Didea.platform.prefix=Idea -Didea.paths.selector=IdeaIC2017.1
其中用户idea.vmoptions
文件中的配置信息已经追加到JVM 启动参数中:
-server -XX:+UseG1GC -XX:+UseNUMA -Xms512m -Xmx8g -XX:MaxMetaspaceSize=512m -XX:ReservedCodeCacheSize=240m
当前堆大小: 376,068 KB
最大堆大小: 8,388,608 KB
提交的内存: 524,288 KB
暂挂最终处理: 0对象
垃圾收集器: 名称 = 'G1 Young Generation', 收集 = 58, 总花费时间 = 1.583 秒
垃圾收集器: 名称 = 'G1 Old Generation', 收集 = 2, 总花费时间 = 1.930 秒
GC 算法已经由CMS
切换成了G1
算法!
idea.vmoptions
文件Since version 14.0.0, the file /Applications/IntelliJ Idea XX.app/Contents/bin/idea.vmoptions or /Applications/IntelliJ Idea CE XX.app/Contents/bin/idea.vmoptions should be copied to ~/Library/Preferences/IntelliJIdeaXX/idea.vmoptions or ~/Library/Preferences/IdeaICXX/idea.vmoptions.
IntelliJ IDEA 版本升级时,除非用户自行控制,默认情况IDEA会将全局的idea.vmoptions
文件覆盖,因此,选择用户的idea.vmoptions
文件可避免升级配置覆盖。
默认的IDEA JVM参数配置较低,其中配置存放在 /Applications/IntelliJ IDEA CE.app/Contents/bin/idea.vmoptions 文件中,该文件为IDEA 全局配置文件:
疯狂小兵 评论了文章 · 2019-05-22
Mybatis有个实用的功能就是逆向工程,能根据表结构反向生成实体类,这样能避免手工生成出错。市面上的教程大多都很老了,大部分都是针对mysql5的,以下为我执行mysql8时的经验。
这里使用的是maven包管理工具,在pom.xml添加以下配置,以引入mybatis.generator
<build>
<finalName>SpringMVCBasic</finalName>
<!-- 添加mybatis-generator-maven-plugin插件 -->
<plugins>
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.2</version>
<configuration>
<verbose>true</verbose>
<overwrite>true</overwrite>
</configuration>
</plugin>
</plugins>
</build>
在maven项目下的src/main/resources 目录下新建generatorConfig.xml和generator.properties文件
generator.properties
jdbc.driverLocation=F:\\maven-repository\\mysql\\mysql-connector-java\\8.0.16\\mysql-connector-java-8.0.16.jar
jdbc.driverClass=com.mysql.cj.jdbc.Driver
jdbc.connectionURL=jdbc:mysql://localhost:3306/demo?useUnicode=true&characterEncoding=utf-8
jdbc.userId=test
jdbc.password=test123
注意:
1,generator.properties里面的jdbc.driverLocation指向是你本地maven库对应mysql-connector地址
2,与老版本不同,这里driversClass为com.mysql.cj.jdbc.Driver
generatorConfig.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<!--导入属性配置-->
<properties resource="generator.properties"></properties>
<!--指定特定数据库的jdbc驱动jar包的位置(绝对路径)-->
<classPathEntry location="${jdbc.driverLocation}"/>
<context id="default" targetRuntime="MyBatis3">
<!-- optional,旨在创建class时,对注释进行控制 -->
<commentGenerator>
<!--是否去掉自动生成的注释 true:是-->
<property name="suppressDate" value="true"/>
<property name="suppressAllComments" value="true"/>
</commentGenerator>
<!--jdbc的数据库连接:驱动类、链接地址、用户名、密码-->
<jdbcConnection
driverClass="${jdbc.driverClass}"
connectionURL="${jdbc.connectionURL}"
userId="${jdbc.userId}"
password="${jdbc.password}">
</jdbcConnection>
<!-- 非必需,类型处理器,在数据库类型和java类型之间的转换控制-->
<javaTypeResolver>
<property name="forceBigDecimals" value="false"/>
</javaTypeResolver>
<!-- Model模型生成器,用来生成含有主键key的类,记录类 以及查询Example类
targetPackage 指定生成的model生成所在的包名
targetProject 指定在该项目下所在的路径
-->
<javaModelGenerator targetPackage="com.ifly.outsourcing.entity"
targetProject="src/main/java">
<property name="enableSubPackages" value="true"/>
<property name="trimStrings" value="true"/>
</javaModelGenerator>
<!--Mapper映射文件生成所在的目录 为每一个数据库的表生成对应的SqlMap文件 -->
<sqlMapGenerator targetPackage="mappers"
targetProject="src/main/resources">
<property name="enableSubPackages" value="false"/>
</sqlMapGenerator>
<!-- 客户端代码,生成易于使用的针对Model对象和XML配置文件 的代码
type="ANNOTATEDMAPPER",生成Java Model 和基于注解的Mapper对象
type="MIXEDMAPPER",生成基于注解的Java Model 和相应的Mapper对象
type="XMLMAPPER",生成SQLMap XML文件和独立的Mapper接口
-->
<javaClientGenerator type="XMLMAPPER" targetPackage="com.ifly.outsourcing.dao"
targetProject="src/main/java">
<property name="enableSubPackages" value="true"/>
</javaClientGenerator>
<!-- 数据表进行生成操作 tableName:表名; domainObjectName:对应的DO -->
<table tableName="user" domainObjectName="user"
enableCountByExample="false" enableUpdateByExample="false"
enableDeleteByExample="false" enableSelectByExample="false"
selectByExampleQueryId="false">
</table>
</context>
</generatorConfiguration>
注意:这里主要注意修改对应的javaModelGenerator ,sqlMapGenerator,javaClientGenerator 为自己的生成路径。以及添加自己的数据表。
点击菜单栏的run,新建一个选项为maven的configurations,name为自己方便看,比如generator,commnd line注意写为:
mybatis-generator:generate -e
点击run即可生成对应文件。
本文最早发布于 Rootrl's blog
查看原文Mybatis有个实用的功能就是逆向工程,能根据表结构反向生成实体类,这样能避免手工生成出错。市面上的教程大多都很老了,大部分都是针对mysql5的,以下为我执行mysql8时的经验。
疯狂小兵 发布了文章 · 2019-05-22
插件名称 | 功能描述 | 备注 |
---|---|---|
lombok | 注解方式实现 Setter 和 Getter 等方法 | 可以大量减少模板代码的编写工作,代码更整洁 |
backgroundimage plus | 设置IDEA 的背景图片 | |
CodeGlance | 相当于当前代码文件的缩略图,可以快速移动到代码块 | IDEA 代码界面右侧的缩略预览 |
Idea restart | IDEA 的重启功能 | 因 IDEA 默认未自带重启功能,使用该插件可实现 |
mybatis log plugin | Mybatis 的日志插件,可在 IDEA 控制台看到完整可执行的 Sql 语句 | |
translation | 翻译插件 | |
markdown support | markdown 文件支持 | 可以打开 markdown 文件并以 markdown 的语法渲览文档 |
free mybatis plugin | Mybatis 的插件,可以在interface 和 sql 的 xml 文件间跳转 | |
maven helper | maven 依赖包的可视化展示 | 对于 maven 项目的依赖包的处理很有帮助 |
gson format | 将 JSON 转为 java 类 | 可以直接根据 JSON 数据定义出 java 类及其 field |
grep console | 控制台日志展示 | 不同级别的日志可以以不同的颜色展示 |
Material Theme | IDEA 的主题插件,可更换 IDEA 的展示风格 | 可选主题比较多,个人比较喜欢 |
string manipulation | String快速操作 | 变量拼写切换,Un/Escape,Encode/Decode,Increment/Decrement,sort,trim 等功能。比较强大 |
CamelCase | 变量风格变换 | 将驼峰、下划线、全大写、全小写等风格的转换。可快速装换变量风格 |
generateallsetter | 一键生成对象的所有 setter 方法调用 | 快捷便利 |
帮助 JAVA 开发者提升开发效率的插件工具 插件名称 功能描述 备注 lombok 注解方式实现 Setter 和 Getter 等方法 可以大量减少模板代码的编写工作,代码更整洁 backgroundimage plus 设置IDEA 的背景图片 CodeGlance 相当于当前代码文件的缩略图,可以快速移动到代码...
赞 1 收藏 1 评论 0
疯狂小兵 赞了文章 · 2019-05-20
原以为线程池还挺简单的(平时常用,也分析过原理),这次是想自己动手写一个线程池来更加深入的了解它;但在动手写的过程中落地到细节时发现并没想的那么容易。结合源码对比后确实不得不佩服 Doug Lea
。
我觉得大部分人直接去看 java.util.concurrent.ThreadPoolExecutor
的源码时都是看一个大概,因为其中涉及到了许多细节处理,还有部分 AQS
的内容,所以想要理清楚具体细节并不是那么容易。
<!--more-->
与其挨个分析源码不如自己实现一个简版,当然简版并不意味着功能缺失,需要保证核心逻辑一致。
所以也是本篇文章的目的:
自己动手写一个五脏俱全的线程池,同时会了解到线程池的工作原理,以及如何在工作中合理的利用线程池。
再开始之前建议对线程池不是很熟悉的朋友看看这几篇:
这里我截取了部分内容,也许可以埋个伏笔(坑)。
具体请看这两个链接。
由于篇幅限制,本次可能会分为上下两篇。
现在进入正题,新建了一个 CustomThreadPool
类,它的工作原理如下:
简单来说就是往线程池里边丢任务,丢的任务会缓冲到队列里;线程池里存储的其实就是一个个的 Thread
,他们会一直不停的从刚才缓冲的队列里获取任务执行。
流程还是挺简单。
先来看看我们这个自创的线程池的效果如何吧:
初始化了一个核心为3、最大线程数为5、队列大小为 4 的线程池。
先往其中丢了 10 个任务,由于阻塞队列的大小为 4 ,最大线程数为 5 ,所以由于队列里缓冲不了最终会创建 5 个线程(上限)。
过段时间没有任务提交后(sleep
)则会自动缩容到三个线程(保证不会小于核心线程数)。
来看看具体是如何实现的。
下面则是这个线程池的构造函数:
会有以下几个核心参数:
miniSize
最小线程数,等效于 ThreadPool
中的核心线程数。maxSize
最大线程数。keepAliveTime
线程保活时间。workQueue
阻塞队列。notify
通知接口。大致上都和 ThreadPool
中的参数相同,并且作用也是类似的。
需要注意的是其中初始化了一个 workers
成员变量:
/**
* 存放线程池
*/
private volatile Set<Worker> workers;
public CustomThreadPool(int miniSize, int maxSize, long keepAliveTime,
TimeUnit unit, BlockingQueue<Runnable> workQueue, Notify notify) {
workers = new ConcurrentHashSet<>();
}
workers
是最终存放线程池中运行的线程,在 j.u.c
源码中是一个 HashSet
所以对他所有的操作都是需要加锁。
我这里为了简便起见就自己定义了一个线程安全的 Set
称为 ConcurrentHashSet
。
其实原理也非常简单,和 HashSet
类似也是借助于 HashMap
来存放数据,利用其 key
不可重复的特性来实现 set
,只是这里的 HashMap
是用并发安全的 ConcurrentHashMap
来实现的。
这样就能保证对它的写入、删除都是线程安全的。
不过由于 ConcurrentHashMap
的 size()
函数并不准确,所以我这里单独利用了一个 AtomicInteger
来统计容器大小。
往线程池中丢一个任务的时候其实要做的事情还蛮多的,最重要的事情莫过于创建线程存放到线程池中了。
当然我们不能无限制的创建线程,不然拿线程池来就没任何意义了。于是 miniSize maxSize
这两个参数就有了它的意义。
但这两个参数再哪一步的时候才起到作用呢?这就是首先需要明确的。
从这个流程图可以看出第一步是需要判断是否大于核心线程数,如果没有则创建。
结合代码可以发现在执行任务的时候会判断是否大于核心线程数,从而创建线程。
worker.startTask()
执行任务部分放到后面分析。
这里的 miniSize
由于会在多线程场景下使用,所以也用 volatile
关键字来保证可见性。
结合上面的流程图,第二步自然是要判断队列是否可以存放任务(是否已满)。
优先会往队列里存放。
一旦写入失败则会判断当前线程池的大小是否大于最大线程数,如果没有则继续创建线程执行。
不然则执行会尝试阻塞写入队列(j.u.c
会在这里执行拒绝策略)
以上的步骤和刚才那张流程图是一样的,这样大家是否有看出什么坑嘛?
从上面流程图的这两步可以看出会直接创建新的线程。
这个过程相对于中间直接写入阻塞队列的开销是非常大的,主要有以下两个原因:
所以理想情况下我们应该避免这两步,尽量让丢入线程池中的任务进入阻塞队列中。
任务是添加进来了,那是如何执行的?
在创建任务的时候提到过 worker.startTask()
函数:
/**
* 添加任务,需要加锁
* @param runnable 任务
*/
private void addWorker(Runnable runnable) {
Worker worker = new Worker(runnable, true);
worker.startTask();
workers.add(worker);
}
也就是在创建线程执行任务的时候会创建 Worker
对象,利用它的 startTask()
方法来执行任务。
所以先来看看 Worker
对象是长啥样的:
其实他本身也是一个线程,将接收到需要执行的任务存放到成员变量 task
处。
而其中最为关键的则是执行任务 worker.startTask()
这一步骤。
public void startTask() {
thread.start();
}
其实就是运行了 worker
线程自己,下面来看 run
方法。
task.run
),接着会一直不停的从队列里获取任务执行,直到获取不到新任务了。workers.remove(this)
)。其实 getTask
也是非常关键的一个方法,它封装了从队列中获取任务,同时对不需要保活的线程进行回收。
很明显,核心作用就是从队列里获取任务;但有两个地方需要注意:
null
之后在上文的 run()
中就会退出这个线程;从而达到了回收线程的目的,也就是我们之前演示的效果workers.size() > miniSize
条件多次执行,从而导致线程被全部回收完毕。最后来谈谈线程关闭的事;
还是以刚才那段测试代码为例,如果提交任务后我们没有关闭线程,会发现即便是任务执行完毕后程序也不会退出。
从刚才的源码里其实也很容易看出来,不退出的原因是 Worker
线程一定还会一直阻塞在 task = workQueue.take();
处,即便是线程缩容了也不会小于核心线程数。
通过堆栈也能证明:
恰好剩下三个线程阻塞于此处。
而关闭线程通常又有以下两种:
我们先来看第一种立即关闭
:
/**
* 立即关闭线程池,会造成任务丢失
*/
public void shutDownNow() {
isShutDown.set(true);
tryClose(false);
}
/**
* 关闭线程池
*
* @param isTry true 尝试关闭 --> 会等待所有任务执行完毕
* false 立即关闭线程池--> 任务有丢失的可能
*/
private void tryClose(boolean isTry) {
if (!isTry) {
closeAllTask();
} else {
if (isShutDown.get() && totalTask.get() == 0) {
closeAllTask();
}
}
}
/**
* 关闭所有任务
*/
private void closeAllTask() {
for (Worker worker : workers) {
//LOGGER.info("开始关闭");
worker.close();
}
}
public void close() {
thread.interrupt();
}
很容易看出,最终就是遍历线程池里所有的 worker
线程挨个执行他们的中断函数。
我们来测试一下:
可以发现后面丢进去的三个任务其实是没有被执行的。
而正常关闭则不一样:
/**
* 任务执行完毕后关闭线程池
*/
public void shutdown() {
isShutDown.set(true);
tryClose(true);
}
他会在这里多了一个判断,需要所有任务都执行完毕之后才会去中断线程。
同时在线程需要回收时都会尝试关闭线程:
来看看实际效果:
上文或多或少提到了线程回收的事情,其实总结就是以下两点:
shutdown/shutdownNow
方法都会将线程池的状态置为关闭状态,这样只要 worker
线程尝试从队列里获取任务时就会直接返回空,导致 worker
线程被回收。null
时就会触发回收。但如果我们的队列足够大,导致线程数都不会超过核心线程数,这样是不会触发回收的。
比如这里我将队列大小调为 10 ,这样任务就会累计在队列里,不会创建五个 worker
线程。
所以一直都是 Thread-1~3
这三个线程在反复调度任务。
本次实现了线程池里大部分核心功能,我相信只要看完并动手敲一遍一定会对线程池有不一样的理解。
结合目前的内容来总结下:
shutdownNow()
方法关闭线程池,会导致任务丢失(除非业务允许)。keepalive
值,使得线程尽量不被回收从而可以复用线程。同时下次会分享一些线程池的新特性,如:
本文所有源码:
你的点赞与分享是对我最大的支持
原以为线程池还挺简单的(平时常用,也分析过原理),这次是想自己动手写一个线程池来更加深入的了解它;但在动手写的过程中落地到细节时发现并没想的那么容易。结合源码对比后确实不得不佩服 Doug Lea 。
赞 16 收藏 11 评论 0
疯狂小兵 发布了文章 · 2019-05-13
org.apache.ibatis.type.EnumOrdinalTypeHandler<E>
:该类实现了枚举类型和Integer类型的相互转换。但是给转换仅仅是将对应的枚举转换为其索引位置,也就是"ordinal()"方法获取到的值。对应自定义的int值,该类无能为力。
org.apache.ibatis.type.EnumTypeHandler<E>
:该类实现了枚举类型和String类型的相互转换。对于想将枚举在数据库中存储为对应的int值的情况,该类没办法实现。
基于以上mybatis提供的两个枚举处理类的能力有限,因此只能自己定义对枚举的转换了。
EnumValueTypeHandler
该类需要继承org.apache.ibatis.type.BaseTypeHandler<E>
,然后在重定义的方法中实现自有逻辑。
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.apache.ibatis.type.MappedTypes;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
/**
* 处理实现了{@link EsnBaseEnum}接口的枚举类
* @author followtry
* @time 2016年8月16日 下午8:06:49
* @since 2016年8月16日 下午8:06:49
*/
//在 xml 中添加该 TypeHandler 时需要使用该注解
@MappedTypes(value = {
QcListTypeEnum.class,
SellingQcBizTypeEnum.class
})
public class EnumValueTypeHandler<E extends EsnBaseEnum> extends BaseTypeHandler<E> {
private Class<E> type;
private final E[] enums;
public EnumValueTypeHandler(Class<E> type) {
if (type == null) {
throw new IllegalArgumentException("Type argument cannot be null");
}
this.type = type;
this.enums = type.getEnumConstants();
if (this.enums == null) {
throw new IllegalArgumentException(type.getSimpleName() + " does not represent an enum type.");
}
}
@Override
public void setNonNullParameter(PreparedStatement ps, int i, E parameter, JdbcType jdbcType) throws SQLException {
//获取非空的枚举的int值并设置到statement中
ps.setInt(i, parameter.getValue());
}
@Override
public E getNullableResult(ResultSet rs, String columnName) throws SQLException {
int i = rs.getInt(columnName);
if (rs.wasNull()) {
return null;
} else {
try {
return getEnumByValue(i);
} catch (Exception ex) {
throw new IllegalArgumentException(
"Cannot convert " + i + " to " + type.getSimpleName() + " by ordinal value.", ex);
}
}
}
/**
* 通过枚举类型的int值,获取到对应的枚举类型
* @author jingzz
* @param i
*/
protected E getEnumByValue(int i) {
for (E e : enums) {
if (e.getValue() == i) {
return e;
}
}
return null;
}
@Override
public E getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
int i = rs.getInt(columnIndex);
if (rs.wasNull()) {
return null;
} else {
try {
return getEnumByValue(i);
} catch (Exception ex) {
throw new IllegalArgumentException(
"Cannot convert " + i + " to " + type.getSimpleName() + " by ordinal value.", ex);
}
}
}
@Override
public E getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
int i = cs.getInt(columnIndex);
if (cs.wasNull()) {
return null;
} else {
try {
return getEnumByValue(i);
} catch (Exception ex) {
throw new IllegalArgumentException(
"Cannot convert " + i + " to " + type.getSimpleName() + " by ordinal value.", ex);
}
}
}
}
该处理器是处理继承了EsnBaseEnum
接口的枚举类,因为该接口中定义了获取自定义int值的方法。
如果在 mybatis 的 xml 中配置 该 typehandler,则需要添加注解@MappedTypes
。在添加 typeHandler 注册时使用具体的实现类注册。
配置文件如下:
<typeAliases>
<!-- 为自定义的 TypeHandler 指定别名,在 sql 的 xml 中即可使用别名访问了 -->
<typeAlias type="cn.followtry.mybatis.EnumValueTypeHandler" alias="enumValueTypeHandler"/>
</typeAliases>
<typeHandlers>
<!--处理枚举的值转换-->
<typeHandler handler="cn.followtry.mybatis.EnumValueTypeHandler"/>
</typeHandlers>
自定义的 Enum 需要实现的接口
/**
* @author jingzz
* @time 2016年8月17日 上午9:22:46
* @since 2016年8月17日 上午9:22:46
*/
public interface EsnBaseEnum {
public int getValue();
}
而实现了EsnBaseEnum
的枚举示例如下:
/**
* 同步的类型
* @author jingzz
* @time 2016年8月3日 上午11:13:06
*/
public enum SyncType implements EsnBaseEnum {
DEPT(3),//部门
PERSON(1);//人员
private int value;
private SyncType(int value) {
this.value = value;
}
@Override
public int getValue(){
return this.value;
}
}
只有在实现了EsnBaseEnum
的接口,EnumValueTypeHandler
才能通过接口的getValue方法获取到对应枚举的值。
到此,对于枚举的简单处理逻辑已经实现完成了,接下来就是如何配置来使用该自定义枚举处理逻辑
首先,mybatis中对于处理逻辑的设置是在sql的映射文件中,如EsnSyncLogMapper.xml
。
关键的设置枚举处理的位置如下:
<resultMap id="BaseResultMap" type="com.test.dao.model.EsnSyncLog" >
<id column="id" property="id" jdbcType="INTEGER" />
<result column="sync_time" property="syncTime" jdbcType="TIMESTAMP" />
<result column="total_num" property="totalNum" jdbcType="INTEGER" />
<result column="success_total_num" property="successTotalNum" jdbcType="INTEGER" />
<result column="type" property="type" typeHandler="com.test.common.EnumValueTypeHandler" />
<result column="msg" property="msg" jdbcType="VARCHAR" />
</resultMap>
其中type列设置了属性typeHandler,其值为自定义的枚举处理逻辑。
而在具体sql中也需要使用typeHandler属性,如:
<if test="type != null" >
#{type, typeHandler=com.test.common.EnumValueTypeHandler},
</if>
在mybatis处理到该位置时,就会调用typeHandler指定的处理类来处理枚举类型。
查看原文org.apache.ibatis.type.EnumOrdinalTypeHandler<E> :该类实现了枚举类型和Integer类型的相互转换。
赞 1 收藏 1 评论 0
疯狂小兵 发布了文章 · 2019-04-03
包名称 | 包内内容简介 |
---|---|
annotation | 注解目录。包括所有的注解。如@SELECT ,@UPDATE 等 |
binding | Mapper类的实例反射生成工具目录 |
builder | 主要是注解,mapper和SqlSuorce的构造器及转换器 |
cache | Mybatis内部缓存接口。实现了一些特定的缓存策略。FifoCache ,LruCache ,BlockingCache ,LoggingCache 等 |
cursor | 默认的游标处理类 |
dataSource | 数据源工厂类及实现。实现类包括JndiDataSourceFactory 、PooledDataSourceFactory 、UnpooledDataSourceFactory 。 数据源实现类: UnpooledDataSource 、PooledDataSource |
exceptions | Mybatis自定义的三个异常类。ExceptionFactory 、PersistenceException 、TooManyResultsException 、IbatisException 。都继承自RuntimeException |
executor | 执行器相关包。包括Key生成器、加载器(包括Cglib、Javassist的代理,结果加载器)、参数处理器接口、结果处理器、结果集(resultSet)处理器、Statement处理器(实现类:BaseStatementHandler 、CallableStatementHandler 、PreparedStatementHandler 、RoutingStatementHandler 、SimpleStatementHandler )、执行器(SimpleExecutor 、ReuseExecutor 、CachingExecutor 、BatchExecutor 、BaseExecutor ) |
io | 主要是定义的几个VFS(VFS 、DefaultVFS 、ClassLoaderWrapper ) |
javassist | javassist的字节码处理包 |
jdbc | 与Sql相关的操作。如Sql运行器,脚本运行器和Sql封装类等 |
lang | 指定是用java7还是java8的API的注解.UsesJava7 、UsesJava8 |
logging | 各个类型的日志适配器,都实现了Log 接口。StdOutImpl 、Slf4jImpl 、NoLoggingImpl 、Log4j2Impl 、Log4jImpl 、Jdk14LoggingImpl 、BaseJdbcLogger 、JakartaCommonsLoggingImpl |
mapping | 主要是接口参数,sql和返回结果的映射类,主要类包括:MappedStatement ,ParameterMap ,ParameterMapping ,ResultMap ,ResultMapping ,BoundSql ,SqlSource 等类 |
ognl | ognl包在Mybatis中的内部代码引用 |
parsing | 变量解析.如解析${} ,#{} 等 |
plugin | 主要包含插件的定义接口。如Interceptor ,Plugin ,InterceptorChain 等 |
reflection | 主要是一些反射操作的工具方法和对象工厂类,以及一些常用的包装类,如BaseWrapper ,BeanWrapper ,CollectionWrapper ,MapWrapper ,ObjectWrapper ,, , |
scripting | 执行驱动和动态Sql解析的老巢 |
session | 主要是SqlSession和SqlSessionFactory |
transaction | 主要是mybatis简单封装的jdbc事务操作类 |
type | 各个类型数据的处理器。用于动态的设置参数和转换数据。如IntegerTypeHandler 用来处理Integer类型的值的set和get操作。除了八大基本类型。还有常用的集合及Map类型,还增加了各种时间类型的处理器 |
通过整理每个包的主要功能。通览整个mybatis的代码结构,了解各个组件的位置和大概的处理关系。为后续分析源码打下基础。
查看原文Mybatis核心包 包名称 包内内容简介 annotation 注解目录。包括所有的注解。如@SELECT,@UPDATE等 binding Mapper类的实例反射生成工具目录 builder 主要是注解,mapper和SqlSuorce的构造器及转换器 cache Mybatis内部缓存接口。实现了一些特定的缓存策略。FifoCache,...
赞 7 收藏 5 评论 0
查看全部 个人动态 →
无
注册于 2016-01-15
个人主页被 882 人浏览
推荐关注