时光飞逝如白驹过隙,离上一篇博客已经有快6年的时间了。6年间,我从工程师逐步成长为团队管理者,有新的挑战以及认知模式上的转变。生活上,我新添了“父亲”这个有重量、有温度的角色。角色上的转变,令我感悟良多,收获满满,我会在后续的博客中细细道来。
后续我的博客会按照风控、技术与产品、领导力、科学与哲学这4个维度展开。先回顾下这6年的收获,再展望未来,开启无限的可能性。
作为回归的开篇,我会先从我最近6年的书单开始梳理我的成长路径,也推荐给大家。
20本,TBD
18本,TBD
32本,TBD
]]>代码质量是一个程序员最好的名片
你的代码不仅代表你个人而且代表整个团队
同一个团队工作, 相互之间代码的对接免不了, 单元测试能让其他同事快速理解你的代码, 并能迅速上手帮你维护. 宏观上来看, 这大大降低了大家的沟通成本, 并能够打破壁垒, 使大家互为备份.
单元测试能让你对自己的代码更有信心, 重构的门槛除了那些方法论, 就是一个高覆盖率的单元测试.
说到谈到打破壁垒, 另外重要的一点就是代码的可读性与整洁工整性. 代码应该让人一眼看上去很舒服, 这样才有看下去的欲望. 此处推荐«编写可读代码的艺术»
很多人说我会写main方法啊, 在我本地跑通了就ok了啊.
的确没错, main方法确实能让那时的你确认你的代码在一般情况下是ok的. 可是过上一段时间, 你再回头看看有点陌生的代码, 可能底层数据也变了, 你还能顺利执行么? 你还能记得你当时想要的结果是什么?
自测方法一般是end to end的测试. 很难去按照模块进行测试, 如果修改了代码, 定位问题也很难.
这些问题单元测试都可以解决.
- 通过命名或者注解, 你可以将你想测试的大致情况写下来.
- assert, 可以方便的进行结果的断言. 将你认为正确的结果与真实结果进行比较
- 不依赖于外部环境? 没问题! mysql, rpc, restapi都可以mock. mock的框架: Mockito, EasyMock, PowerMock
- 想真实的测一下sql? 没问题! H2 内存数据库与Spring Test完美集成.
- 想测一下异常处理? 没问题! Mock可以帮你模拟任何异常情况
- 各种corner case? 还是万能的Mock, 都能帮你搞定!
我认为单元测试也并不是每一行代码都需要写. 还是抓住那个要领. 关键路径, 重构 必写. CRUD代码可以不写.
如果你觉得没时间写单元测试, 那么有这么几种可能: 要么你只写CRUD简单代码, 要不你觉得你的代码质量无所谓. 这两点都是致命的.
总之, 不要为了写而写. 要抱着对自己负责, 对团队负责的心态去写. 相信经过时间的考验, 你一定离不开它.
2015年11月6日夜于家中.
下面我会先来介绍DISC性格测试及自组织团队的定义
这是一种心理测评, 能反映出一个人大致的性格倾向. 具体的介绍网上很多, 也可以在线测评
这里说下大致的解释:
D(支配型): 外向-任务, 表现: 气场足, 敢于挑战困难, 寻求胜利.
I(影响型): 外向-人际, 表现: 开放, 愿意分享情感, 希望被他人喜欢及认可.
S(稳定型): 内向-人际, 表现: 以家庭和团体为导向, 有组织性, 谨慎做出改变.
C(遵从型): 内向-任务, 表现: 重视规则, 有勤恳工作的价值观.
我也是在入职培训的时候首次听说过这个DISC理论. 虽然我一直反对将性格定性, 但最起码, DISC理论分析出来人格, 应该是你多种性格中占比较重的那个.
自组织团队在很多将敏捷的书中被反复提及, 其实并不是软件行业才可有自组织团队, 整个自然都是相通的, 就像«失控»(凯文凯利)这本书里面说的一样. 自然界中拥有很多分布式系统, 这些系统有各种我们熟知的名字: 蜂群, 电脑网络, 大脑神经元网络, 食物链等等.
以下是«失控»总结的分布式系统的突出特点, 我认为也可以映射为自组织团队的特点:
没有强制性的中心控制. – 不需要智商超常, 亲力亲为, 关注细节的D型领导
次级单位具有自治特质 – 团队里面的每个成员都要具有自我驱动, 自我管理的特质及尽心为团队服务的意识
次级单位之间彼此高度连接 – 团队成员之间频繁交流, 关系融洽, 没有隔阂
点对点间的影响通过网络形成了非线性因果关系 – 产出是经过多个成员的努力所完成的, 形成1+1>2的结果.
很有幸的是自己曾经在多个自组织团队中工作过, 反思那几段时光对于自身的成长极有帮助, 无论是技术还是生活上. 大家在一起为同一个目标努力及奋斗, 分享各自收获的知识, 问题往往在团队的讨论中就得出了最优解决方案, 从不感觉累并且每天干劲十足. 我也十分认同其他自组织团队中成员说过的话: 我们可以有很资深的人来指导我们, 也可以没有, 我们相信团队的力量与群体的智慧. 三个臭皮匠赛过诸葛亮!
上面说了那么多关于 DISC性格, 自组织团队 , 那么怎样打造一支自组织团队呢?
看上去好像自组织团队中领导并没有任何作用, 完全依赖于每个员工自身的相互联系, 其实不然, 领导其实是打造或者说培养一个自组织团队中最重要的一环. 就像«道德经»所说: “上善若水, 水利万物而不争, 处众人之所恶, 故几于道”. 一个优秀的领导应该是那种喜欢聆听, 乐于助人, 信息透明, 善于带动团队氛围, 帮助团队成员解决问题, 更重视结果而非过程的人. 宏观上看, 一个团队的完美运作跟领导看似没什么关系, 其实优秀的领导在默默地帮助团队中每一个人解决他们自身的问题, 可能是生活上, 可能是工作上的. 把一些脏活累活扛在肩上, 让团队去做他们应该做的事情.
这种领导往往在性格上偏向于I型. 他能够去带动团队建立好的分享氛围, 建立起员工的自我意识.
说到分享精神, 最近和好友聊天, 发现具有分享精神的行业发展应该都是比较迅速的, 比如现代医学, 互联网, 在电动车行业特斯拉也算是一家比较传奇的公司. 能够开放自己的技术专利, 为全人类的技术进步做贡献, 我认为这种具有普世精神公司与行业是最有希望和最值得燃烧生命的.
刚才说完了领导在自组织团队的重要性, 团队成员应该有怎样的特质呢?
我认为人品是超越一切的存在, 一些专业实力和技巧都可以通过学习去获得. 但是这个员工自身性格, 主人翁意识缺很难改变. 如果无法与团队成员建立平等与密切的关系, 那么这些节点间的沟通一定会出现问题, 当然, 如果专业素养及知识栈, 价值观等与团队格格不入那肯定也是有问题的.
那么过于强势(D型)和过于弱势(S型)及顺从(C型)的人是不是难以在自组织团队中站稳自己位置呢? 其实不然, 团队中不同性格的人可以玩的很融洽的, 大家性格并不是一成不变的, 是在相互的接触中相互影响的. 前提是这个人要跟大家建立足够好的关系与私交. 大家成为朋友, 无话不谈, 而且相互认可对方, 那么团队在离职率上面也会下降很多.
上面说了那么多, 最终还是用一句话总结, 就是:
去中心化的团队中, 领导其实很重要.
成员性格其实没那么重要, 人品最重要.
分享精神是值得鼓励与尊重的.
如果项目中需要发布一些外部系统所依赖的包, 那么应该有如下几步:
- 把版本号后面的SNAPSHOT去掉(SNAPSHOT是可以被重复覆盖的), 向maven私服发布一个稳定版本;
- 需要在git上面建立tag, 作为release的milestone;
- 为下一个迭代进行SNAPSHOT版本的准备.
如果人肉去做这几件事情, 会很烦还容易出错. maven release plugin 就是帮我们做这几件事情的. 他还会额外帮助我们做:
- 检查项目中所有的外部依赖包, 是否包含SNAPSHOT;
- 检查是否有未提交的代码;
- 运行单元测试, 确保全部通过.
一般来说引入maven release plugin需要如下几步:
- 在项目的pom文件中(如果是multi-module的只需要在主Pom中添加), 增加
和 , 无需添加mvn-release-plugin的依赖, 因为它默认被包含于maven的effective pom中; - 检查自己的maven settings.xml是否包含了私服的用户名密码;
- 确保自己本地代码是在主分支, 并且是最新的副本, 否则后果自负;
- 执行 mvn release:prepare, 这时插件会扫描项目依赖查看是否有SNAPSHOT, 是否存在未提交的文件, 确定当前release的版本号和下一个迭代的版本号, 插件会运行单元测试, 并向git中提交两次commit, 一次是release版本, 一次是下一个迭代的版本. 并将当前release版本打一个tag并提交到git上面去;
- 执行 mvn release:perform, 插件会执行mvn deploy 操作, 并clean掉生成的缓存文件.
so easy, 顺利的进行完成后, 你就会发现, 项目已经顺利的从 1.0-SNAPSHOT 变成了 1.0.1-SNAPSHOT. 并且 1.0 版本已经在git的tag和maven的私服上面了. 大功告成!
执行mvn release:prepare之后, git log
commit ff
Author: jack.zhang <jack.zhang@xxx.com>
Date: Tue Aug 25 17:45:56 2015 +0800
[maven-release-plugin] prepare for next development iteration
commit be
Author: jack.zhang <jack.zhang@xxx.com>
Date: Tue Aug 25 17:45:50 2015 +0800
[maven-release-plugin] prepare release project-1.0.1
pom文件中的设置
<distributionManagement>
<repository>
<id>releases</id>
<name>xxxx internal releases repository</name>
<url>http://xxx.com/nexus/content/repositories/releases</url>
</repository>
<snapshotRepository>
<id>snapshots</id>
<name>xxxx internal snapshots repository</name>
<url>http://xxx.com/nexus/content/repositories/snapshots</url>
</snapshotRepository>
</distributionManagement>
<scm>
<url>http://xxx.com/project</url>
<connection>scm:git:git@xxx.com/project.git</connection>
<developerConnection>scm:git:git@xxx.com/project.git</developerConnection>
<tag>HEAD</tag>
</scm>
一般情况下, 如果出现任何的build失败的情况, 请仔细查阅maven日志, 无非是以下几种情况
出现这些情况不要慌, 如果没有执行mvn release:perform, 那么改动是不会真正发布到maven私服上面的, 可以通过 mvn release:rollback, 进行回退处理.
如果还不行那么最简单的方案就是 mvn release:clean , git clean -fd, 会清除一切的maven插件产生的中间文件
]]>如果是mvn release:perform时出现400问题, 那么检查一下你的私服的用户名密码是否在maven settings.xml中配置正确, 再检查一下当前release的版本是否在私服上已经有了.
最近项目中, 使用AngularJS和Bootstrap进行数据渲染和前端布局, 也遇到和解决了一些问题. 这次先把文件上传下载这块记录下来, 也算是知识备份.
文件上传, HTML本身支持的文件上传不够优雅, 与Angular结合度比较低. github上关于这块的开源项目比较多, 最终选择了功能更为强大, 文档写的更细致的angular-file-upload.
1. 支持多文件批量上传
2. 在文件添加后, 上传前, 上传后, 返回后等各个阶段提供回调函数. 我用它来做文件后缀名的检查, 比较好用.
3. 支持图片的上传缩略图
4. 支持拖拽上传
5. API文档详细, 例子全面.
示例代码:
<div class="row" ng-show="upload_classes == null">
<div ng-show="uploader.isHTML5">
<div>
<input type="file" nv-file-select="" uploader="uploader"/>
</div>
<div ng-repeat="item in uploader.queue">
<div class="progress" style="margin-bottom: 0;">
<div class="progress-bar" role="progressbar"
ng-style="{ 'width': item.progress + '%' }"></div>
</div>
</div>
</div>
<button type="button" class="btn btn-success" ng-click="uploader.uploadAll()"
ng-disabled="!uploader.getNotUploadedItems().length">
<span class="glyphicon glyphicon-upload"></span> 上传
</button>
</div>
angular.module('pluginMgmt', ['ngDialog', 'angularFileUpload'])
.controller("pluginMgmtCtrl", ['$scope', '$http', 'FileUploader', 'ngDialog', function ($scope, $http, FileUploader, ngDialog) {
//显示上传浮层
$scope.show_uploader = function () {
$scope.creator_ref = ngDialog.open({
className: 'ngdialog-theme-default dialogwidth600',
template: 'plugin_uploader',
scope: $scope,
controller: ['$scope', function ($scope) {
//清空uploader
function clear_uploader() {
uploader.clearQueue();
uploader.destroy();
}
//检查是否是jar文件
uploader.onAfterAddingFile = function (fileItem) {
var name = fileItem.file.name;
if (name.indexOf(".jar") != name.length - 4) {
warn("请上传JAR文件");
clear_uploader();
}
};
//上传后展示
uploader.onCompleteItem = function (fileItem, response, status, headers) {
if (status == 200) {
uploader.clearQueue();
uploader.destroy();
$scope.upload_classes = response.result;
return;
} else if (status == 400) {
warn("提交数据有误:" + response.message);
} else if (status == 401) {
warn("操作失败,因为权限受限")
} else if (status == 403) {
warn(response.message)
} else if (status >= 500) {
warn("服务暂不可用");
} else {
warn("系统异常");
}
$scope.creator_ref.close();
clear_uploader();
};
}]
});
};
}
SpringMVC
SpringMVC上传只是老生常谈的问题, 只是注意几个点就可以
1. pom中添加[commons-fileupload]依赖
2. bean.xml中需要增加 <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver" /> 指定mvc使用multipartResolver
3. 如果需要在上传文件的同时传参, 那么需要在js中加入formData即可, 注意formData是数组类型, API文档写的好! SpringMVC端改动不大
示例代码:
@RequestMapping(value = "import", method = RequestMethod.POST)
@ResponseBody
public JsonResult processUpload(@RequestParam MultipartFile file,
@RequestParam("project_id") Integer projectId,
@RequestParam("pkg_id") Integer pkgId) throws Exception {
ruleService.importRule(file.getInputStream(), projectId, pkgId);
return JsonResult.create();
}
var uploader = $scope.uploader = new FileUploader({
url: 'api/rule/import',
formData: [{
project_id: $scope.project_id,
"pkg_id": $scope.my_tree.get_selected_branch().data.id
}],
queueLimit: 1 //only can add one item
});
uploader.onCompleteItem = function (fileItem, response, status, headers) {
if (status == 200) {
success("导入成功");
$scope.my_tree_handler($scope.my_tree.get_selected_branch());
} else {
warn(response.message);
}
$scope.import_ref.close();
};
$scope.upload = function () {
if (confirm("导入后会覆盖同名规则, 是否导入?")) {
uploader.uploadAll();
}
}
文件下载有几种方式,
比如前端知道文件名的, 可以直接使用window.open(url)方法获取文件进行下载, 这种情况比较少见, 除非是做下载服务器.
根据参数生成文件, 将文件暂存在服务器上, 返回给前端一个文件名或者url, 然后前端通过iframe或者window.open(url)进行文件获取. 个人认为这种方法比较挫, 原因有2, (1)本来能一次干完的事儿分了两步做, (2)服务器的这种临时文件多需要清理, 否则会造成潜在隐患和问题(比如权限, 或者临时目录清理等).
根据参数生成文件, 不生成临时文件, 直接将流写入Response的OutputStream, 如果是get请求则使用window.open(url)获取. 如果是post或者put请求, 那么需要在前端将流进行解析并输出, 这个时候需要使用FileSaver. 这是我最中意的办法, 节约服务器资源, 又比较优雅.
前端
window.open('/api/rule/export?project_id=' + $scope.project_id + "&rule_names=" + $scope.rule_names);//get 请求
//post请求, 使用FileSaver.saveAs(), 直接将文件输出, 文件名从header里面拿
$scope.export = function() {
$http({
url: '/api/rule/export_dt',
method: "POST",
data: $.param({
project_id : $scope.project_id,
content: JSON.stringify($scope.decisionTable.toJson())
}),
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
responseType: 'arraybuffer'
}).success(function (data, status, headers, config) {
var blob = new Blob([data], {type: "application/vnd.ms-excel"});
saveAs(blob, [headers('Content-Disposition').replace(/attachment;fileName=/,"")]);
}).error(function (data, status, headers, config) {
//upload failed
});
};
后端
如果是普通文本, 则注意设置 Content-Disposition ,ContentEncoding, Content-Type, 因为没有临时文件生成, 所以content-length不能进行设置, 使用Transfer-Encoding: chunked 代替(如果两个都设置了, 浏览器会使用Transfer-Encoding).
@RequestMapping(value = "export")
public void export(@RequestParam("project_id") Integer projectId, HttpServletResponse response) throws IOException {
String filename = "rules_" + DateFormatUtils.ISO_DATE_FORMAT.format(new Date()) + ".txt";
response.setCharacterEncoding("utf-8");
response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
response.setHeader("Content-Disposition", "attachment;filename=" + filename);
response.setHeader("Transfer-Encoding", "chunked");
IOUtils.copy(new StringReader(ruleService.export(projectId)), response.getOutputStream(), "utf-8");
}
如果是其他文件格式, 比如excel, 则注意设置ContentType, Content-Disposition, Content-Transfer-Encoding:binary
@RequestMapping(value = "export_dt", method = RequestMethod.POST)
public void exportDecisionTable(@RequestParam("project_id") Integer projectId, @RequestParam("content") String content, HttpServletResponse response) throws IOException {
String fileName = "decisionTable_" + DateFormatUtils.ISO_DATE_FORMAT.format(new Date()) + ".xls";
response.setContentType("application/vnd.ms-excel");
response.setHeader("Content-Disposition", "attachment;fileName=" + fileName);
response.setHeader("Content-Transfer-Encoding", "binary");
ruleService.exportDecisionTable(projectId, content, response.getOutputStream());
}
https://en.wikipedia.org/wiki/Chunked_transfer_encoding
https://en.wikipedia.org/wiki/MIME
]]>http://www.w3.org/Protocols/rfc1341/5_Content-Transfer-Encoding.html
因为这是我们俩的第一次海外自由行, 之前也做了比较详尽的准备, 自认为算是比较成功的. 下面是在日本的五天时间里面的行程, 交通, 食宿的规划方案和心得体会.
根据逗留时长确定每日的大致行程, 我们一共待4天半的时间. 其中大阪三天半, 京都一天
第一天中午降落, 下午计划是稍微逛一下难波, 对日本有一个大概印象, 入住酒店
第二天是扫货时间, 早上起来排队抢baobao(日本本地的一个品牌, 在中韩很火, 还搞饥饿营销每天只卖20-30个…orz), 药妆店买老婆的化妆品(老婆事先就打印出了一长串的购物清单), 电器啊, 水壶啊, HelloKitty啊, 巴拉巴拉
第三天我们计划去京都玩一天, 晚上返回大阪
第四天我们大阪一日游
第五天早上机场附近奥特莱斯逛逛逛, 下午4点飞机回国
心得: 网上找到大阪和京都地图, 大阪旅游局网站上就有, 特别贴心还支持中文. 在地图上将你想去的地点先标出来. 为后续的交通路线规划做准备. 如果是参观景点的话, 最好去旅游局网站上将景点的开关门时间同样标记在地图上, 这也对路线规划有重要作用.
日本的地铁分为市营(国家的), 私营两种. 因为有一些针对游客贩卖的一日(多日)乘车券并不是所有地铁都通用的, 事先做攻略的时候几乎都要晕了. 最后通过大阪旅游局网站上的介绍才勉强搞明白.
心得:
- 大阪: 我们大阪一日游使用的是大阪周游卡(一日), 售价2300日元, 当日私营地铁和市营地铁免费乘坐, 一些热门景点门票也可免. 靠它帮我们省了至少5000日元. 此卡的介绍在大阪旅游局网站上就能找到(真乃业界良心, 回想下中国各地方旅游局, 你们干啥吃的?).
- 大阪的剩下两天时间使用的是ICOCA, 就是一种充值卡, 并不能帮我们省钱, 但是免去了每次乘车买票的麻烦. 卡押金500日元, 可以在市营地铁站内退卡, 退卡如果卡内金额超过xxx, 貌似要交手续费220日元. 故要计划好每次充值的金额, 好像是1000起充. 用不完的话各大便利店基本都支持ICOCA付账, 到时候用不完的话刷一点零食咯
- 京都的话公交系统很发达, 我们京都一日游是全靠公交车进行的, 公交车也有一日券, 500日元. 坐三次就回本啦.
- 怕迷路, 不知道怎么坐车, 怕坐过站? 下载Google Map, 下载Google Map,下载Google Map. 一定要多说几次. GoogleMap简直是自由行必备神器, 实现将计划的地点做标记. 或者现查都可以, 支持中文输入, 全程使用无障碍.
- 随身WIFI建议taobao上购买一个, 我买的8块一天, 还是相当给力的.
大阪有名的好吃的地方基本去网上一搜一大把, 点评上也有大阪专区. 好买好吃好玩的店主要都是集中在心斋桥-道顿崛(大阪市南部), 北部市中心是在梅田-新大阪附近. 个人还是推荐心斋桥步行街, 真的能逛一天…
说了那么多, 下面谈谈我这次自由行之后对日本的认识. 在去之前, 其实心里一直有种小小的抗拒感, 不知是狭隘的民族主义还是啥的, 先入为主的感觉日本人对中国人是不友好的. 但是在日本的这几天完全打消了这个念头, 反而让我有点敬佩日本这个国家的国民素质. 下面说几个日本给我留下最深刻印象的几点:
这让我想到了中国, 垃圾桶基本到处都是, 但是照样脏. 我们的原因还会说是垃圾桶不够, 政府的问题. 但是仔细的思考一下, 这真的完全赖政府么? 我认为还是国民素质问题(这一点还是可以赖一下政府的), 所导致的破窗效应. 而在日本, 国民素质都相对较高, 破窗的几率就会小很多.
相比下来, 感觉目前中国的无障碍设施做的还差的远. 不客气的说就是一个花架子.
日本的隐私保护也是令人发指. 公共场所的一切可供私人用显示屏都是防偷窥的, 地铁售票机, ATM, 就连进站轧机上显示金额的屏幕都是偏光的.
帮助他人时那种热心而又谦卑的态度. 在日本待了这4天问了三次路, 每个被问的人都会亲自领着我去我所询问的地点, 然后确定这是我要找的之后才会离开. 不管他们距离这个位置有多远. 这一点让我十分感动.
回想询问时, 我们国民的态度, 能指出正确的路就算很好的了, 以前还碰到过向相反方向指路的情况. 在中国人与人的信任由此可见一斑.
]]>由此反映出的日本人严谨的处事态度, 也可想而知为何日本制造能风靡全球.
在我的github上我写了几个示例代码, 来说明它的各种用法. 请移步这里
为了防止程序员误用, Unsafe的构造器声明为私有. 并且静态方法Unsafe.getUnsafe()会检查类加载器是否是BootstrapClassLoader, 否则抛出SecurityException
public static Unsafe getUnsafe() {
Class cc = sun.reflect.Reflection.getCallerClass(2);
if (cc.getClassLoader() != null)
throw new SecurityException("Unsafe");
return theUnsafe;
}
- 我们可以指定使用bootstrap来加载我们的class, 但是这样做不方便. java -Xbootclasspath:/usr/jdk1.7.0/jre/lib/rt.jar:
- 我们可以通过Reflecttion获得Unsafe中的私有成员变量theUnsafe
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
Unsafe unsafe = (Unsafe) f.get(null);
=============================================
Unsafe中有105个方法, 我将其中重要的几类列下来:
- addressSize
- pageSize
- allocateInstance
- objectFieldOffset
- staticFieldOffset
- defineClass
- defineAnonymousClass
- ensureClassInitialized
- arrayBaseOffset
- arrayIndexScale
- monitorEnter
- tryMonitorEnter
- monitorExit
- compareAndSwapInt
- putOrderedInt
- allocateMemory
- copyMemory
- freeMemory
- getAddress
- getInt
- putInt
使用allocateInstance(), 可以防止对象初始化. 包括field的初始化和构造器
class A {
private long a; // not initialized value
public A() {
this.a = 1; // initialization
}
public long a() { return this.a; }
}
A o1 = new A(); // constructor
o1.a(); // prints 1
A o2 = A.class.newInstance(); // reflection
o2.a(); // prints 1
A o3 = (A) unsafe.allocateInstance(A.class); // unsafe
o3.a(); // prints 0
使用putInt可以将指定位置的内存进行填充和替换, 与Reflection的区别是: 反射必须指定对象, 而Unsafe只需要指定内存地址
unsafe.putInt(obj, 16 + unsafe.objectFieldOffset(f), 42); // obj对象的大小在32位系统中占16位, 故可以将相邻的obj中字段进行赋值
使用objectFieldOffset可以获得对象大小. (shallow size 并非真实的占用内存大小)
遍历所有非静态变量, 包括父类. 获得每个变量的offset, 找到最大的offset. 并增加padding(内存对齐. 按8位进行对齐)
public static long sizeOf(Object o) {
Unsafe u = getUnsafe();
HashSet<Field> fields = new HashSet<Field>();
Class c = o.getClass();
while (c != Object.class) {
for (Field f : c.getDeclaredFields()) {
if ((f.getModifiers() & Modifier.STATIC) == 0) {
fields.add(f);
}
}
c = c.getSuperclass();
}
// get offset
long maxSize = 0;
for (Field f : fields) {
long offset = u.objectFieldOffset(f);
if (offset > maxSize) {
maxSize = offset;
}
}
return ((maxSize/8) + 1) * 8; // padding, 8位对齐
}
juc中的Atomic, ConcurrentLinkedQueue, ReentrantLock的底层实现都有Unsafe的参与, 主要是使用了两个方法, objectFieldOffset和compareAndSwapObject
Counter counter = new Counter() {
private volatile long counter = 0;
private Unsafe unsafe;
private long offset;
{
unsafe = UnSafeFactory.getInstance();
offset = unsafe.objectFieldOffset(this.getClass().getDeclaredField("counter"));
}
@Override
public void increment() {
long before = counter;
unsafe.getAndAddLong(this, offset, 1L); //jdk1.8优化, 无需loop
// while (!unsafe.compareAndSwapLong(this, offset, before, before + 1)) {
// before = counter;
// }
}
@Override
public long getCounter() {
return counter;
}
};
//以下是从OpenJDK copy来的, 保留行号.
1047 public final long getAndAddLong(Object o, long offset, long delta) {
1048 long v;
1049 do {
1050 v = getLongVolatile(o, offset);
1051 } while (!compareAndSwapLong(o, offset, v, v + delta));
1052 return v;
1053 }
关于cas的参考资料:
]]>瑞士很美, 也很贵. 少女峰的雄伟, 卢瑟恩旖旎的风光, 蒙特勒的西庸古堡, 使我现在回想起来都能沉浸在瑞士的山水当中. 有机会自驾欧洲的时候一定再去少女峰滑个雪.
瑞士物价在欧洲都算是数一数二的, 任何东西都不便宜. 注意, 是任何东西. 一瓶水1-2瑞郎, 三明治5瑞郎, 随便吃个饭50瑞郎. 瑞士最著名的军刀和手表, 手表我不清楚, 但是军刀绝对的不便宜. 一款小号基本款都要20欧左右. 国内X猫X东上面的价格比瑞士还便宜. 我竟然还买了4把…崩溃…
包包这个单独提一下, 老婆在瑞士看中了MK和Longchamp的包. 打九折还能现场退税, 看起来还不错, 但其实价格算下来还是比希腊贵60欧左右.
所以我不建议在瑞士买任何东西, 当然土豪们请随意.
如果跟团领队和导游可能会带你去一些小镇上让你自由活动(购物), 请注意几点:
- 你要买的包包的尺寸, 颜色, 外观情况麻烦一定一定确认清楚. 因为离开一个地方是很难再回去讲理的. 我们就吃了个小亏, 买的Longchamp的包包, 要的是中号, 导购最后装起来的是小号, 因为包包封在袋子里又是折叠的, 再加上我们过于信任国外的导购(马来西亚人和日本人, 不知道欧洲人会不会干这种事情), 就根本没检查. 回国之后才发现. 所幸就损失了差价的10瑞郎左右.
- 买东西考虑清楚, 三思后行. 最好先了解清楚国内价格, 再下手, 而不是盲目跟风
希腊市容比较差. 由于失业率居高不下(40%), 导致街边都是涂鸦. 经常有罢工和示威游行. 个人物品注意保管.
希腊属于欧洲的购物天堂. 物价是欧洲最便宜的, 基本是瑞士的1/2. 所以建议来希腊购物.
圣托里尼岛的邮轮升舱, 这一点单独拿出来讲是有原因的. 因为我们团除了我们和剩下的一对情侣之外, 其他人都被导游建议升舱为卧铺, 这个卧铺房间4人一间上下铺无窗户, 很小, 需要额外每人出20欧. 其实3月底根本就是淡季, 游轮上到处都是座位. 我们坐在船头的沙发, 特别舒服, 还是下午2点多的船, 所以下午的船根本没必要升舱. 晚上开的大家可以酌情考虑, 不过我觉得年轻人也没有太多必要.
希腊机场免税店的香水化妆品很便宜, 还有第二件半价活动. 比日上便宜多了去了. 日上选好不要买单, 回来再说.
希腊基本没有现场退税的, 都是给退税单去海关盖章后选择机场退税/退信用卡/回国退. 强烈建议回国退税.
- 机场退税: 优势是盖完章就能拿到退税的钱, 比较安心. 劣势是退税单每个收取3欧的手续费, 另外加消费金额8%~15%的增值税. 故我们团友原本能退200欧的最终只拿到170欧, 算算有点是亏.
- 信用卡退税: 优势是简单方便, 填个卡号在机场邮寄就能坐等收钱. 劣势是不一定等得到, 而且时间不确定.
- 回国退税: 优势是毫无任何手续费. 退多少拿多少, 可选择实时结算为人民币. 劣势是只有北上广的几家工商银行代理这个业务, 故非北上广不太好办.
游玩: 风光摄影基本卡片机就足够了. 瑞士比较冷, 备一件厚点的外套足够了.
购物: 不要盲目信任营业员, 自己买的东西自己检查清楚, 否则就没机会了.
退税: 北上广选择回国退税, 拿到钱最多. 非北上广的国外机场退税吧. 万一遇到机场退税失败(我们就是机场退税失败, 好像是店铺被block了. 才发现原来回国退税是最好的), 那么就机场邮寄吧.
跟团: 一定要学会独立思考避免盲目跟风, 避免被忽悠. 欧洲领队其实素质都是蛮高的, 人也很好, 但是毕竟人家是有一些业务指标的, 所以必然会引导你购物啊或者其他的. 还是要保持清醒的头脑和独立思考能力.
提供读写锁, 3.0 MMAPv1引擎支持Collection Level的锁, WiredTiger引擎提供document level的锁; 2.2 ~ 3.0 只能提供database级别的锁
读写锁会对其他锁让步. 长时间的读/写操作都会做出让步. MongoDB的启发式算法会判断数据是否在物理内存, 非物理内存的操作会让步于从内存读取的操作, 一旦数据load到内存, 则会重新获得锁.
当查询索引数据时, 无论是否在物理内存中, Mongo不会释放锁.
读写锁的场景
| Operation | Lock Type |
|---|---|
| Issue a query | Read lock |
| Get more data from a cursor | Read lock |
| Insert data | Write lock |
| Remove data | Write lock |
| Update data | Write lock |
| Map-reduce | Read lock and write lock, unless operations are specified as non-atomic. Portions of map-reduce jobs can run concurrently. |
| Create an index | Building an index in the foreground, which is the default, locks the database for extended periods of time. |
Mongo直接使用insert即可, 如未指定, _id作为unique key自动加入表结构中, 也可以单纯建表
db.users.insert( {
user_id: "abc123",
age: 55,
status: "A"
} )
db.createCollection("users")
无强制schema要求, 如果想为之前的数据增减字段, 则可用update. 文档
db.users.update(
{ },
{ $set: { join_date: new Date() } },
{ multi: true }
)
db.users.drop()
相关文档: http://docs.mongodb.org/manual/reference/method/db.collection.createIndex/#additional-information http://docs.mongodb.org/manual/core/indexes-introduction/
db.collection.createIndex(keys, options)
//唯一索引
db.collection.createIndex( { "a.b": 1 }, { unique: true } )
//稀疏索引, 适合存在null的字段, 不会将null字段所在行建立在索引中
//使用sort的时候, 需要使用hint()来使用到稀疏索引
db.addresses.createIndex( { "xmpp_id": 1 }, { sparse: true } )
//复合索引 user_id asc, age desc
db.users.createIndex( { user_id: 1, age: -1 }
db.users.find().sort( { user_id: 1, age: -1 } ) //Ok
db.users.find().sort( { user_id: -1, age: 1 } ) //OK
db.users.find().sort( { user_id: 1, age: 1 } ) //无法使用索引支持这种查询
Mongo本身对SQL中的语义支持较全.
默认查询返回的是前20条记录的游标(可使用DBQuery.shellBatchSize), 10分钟无操作则会关闭.
支持读隔离, snapshot
sort操作没有index的话, server会将全表load到内存中
支持各种聚合操作, mapR, 文档
db.collection.find(<criteria>, <projection>)
db.users.find() //查询全表, 默认值返回前20行的cursor
//查询user_id和status字段, 默认包含_id字段
db.users.find(
{ },
{ user_id: 1, status: 1 }
)
//查询user_id和status字段, 不包含_id字段
db.users.find(
{ },
{ user_id: 1, status: 1, _id: 0 }
)
//where status = A
db.users.find(
{ status: "A" }
)
//where status != A
db.users.find(
{ status: { $ne: "A" } }
)
//status = A and age = 50
db.users.find(
{ status: "A",
age: 50 }
)
//status = A or age = 50
db.users.find(
{ $or: [ { status: "A" } ,
{ age: 50 } ] }
)
//WHERE user_id like "%bc%"
db.users.find( { user_id: /bc/ } )
//WHERE user_id like "bc%"
db.users.find( { user_id: /^bc/ } )
//WHERE status = "A" ORDER BY user_id ASC
db.users.find( { status: "A" } ).sort( { user_id: 1 } )
//select count(*)
db.users.count()
db.users.find().count()
//SELECT COUNT(user_id)
db.users.count( { user_id: { $exists: true } } )
db.users.find( { user_id: { $exists: true } } ).count()
//SELECT DISTINCT(status) FROM users
db.users.distinct( "status" )
//SELECT * FROM users LIMIT 1
db.users.findOne()
db.users.find().limit(1)
//SELECT * FROM users LIMIT 5 SKIP 10
db.users.find().limit(5).skip(10)
//EXPLAIN SELECT * FROM users WHERE status = "A"
db.users.find( { status: "A" } ).explain()
写操作的Concern(ACK): http://docs.mongodb.org/manual/core/write-concern/
读一致性: Mongo可读到其他client未提交/新插入的数据, 不管写入控制和日志配置, Read uncommitted
isolate: MongoDB更新每单行是隔离的, client不会看到中间状态的. 但多行数据操作, Mongo未提供事务和隔离, 在集群中无效
w Option: 表示写通知的数目. 1为默认值: 表示单节点或集群主节点; 0表示: 关闭默认的, 但如果同时开启j, 则可通知; 大于1: 表示除主节点外还有N-1个需要写入成功, 但N-1必须>0, 否则可能陷入死循环; majority: MongoDB自行决定多数写入则成功, 避免hardcode
j Option: 表示Mongo需要写磁盘日志: true为开启, 开启写日志配置到disk, 则可在Mongo宕机重启后, 看到数据. 否则数据重启后可能丢失
wtimeout: 表示写入等待, 超时则返回error
_id字段未指定则使用12byte的BSON类型来保证唯一性
db.collection.insert(
<document or array of documents>,
{
writeConcern: <document>,
ordered: <boolean> //出错后是否还继续插入其余行
}
)
//多行插入
db.products.insert(
[
{ _id: 11, item: "pencil", qty: 50, type: "no.2" },
{ item: "pen", qty: 20 },
{ item: "eraser", qty: 25 }
]
)
更新默认是更新单条记录(自带limit 1), 可设置multi: true来修改多行
如果未查到配置数据, upsert:true则可插入新的记录, 类似insert or update语法, 为防止插入多行数据, 请在使用upsert:true时, 确保查询条件的唯一性
使用默认的写入策略
db.collection.update(
<query>,
<update>,
{
upsert: <boolean>,
multi: <boolean>,
writeConcern: <document>
}
)
//更新多行age>25的将status设置为C
db.users.update(
{ age: { $gt: 25 } },
{ $set: { status: "C" } },
{ multi: true }
)
//更新多行将status=A的age=age+3
db.users.update(
{ status: "A" } ,
{ $inc: { age: 3 } },
{ multi: true }
)
//
默认删除所有的符合条件的行, 如果想删掉单行并排序, 使用findAndModify()
db.bios.remove( { } ) //移除全部行
db.products.remove( { qty: { $gt: 20 } } ) //移除全部大于20的
db.products.remove(
{ qty: { $gt: 20 } },
{ writeConcern: { w: "majority", wtimeout: 5000 } }
)//使用自定义ack
db.products.remove( { qty: { $gt: 20 } }, true )//只移除单行, limit 1
db.products.remove( { qty: { $gt: 20 }, $isolated: 1 } ) //事务隔离, 只在单机有效
工作上:
上半年基本在做一些业务代码的开发, 下半年转做风控平台, 不断的有新知识新技能GET, 感觉棒极了!
- 新会员体系的重构, 将代码开发改为API配置, 更加灵活;
- 对接公安系统身份证验证, 保证数据安全性;
- 设计商品收藏API, 掌握了Redis的使用, 扩容, 分片;
- 旧风控系统的重构, 优化代码;
- 新风控系统设计, 熟悉了Drools, Angular, D3. 也尝试了Zookeeper, Kafka, Storm, Hive, Hbase. 体会了Scala的强大, 也体会了Cloujre的难懂.
…. 总之, 工作上学到的东西太多. 不单单是技能, 也感觉自己在设计和编程方法论上面有了提高, 也越发感觉自己不会的领域更多. 所以努力吧, 少年!
生活上:
上半年还好, 下半年基本上连轴转
- 婚纱照;
- 领证;
- 房子装修;
- 婚礼*2;
- 过户;
…. 生活上2014年也是大事一个接着一个, 回想起来, 当时有段时间工作和生活的压力还是蛮大的. 所幸都顺顺利利的完成了. 还是要多感激我的家人和朋友, 特别是我的妈妈, 14年实在是付出了太多太多. 一帮哥们们也十分给力, 有求必应. 这都是人生中宝贵的财富, 时刻怀揣感恩的心.
读书:
2014年读的书比较的杂, 基本都是在地铁上读的, 从印象深浅排序大致是:
- 黑客与画家
- Java Performance
- 编程珠玑
- 编写可读代码的艺术
- 分布式系统设计
- 深入理解Java虚拟机
- 道德经
- 易经
- 大刘的其他短篇小说
- 伟大的博弈
- 分布式存储
- Netty权威指南
2013年读的现在还记忆犹新的有: REST cookbook, 领域模型设计, 企业架构设计, 重构, 三体*3, Tomcat6高级编程, 白帽子讲安全,
2011-2012年读过的仍历历在目的有: Effective Java, Java Concurrence in Practice, Java 解惑. 这三本对于java程序员来讲基本称得上神书了.
对于已经到来的2015年, 我的目标很简单: Scala, Akka, 业务时间写写swift玩玩, 学习下开源框架里面Netty NIO AIO的用法.
最后, 15年年初读到: **kk的<失控>**, 鼎力推荐, 翻译的相当到位.失控>
]]>