Friday, December 30, 2016

AWS Lambda

Lambda的特点是只要写代码而不需关心provision和manager server。计费方式是按程序运行时间。

使用场景

使用场景为event-driven,也就是需要trigger event的。
1. S3(文件类型存储服务如FTP)中文件到达后进行ETL
2. DynamoDB中某一天数据load进去后进行model计算。此用例类似于一。
3. API Gateway(HTTP)收到request后进行计算。如某TV供应商不想管理server或者与产生event的server不是一个语言,只能通过http json进行沟通。一个更具体的例子是在Alexa创建一个skill时,就要lambda编程,如ask WeatherHelper to get weather,这个HTTP发到我用任何语言(Java, python)编写lambda(叫WeatherHelper),然后在我的lambda里写程序去调用weather.com去获取天气再返回结果。所以lambda其实是事件驱动构件,无编程环境只与语言相关的EC2。
4. 移动程序数据分析:kinesis->lambda (数据转换json->csv)

另一个较著名的使用场景是从外部网站买数据导入到dynamoDB,新数据触发lambda更新elasticSearch的数据。















功能

重点:
事件源和计算(重于与其他服务integration)

事件源
更改 Amazon S3 存储桶或 Amazon DynamoDB 表中的数据
使用 Amazon API Gateway 运行代码以响应 HTTP 请求。
AWS Lambda Invoke API
Amazon S3 始终异步调用 Lambda 函数,Amazon Cognito 始终同步调用 Lambda 函数。对于基于轮询的 AWS 服务(Amazon Kinesis、Amazon DynamoDB、Amazon Simple Queue Service),AWS Lambda 轮询流或消息队列并同步调用您的 Lambda 函数。

限制
运行时间15min,内存3G,所有lambda总并发数1000。异步调用,重试调用两次,且在重试之间有一定的延迟。同步调用由caller决定收到200后是否重试。

并发
您无需担心同步线程或进程的问题。不过,您可以使用异步语言功能并行处理事件批次,并将数据保存到 /tmp 目录以便在同一实例上的未来调用中使用。
亲身经历:用SQS发多个messages来触发多个lambda的实例,lambda可以设置concurrency为2,表示最多有2个instance同时运行,其他未被处理的messages会放入到in flight中。

Coldstart
比较慢,10分钟之内instance如果没有活动,就会被reclaimed,下一次lambda再invoke,就会出现cold start问题,cold start大概6-8秒,log不会看到这个gap。
解决方案用Provisioned Concurrency (Config -> Concurrency),此法还是不需要和canary结合用,一些heavy的资源需要初始化如db connections。此法缺点是alias跟version bind在一起,所以新代码不会被deployed。所以需要手动publish新的version,然后alias不用改(bind to latest)。这些都可以用CDK自动化,需要在CDK的test dependency中加入lambda package,这样lambda的改动会trigger CDK去publish一个新的version。


访问其他资源
AWS资源如S3
非AWS资源
VPC中访问RDS和internet

环境变量
lambda的config

函数版本控制和别名
用于发布,别名指向单个Lambda 函数版本

别名的流量转移
类似于weblab来测试和发布新版本的lambda


lambda中的library,最多5个,所有layer大小不超过250M。

重试
基于轮询的事件源(基于流)DynamoDB,Lambda 尝试处理错误的记录批次,直至数据过期,这最多可以为七天。

私信队列DLQ
将异步执行重试两次后失败的运行发送到SQS/SNS进行原因分析

CloudFormation
用其进行lambda代码的管理和部署

其他AWS服务结合
Cloud Events定时trigger
LB
Alexa
Kinesis/Kinesis Firehose
S3
DDB
SQS/SNS


最佳实践

API gateway->lambda->RDS
为了封装weather.com以及其他企业内部数据,所以用lambda作为一个到weather.com的API gateway。

request如
{
    "operation": "create",
    "tableName": "LambdaTable",
    "payload": {
        "Item": {
            "Id": "1",
            "name": "Bob"
        }
    }
}

Lambda中handler为Lambda函数入口:
exports.handler = function(event, context, callback) {
    var operation = event.operation;
    if (event.tableName) {
        event.payload.TableName = event.tableName;
    }
    switch (operation) {
        case 'create':
            dynamo.put(event.payload, callback);
            break;

配置:

假设lambda要写入数据到RDS,首先配置好RDS,然后需要一个执行lambda的role,这个role要有以下这些权限(IAM中配置):
AmazonRDSFullAccess
AWSLambdaFullAccess
AWSLambdaExecute
AWSLambdaVPCAccessExecutionRole
CloudWatchFullAccess

Handler为文件名.入口函数如abc.lambda_handler

abc.py:
   def lambda_handler(event, context):

另一个Lambda的完整例子:S3->Lambda->dynamoDB


这个例子是实现存取删单词

1. 准备dynamoDB表:叫Words, partition key: CustomerId, sort key: Word
2. 给lambda_basic_execution role的权限:AmazonDynamoDBFullAccess
3. 用js写Lambda,API参考
4. 写Test event

lambda的触发事件:

只要上传一个新的文件到S3就可以触发lambda执行,这个事件叫S3 put

for record in event['Records']:
        bucket = record['s3']['bucket']['name']
        key = record['s3']['object']['key']
        print('bucket='+bucket+',key='+key)

测试lambda时候,可以create一个test event,只需要修改bucket name和key即可
         "object": {
          "key": "folder/test.html",
        },
        "bucket": {
          "name": "bucket-1",

测试完毕后就可以add trigger把bucket=bucket-1以及prefix=folder/ 的put事件作为trigger,这样只要上传新文件到这个文件夹lambda即可运行。


'use strict';

console.log('Loading function');

var AWS = require('aws-sdk');
var DOC = require('dynamodb-doc');
var dynamo = new DOC.DynamoDB();
var docClient = new AWS.DynamoDB.DocumentClient();
exports.handler = function(event, context) {

-------------------------------------------------------------------------------------ADD
    var item = {
                 CustomerId: event.Records[0].user,
                 Word: event.Records[0].word
            };

    var cb = function(err, data) {
        if(err) {
            console.log(err);
            context.fail('unable to update Words at this time');
        } else {
            console.log("Successfully saved a word");
            context.done(null, data);
        }
    };
 
    dynamo.putItem({TableName:"Words", Item:item}, cb);
 
    var cbget = function(err, data) {
      if(err) {
         console.log('error on GetWordsInfo: ',err);
         context.done('Unable to retrieve words information', null);
      } else {
         console.log("Query succeeded: Records returned " + data.Items.length);
         var rand = data.Items[Math.floor(Math.random() * data.Items.length)];
         console.log("Random word: " + rand.Word);
      }
   };
-------------------------------------------------------------------------------------GET

    var params = {
        TableName : "Words",
        KeyConditionExpression: "#cust = :customerid",
        ExpressionAttributeNames:{
            "#cust": "CustomerId"
        },
        ExpressionAttributeValues: {
            ":customerid": event.Records[0].user
        }
    };

   dynamo.query(params, cbget);
 
 
   -------------------------------------------------------------------------------------DELETE
   var cbdelete = function(err, data) {
      if (err) {
           console.log('error on deleting word: ', err);
      } else {
           console.log("Successfully removed a word");
      }
   };
     
   var deleteparams = {
        TableName: "Words",
        Key:{
             "CustomerId": event.Records[0].user,
             "Word": event.Records[0].word
        }
    };
 
    console.log("Attempting a delete...");
    docClient.delete(deleteparams, cbdelete);
 
};

Test Event:
Sample event: S3 put

{
  "Records": [
    {
      "eventSource": "aws:s3",
      "eventID": "110",
      "user": "Jack",
      "word": "back"
   
    }
  ]
}

retry:
lambda的retry取决于caller,若无caller自己会retry,否则caller决定,包括ddb stream (https://docs.aws.amazon.com/lambda/latest/dg/invocation-retries.html)

ref:
官方指南(中文)
webtask
lambda2RDS
官方例子: lambda->RDS
官方例子: S3->lambda

AWS DynamoDB

DynamoDB是AWS产品线之一,它有如下特点:
1. NoSQL
2. schema less设计。也就是每个item(行)的列名不固定可省空间。原理是每item按json存。
3. 只能query(partition)/sort (range key),不能query/sort非主键。除非创建GSI(global secondary index)
4. 计价方式颠覆传统数据库。传统用空间大小计费,而DynamoDB用request峰值计算
5. 存储按partition key来分配存储位置,不用像传统一样做增加空间、shrink、备份等DBA工作
6. 每份数据至少有3个备份

新特性:
1. 无需规划容量read/write capacity,采用按请求付费的定价模式
2. 支持事务await dynamoDb.transactWriteItems
3. Accelerator - dynamoDB的cache


架构

重点:
存储key-value和更改trigger(stream)

核心概念:
Table:与传统DB一样
Item:传统DB的行
Attribute:传统DB的列名
Primary key:是必须的。有两种primary key:一种只用partition key,此时partition key在全表中唯一。另一种是partition key+sort key作为组合主键(如名字+生日唯一确定一个人)。数据时按partition key来存储的,同一partition内,数据按sort key排序。partition key叫hash attribute只能用于相等性比较,而sort key叫range attribute,可以进行相等和大小比较。
Secondary Indexes:额外索引来提高非主键数据读取速度,由于primary key已是索引,所以这里叫额外索引。索引包括全局和局部索引。全局索引针对全表,如studentID(HashKey), courseID(SortKey),  score(IndexHashKey)可以获得某个分数的所有人。局部索引是针对每个partition key的索引,类似于SQL Server的filter index。指定index时还要选include primary key还是其他非key属性或是全部,类似与SQL Server中index的include。但是数据一致性并不能保证。写入新数据,可能需要一些时间才会写到二级索引。
read capacity:默认的read capacity为5,表示每秒只允许5个读操作。

最佳实践
CID, DeviceId+type
EntityType, entityId
如果存epoch time,用Number存
customerid+startPoint作为hash key,endPoint做range Key,再对startPoint做secondary index

底层API:

创建table如下,dynamoDB.getTable("Movies").putItem, getItem, batchGetItem, batchWriteItem, query, scan, delete,这是dynamoDB的API属于low level API。

 AmazonDynamoDBClient client = new AmazonDynamoDBClient()
            .withEndpoint("http://localhost:8000");

 DynamoDB dynamoDB = new DynamoDB(client);

 Table table = dynamoDB.createTable(“Movies”,
                Arrays.asList(
                    new KeySchemaElement("year", KeyType.HASH),  //Partition key
                    new KeySchemaElement("title", KeyType.RANGE)), //Sort key
                    Arrays.asList(
                        new AttributeDefinition("year", ScalarAttributeType.N),
                        new AttributeDefinition("title", ScalarAttributeType.S)),
                    new ProvisionedThroughput(10L, 10L));
            table.waitForActive();

封装层 - Mapper:

AWS SDK
Developer Guide中的例子:

代表一个item:
@DynamoDBTable(tableName="Music")
public class MusicItem {
    private String artist;
    private String songTitle;
    private String albumTitle;
    private int year;

    @DynamoDBHashKey(attributeName="Artist")
    public String getArtist() { return artist;}
    public void setArtist(String artist) {this.artist = artist;}

    @DynamoDBRangeKey(attributeName="SongTitle")
    public String getSongTitle() { return songTitle;}
    public void setSongTitle(String songTitle) {this.songTitle = songTitle;}

    @DynamoDBAttribute(attributeName = "AlbumTitle")
    public String getAlbumTitle() { return albumTitle;}
    public void setAlbumTitle(String albumTitle) {this.artist = albumTitle;}
}

@DynamoDBAttribute不是必须,与lombok连用:

@DynamoDBTable(tableName="Music")
@Data
public class MusicItem {
     @DynamoDBHashKey(attributeName="Artist")
      private String artist;
      private String albumTitle;

      @DynamoDBVersionAttribute
      private Integer version;
}

根据primary key读取一个item:
        AmazonDynamoDBClient client = new AmazonDynamoDBClient();
        DynamoDBMapper mapper = new DynamoDBMapper(client);
     
        MusicItem keySchema = new MusicItem();
        keySchema.setArtist("No One You Know");
        keySchema.setSongTitle("Call Me Today");
        MusicItem result = mapper.load(keySchema);

SDK中核心类为DynamoDBMapper作为持久层的接口来操作DynamoDB,作用也类似于ORM或Hibernate作为Table和class的桥梁。DynamoDBMapper只能用于某一个table的item的创建、读、更新、删除,若要对table进行操作就要用low level API。
AmazonDynamoDBClient client = new AmazonDynamoDBClient(new ProfileCredentialsProvider());
DynamoDBMapper mapper = new DynamoDBMapper(client);
CatalogItem item = new CatalogItem();
mapper.save(item);    

query第一个参数是结果类,第二参数为query条件也是用结果类封装:
CatalogItem partitionKey = new CatalogItem();
partitionKey.setId(102);
DynamoDBQueryExpression<CatalogItem> queryExpression = new DynamoDBQueryExpression<CatalogItem>()
    .withHashKeyValues(partitionKey);
List<CatalogItem> itemList = mapper.query(CatalogItem.class, queryExpression);
for (int i = 0; i < itemList.size(); i++) {
    System.out.println(itemList.get(i).getTitle());
    System.out.println(itemList.get(i).getBookAuthors());
}

mapper的方法包括save, batchSave, load, query, delete
非DynamoDBMapper方法去读数据

Index:
DynamoDBQueryExpression<TableClass> expression = new DynamoDBQueryExpression<TableClass>()
        .withIndexName("index-name")
        .withConsistentRead(false)
        .withHashKeyValues(tableObject)

功能


操作:
GUI:创建表格、增加item都可以通过AWS界面完成

Table区域:
table按区域,也就是east-2有table,west-1就不会有。

Auto scaling:
30min才会生效。

GSI/LSI:
GSI是任何两个属性,而LSI的分区键与原分区键一致,sort key不同。GSI是不保证强一致性,LSI则可以。

乐观锁:
指定一个属性作为版本号。如果保存,版本号不一致,更新失败。用意在于确保,更新的改动不会被其他transaction覆盖。具体实现是客户端版本号+1,输入后端,若match DDB中版本号+1,就成功更新,返回整个item包括新版本号。否则不成功。

PutItem:
覆盖现有item。但可以用条件先判断是否存在。

TTL:
每个item的生存时间(time to live),dynamoDB会48小时内删除过期的项目。用户修改table属性指定TTL的attribute,然后写入item时候用户指定过期时间如currentTime+5 days. 

DDB streams:
用例有DB不同区域sync,DDB数据分析,新用户通知。一个stream是一个改动,只能保留24小时。Kinesis是DDB streams客户端适配器。
DDB stream->lambda->SNS->notification

DDB stream默认设置
Retry = -1 无限重试 (这个retry控制lambda的retry,lambda本身的retry不起作用,所以lambda设的DLQ也没用)
Maximum age of event = -1 无时间限制,event永远留在Stream
https://enrico-portolan.medium.com/how-aws-lambda-retry-really-works-fb823e78b4a1

全局表:
跨区的相同表结构,不保证强一致性,只保证最终一致性。

静态加密:
加密敏感数据。

事务:
仅支持10个项目

DAX:
Cache层,若需要强一致性,不推荐使用。从10毫秒降到微秒。

QueryFilter
用于DAO层的实现如contains, id=(:id).

On-demand mode
provisioned会让request throttled,即使设置了auto-scaling也会有delay。但on-demand就自动30分钟增加两倍,对于6000以下RPU的,就可以设置这个。因为初始为6000。 

最佳实践:
多对多关系:同一个表中,用自join存储。

Export/Import:
用data pipeline来export到S3. 不要设onTerminate的时间,因为大概要过10分钟才会真正开始,若设置了时间过短,就不会开始。

常见问题

resource not found exception
由于throttling,提高read capacity可以解决


AWS CLI:

shorthand syntax:
aws dynamodb create-table --table-name MusicCollection --attribute-definitions AttributeName=Artist,AttributeType=S AttributeName=SongTitle,AttributeType=S --key-schema AttributeName=Artist,KeyType=HASH AttributeName=SongTitle,KeyType=RANGE --provisioned-throughput ReadCapacityUnits=5,WriteCapacityUnits=5

CLI比较方便,但文档不足,也可以用于其他AWS产品如S3。或者backup到S3再import


Ref:
官方指南(中文)
DynamoDB深度体验
DynamoDB使用经验

Sunday, December 18, 2016

Mockito简介

Mockito是基于JUnit,可以先学习一下JUnit。原则是依赖什么,模拟什么
Mockito可以验证
1. 值是否相等(assertEquals)
2. 方法调用几次(verify)
3. 方法调用参数是否一致(eq/ArgumentCaptor)
4. 方法调用是否按顺序

Stub

比如一个FTP程序,要测试如果断开连接是否会log错误信息Disconnected from FTP。这里断开连接是测试前提(或叫条件)是Stub,会否log错误信息是测试对象(或叫目标)。when里面只能是mock对象,不能是真实对象。when(getName(anyString(), "abc")这是不对,因为参数如果一个是matcher,其他就一定要matcher(eq, any...), i.e. eq("ewf", any())

FTPClient mockedFTP = mock(FTPClient.class);
或者
@Mock
FTPClient mockedFTP

//Stub “无法连接”
when(mockedFTP.isConnected())).thenReturn(false);
when可以任意值如when(foo.add(lt(5), eq(8)))第一参数任意小于5的值及第二参数=8时返回。。
但thenReturn一定要具体指不能用any(Test.class)

//开始测试,真实调用
test.isFTPConnected();

//检查结果
assert(true, Logger.endWith("Disconnected from FTP"));

这样的测试看似显而易见,由代码一看就知道是正确,但为了确保后续维护或者refactor仍能保持这个逻辑(需求),有必要写这样的测试。

Mockito不支持连续mock,如when(mockedFTP.getConn().isConnected).thenReturn(true); 此情况可以逐个mock来做接力
@Mock Connect conn;
when(conn.isConnected).thenReturn(true);

any用于假设,any(class)用于verify
when(mockClass.get(any())).thenReturn();
verify(dynamoDao).put(any(List.class)

API:
doReturn
doNothing
doThrow

Exception的测试
  @Test(expected=ArithmeticException.class) public void testException2(){ doThrow(ArithmeticException.class).when(customer).getCustomerId(); // Act underTest.buildAnimal("tiger", 4); }

InOrder的测试
声明mock的预期调用顺序,verify再验证
// Arrange
InOrder inOrder = inOrder(customer, myJsonHelper);

// Act
underTest.callInOrder();

// Assert
inOrder.verify(customer).setCustomerId(Mockito.anyInt());
inOrder.verify(myJsonHelper).toObject();

Verify

Mockito包括stub, verify, spy三种API。Verify是检查mock对象的某一个函数+参数这个组合(参数不同视为不同调用)的调用次数。使用场合:如果测试对象不返回结果或结果极为复杂就用此校验法。

//真实调用
mockedList.add("one");
//验证add("one")这个调用是否一次
verify(mockedList).add("one");

另一个例子:
request.timestamp=1483685164
device.timestamp=1483685164
//真实调用
updateTimestampActivity.update(request);
//验证add("one")这个调用是否一次
verify(dynamoDao).put(devices);
verify(dynamoDao, times(0)).put(any(Device.class));

Main.java:
update(request){
    request.timestamp=Date.now();
    Devices devices = convertToObject(request);
    dynamoDB.put(devices);
}
这个例子中verify对象时dynamoDao的put方法以及devices参数,由于devices在真实调用中被修改成当前时间与1483685164不一致,所以会报错:参数不一样。代码要更改成verify(dynamoDao).put(any(List.class));表示匹配任何List参数也就是不验证参数。
Matchers.any(Test.class)
Matchers.anyListOf(Test.class)

参数捕捉:

public void test(){
    //act
     underTest.exec();

    //assert
     verify(math).pow(baseCaptor.capture(), exponenetCaptor.capture());
     Integer base = baseCaptor.getValue();
     Float exponenet = exponenetCaptor.getValue();

     assertEqual(2, base);
   
}

@Captor
ArgumentCaptor<Integer> baseCaptor;

@Captor
ArgumentCaptor<Float> exponenetCaptor;

@Mock
Math math;

@InjectMocks
Test underTest;

捕捉参数还可以用eq来代替,如verify(math).pow(eq(2), eq(1.3));等价于verify(math).pow(2,1,3)当prmitive类型时候。如果参数为复合类型,eq会调用equals去做匹配,如eq(student),而不加eq时候会比较指针。

Spy

spy是修改部分测试对象的部分API以满足测试条件,此法慎用。
假设UnderTest有两个函数getProfile(), getName(),而getProfile()内部调用getName(), 此时getName若用when().thenReturn做mock会报错因为getName()也是UnderTest的一个函数。这是可以用spy来修改UnderTest的API来满足。@InjectMocks和@Spy可以连用。例如:

StudentTest:

public void test(){
    //因为student是spy,所以student.get(Name)可以假设
    doReturn("Gary").when(student).getName();
}

@InjectMocks
@Spy
Student student;


Mockito用于有dependency情况下测试,JUnit是无条件下测试,两者结合使用。

Annotation:

annotation显得代码更简洁
annotation mock 功能
@RunWith(MockitoJUnitRunner.class) MockitoAnnotations.initMocks(this); 初始化
@Mock Mockito.mock(ArrayList.class) 自动产生实例,用于假设或叫依赖。如果mock类成员一定要与@InjectMocks连用,否则时null不自动产生实例
@Captor ArgumentCaptor.forClass(String.class) 参数捕捉
@InjectMocks MyDictionary dic; 自动产生实例。需要测试的obj也叫UnderTest,而它的域成员可以mock,用来测试每个函数

class Student {
     String name;
     public void exam(Dialog dialog){}
}

StudentTest:

@Mock
String name;

@InjectMocks
Student student;

@Mock
Dialog dialog;

这里dialog是独立的,即使没有InjectMocks也不影响。但name依赖于student的存在。

另一个例子:

@Test
public void test_BuildAnimal(){
// Arrange
when(customer.getCustomerId()).thenReturn(0);

// Act
Animal animal = underTest.buildAnimal("tiger", 4);

// Assert
assertEquals("0:tiger", animal.drivedName());

}

@InjectMocks
VideoSpeechlet underTest;

@Mock
Customer customer;

Mockito有一定局限性,解决方案是PowerMock

mock contructor:
https://lkrnac.net/blog/2014/01/mock-constructor/

EqualsBuilder
排除某些fields如id
assertTrue(EqualsBuilder.reflectionEquals(student, student2, new String[]{"id"}))
assertTrue(EqualsBuilder.reflectionEquals(student, student2, "id"))

Ref:
理论:
http://blog.csdn.net/zhangxin09/article/details/42422643
API:
http://blog.csdn.net/onlyqi/article/details/6544989
实例:
http://blog.csdn.net/onlyqi/article/details/6546589
JUnit:
http://blog.csdn.net/zhangxin09/article/details/42418441



Wednesday, October 12, 2016

Hexo + GitHub Pages to build blog by Node.js

my github website: https://shineboy2013.github.io/

<code>O(_n_<sup>2</sup>)</code>
句末加两个空格表示换行
&ast;表示*
&times;表示乘号
<br>
---表示分隔线
<pre>有时不work,要改成div,重启,再改为pre即可


写新的blog,可以直接写,不用重启server(hexo s)但若要checkin时需要重新启动

安装:
这里https://xuanwo.org/2015/03/26/hexo-intor/解释很详细了,但有几点要补充:

1. download latest hexo,旧的版本安装失败
2. 要建GitHub page,github的subdomain要与Github的用户名一致。
3. 注意cd到hexo dir to run hexo g
4. git d有问题ssh解决不了,所以用TurquoiseGit check in

Theme:
我用的theme为https://hmybmny.com/hexo-theme-concise/
主题可以通过github下载(可以直接下载,解压,放入themes文件夹),然后修改_config.yml改theme名字即可,具体见上面link

Plugin:
npm install hexo-generator-feed --save
npm install hexo-generator-sitemap --save
npm install hexo-math --save
npm install hexo-generator-archive --save

=>atom.xml/sitemap.xml

write an article:
Software\Hexo\source\_posts写一个MD文件
HTML to markdown: https://www.browserling.com/tools/html-to-markdown
Online Markdown Editor: https://www.zybuluo.com/mdeditor

Generate files to localhost:
cd C:\Users\KK\Documents\MyFiles\Coding\Hexo Server
hexo clean
hexo g
hexo s

http://localhost:4000/

Deploy to Prod:
网站文件产生在Software\Hexo\public的文件夹,然后将其所有文件复制到public-git,这样做因为hexo clean会把.git的文件也去掉。
push public-git to https://github.com/shineboy2013/ by TurtoiseGit
同时备份到Coding\JS\Hexo\source\_posts


Markdown:
https://hexo.io/docs
http://syxiaqj.github.io/2014/02/23/write-blog-with-markdown/
how to use tags

Styling:
可以自己改主题的styling

比如更改table styling,在\Hexo\themes\hexo-theme-concise\source\css\_partial\article.styl
table{ border-collapse: collapse; width: 100%; } .highlight table{ border-collapse: collapse; width: 0% !important; }

th, td {
text-align: left;
padding: 8px;
}

tr:nth-child(even){background-color: #f2f2f2}

th {
background-color: #4CAF50;
color: white;
}


比如更改tag cloud styling,在\Hexo\themes\hexo-theme-concise\source\css\_partial\sidebar.styl
覆盖inline style
   .tagcloud a {
 font-size: 20px !important;
   }

Archiving:
然后在站点的 _config.yml 中设置,例如:
index_generator:
  per_page: 5

archive_generator:
  per_page: 0
  yearly: true
  monthly: true

tag_generator:
  per_page: 10

加广告栏:
[concise]
在theme上加入自己的script:
1. 在Hexo Server\themes\hexo-theme-concise\layout\donation加入donation folder
2. 加入sidebar.ejs文件
<div style="color:blue;text-align:center;margin-bottom:10px">
    <h4>Donote me at Venmo</h4>
<img alt="" border="0" src="/images/static/Venmo_QR_Code.png" width="131" height="160" />
</div>
3. 修改Hexo Server\themes\hexo-theme-concise\layout\layout.ejs加入步骤2的script
  <%- partial('donation/sidebar') %>

[next]
2. reward and reward_settings in _config.yml
3. themes\next\source\images\static\ad.jpg
    themes\next\layout\_layout.swig
  
  <a href="mailto:metajobreferral@gmail.com?subject=Mail from Github">
   <img alt="Free mock interview" border="0" src="/images/static/ad.jpg" width="270" />
  </a>

加网站数据:
<script type="text/javascript" src="//rf.revolvermaps.com/0/0/8.js?</script>

加favicon:
[concise]
1. 在\themes\hexo-theme-concise加入favicon: /favicon.png
2. 在\themes\hexo-theme-concise\source文件夹中加入图片(路径相对于source)
[next]
themes\next\source\images\favicon-32x32-next.png (favicon in _config.yml)

How to add tags page
[next] https://theme-next.js.org/docs/theme-settings/custom-pages

加评论:
https://shineboy2013.disqus.com/admin/
hexo-theme-concise不支持disqus,所以换了nexT主题

theme_config:
  disqus:
    enable: true
    shortname: xxx
    count: true
    lazyload: false


加Search:
https://theme-next.js.org/docs/third-party-services/search-services

Bugnet, open source bug tracking system for .Net

Sunday, September 4, 2016

PowerShell

当文件很大时,可以用PowerShell来查看
看某一行(跳过N行看之后的M行):
get-content a.csv | select -first 1 -skip 90000

Select-String -Path a.csv -SimpleMatch "12345" | select filename, linenumber

Saturday, July 9, 2016

ASP MVC

Bootstrap: js/css framework
Razor: front-end framework like struts
@Html.DropDownListFor

Code First Data Annotations:
Model->database framework
key如果自定义的话不是identity的话就要用[DatabaseGenerated(DatabaseGenerationOption.None)]
其他validation

Identity:
[DatabaseGenerated(DatabaseGenerationOption.Identity)]
需要manually加入[Id] Int Identity(1,1)

NuGet push changes to database:
Update-Database -Verbose -Force

Route:
http://www.codeproject.com/Articles/641783/Customizing-Routes-in-ASP-NET-MVC








Controller:
Delete non-required fields here








Pirce price = db.Prices.ToList().Where(row=>rowId == priceid).First();
return RedirectToAction("Select", "Manufacturers", new {id=1});

Model:
Add caption







View:



@Url.Action既可用于view也可用于controller不要hardcode url尽量用这个。
@Html.ActionLink("Add another price","Select","Manufacturers", new {id=1},null);
window.location.href='@Url.Action("Create")'+'/'+'@ViewBag.id';

@using (Html.BeginForm(null,null,FormMethod.Post, new{id="myForm",@onSubmit="return validate();"}))
@Html.HiddenFor(model=>model.Id)

Publish website in Windows:
1. publish website to local(right click proj in VS2015)
2. copy all files to production iis dir
3. Restart Default web site


https://www.youtube.com/watch?v=zWFoZb6EiwU&list=PLkhqom1HfPQrtTsUzSZk2Xvs2_F-CGTDq&index=1


select2是一个很好的搜索型多选和单选插件。
datepicker也是日历选择器首选。
$('#radio').select2().val(['700','750']).trigger('change');

Friday, June 3, 2016

resharper在VS中的配置

Tools->Options

我的设置是
Shift+Alt+L->Locate a file
Shift+Alt+F->Find a file
Alt+left arrow->navigate backward
Alt+right arrow->navigate forward

















Thursday, April 14, 2016

C#之UnitTest

C# UnitTest在VS 2010后有自带框架,但2008和2005最好的选择是NUnit。

具体操作是
1.新建一个library project
2.下载最新版本,然后添加相应版本(.Net 2.0, 3.0)的DLL到项目
3.写测试代码
4.右击项目run tests即可








代码的attribute:
TestFixture for test class
Setup for initialization, not for test cases
Test for each test case
TearDown for clean up after tests like delete test records in database


C#之泛型编程generic programing


泛型编程是对某些共同操作抽象出来

















泛型和反射的区别是泛型编程是对未知类型进行模板式的操作,并不涉及类本身的操作,比如T obj, list.add(obj). 而反射则可以对类本身的属性和函数进行操作,比如GetType.setValue.


Saturday, March 26, 2016

Hibernate简介



http://blog.csdn.net/liujiahan629629/article/details/21442607


Lazy Loading:就是当load一个parent时候并不load其children当其后需要到children的信息再load它lazy loading

session是打开一个query窗口,是一级缓存(查询结果存在内存),只有session close才会释放缓存
sessionFactory是存所有session,是二级缓存(查询结果存在内存)

hibernate.cfg.xml是config hibernate
*.hbm.xml是每一个class和table的映射config
xml可以写程序动态加入xml string,不一定需要预先有xml文件

query.list()对数据全取
query.iterate()对数据全取Id,但需要到其他fields才取所以是N+1 query(N+1问题)

为了适应其他不同需求hibernate还提供HQL[不定参数查询](session.createQuery("FROM Student s where  s.sname like ?"))和QBC(cra.add(Restrictions.like("sname", "%s%")))语言

Hibernate包括悲观锁(锁一直加上,性能较低)和乐观锁(数据读写冲突时才加上)

hibernate.cfg.xml:
 <propertynamepropertyname="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</property>支持list一级与二级缓存

User.hbm.xml:
  <id name="pid" column="pid">
            <generator class="identity"/>
        </id>
id是Hibernate给的主键,跟table的主键没有任何关系,给了id以后可以调用函数session.get(id). 如果不写generator,就是用用户给的值,如果给出identity就是用自增值。

NHibernate
NHibernate official

我工作的公司用到了NHibernate,设计者将session设计为singleton导致了一个大型程序越跑越慢,原因session一直在缓存越来越多查询。解决方法1. 每个查询new一个session不要singleton。2. sessionFactory.OpenStatelessSession()(.Net中)

Sunday, March 20, 2016

NumPy简介

X[:, 0]表示所有行第一列。第一个参数表示行,第二个参数表示列

Python indexing

http://blog.csdn.net/mokeding/article/details/17476979

朴素贝叶斯分类器Native Bayes classifier之Python实现

这篇博文主要是讨论用scikit-learn这个包里面的Native Bayes(NB)算法去对数据进行分类。NB主要有3种模型:高斯、多项式、伯努利。下面主要讨论高斯。这个算法有几个主要函数:fit函数是用训练数据特征值X和输出目标Y训练模型fit(X, Y),predict函数是根据测试数据特征值计算出输出目标Y2=predict(X2)

第一个例子是scikit-learn官网,数据是假设的

import numpy as np
X = np.array([[-1, -1], [-2, -1], [-3, -2], [1, 1], [2, 1], [3, 2]])
Y = np.array([1, 1, 1, 2, 2, 2])
from sklearn.naive_bayes import GaussianNB
clf = GaussianNB()
clf.fit(X, Y)
print(clf.predict([[-0.8, -1]]))

第二个例子也是来自于scikit-learn官网数据是鸢尾花的分类,数据总共150个,每种花有50个数据,总共有3种类型。程序用全部数据用作训练,再用全部数据用于测试,然后比较结果。运行后准确率达到96%.

from sklearn import datasets
iris = datasets.load_iris()
print(iris.data)
print(iris.target)
from sklearn.naive_bayes import GaussianNB
gnb = GaussianNB()
y_pred = gnb.fit(iris.data, iris.target).predict(iris.data)
print("Number of mislabeled points out of a total %d points : %d" % (iris.data.shape[0],(iris.target != y_pred).sum()))

第三个例子是经过我修改第二个例子而成,我用60%作为training, 40%用作testing,最后得到test data的准确率达到96.7%.

import numpy as np
from sklearn import datasets
iris = datasets.load_iris()
train = np.vstack((iris.data[:30], iris.data[50:80], iris.data[100:130]))
test = np.vstack((iris.data[30:50], iris.data[80:100], iris.data[130:]))
trainTarget = np.hstack((iris.target[:30], iris.target[50:80], iris.target[100:130]))
testTarget = np.hstack((iris.target[30:50], iris.target[80:100], iris.target[130:]))
print(train.shape[0])
print(test.shape[0])
print(trainTarget.shape[0])
print(testTarget.shape[0])
from sklearn.naive_bayes import GaussianNB
gnb = GaussianNB()
y_pred = gnb.fit(train, trainTarget).predict(test)
print("Number of mislabeled points out of a total %d points : %d" % (test.shape[0],(testTarget != y_pred).sum()))

第四个例子改用train_test_split函数分割training and testing data
from sklearn import datasets
iris = datasets.load_iris()
X = iris.data
y = iris.target
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=.4)
gnb = GaussianNB()
y_pred = gnb.fit(X_train, y_train).predict(X_test)
score = gnb.score(X_test, y_test)
print("Number of mislabeled points out of a total %d points : %d, score: %f" % (X_test.shape[0],(y_test != y_pred).sum(),score))

Saturday, March 19, 2016

Git简介

Git和SNV最大的区别是Git多一个repository在local,这样如果check-in到prod的话可以现在local做好code review再push,还可以对自己的各个改动做备份(local SVN本地版本管理)。


工作流程


git pull
git checkout -b issue 1
git commit
git rebase -i HEAD~2
git pull (in mainline)
git rebase mainline (in issue1)
git merge issue1 (in mainline)
git push
git branch -d issue1

开始一个项目/bug时,都要习惯做git pull,否则可能会出现unexpected逻辑问题由于别人代码没有进入

Git commnds:

git fetch origin remoteBranch
git checkout remoteBranch
下载remote端的分支(非mainline)

git stach save
git stach pop
暂存改动,使得没有unstaged文件
恢复暂存的改动
git pull开始一个issue时,都要先做此步
git log查看本分支commit历史记录(从新到老排序)
branchgit branch显示所有分支
branchgit branch issue1创建issue1的分支,与mainline基点一致
branchgit checkout -b issue1创建且切换到issue1分支
git status查看在哪个分支,与origin/mainline(remote端)比ahead了几个commit,文件状态(增改删)。Commit后不显示文件状态
commitgit diff (test.java)
git diff abcd fghi
git diff -z --name-only abcd fghi
查看所有/单个文件与上次commit的不同,git add后不会显示diff。还可以比较两个版本所有代码的异同。最后可以只查看修改的文件名
commitgit checkout abcd Test.javarevert一个unstaged文件,abcd是commit id从git log可得
commitgit reset --hard abcdrevert所有文件(unstaged/staged),abcd是commit id从git log可得
commitgit add (test.java/*.java)commit前加入到index,任何添加修改都需要git add。之后gitstatus这些文件由红变绿(staged)
commitgit reset (test.java/*.java)git add的逆操作
commitgit reset --hard撤销git add的所有文件
commitgit clean -dfx删除新加且unstaged的所有文件
commitgit commit -m "add a log"
git commit --amend
执行前先做git status确保分支和修改文件正确。amend修改log最近提交的信息。
commitgit reset --soft HEAD~撤掉git commit,文件仍在git add后状态(staged)
commitgit reset HEAD~撤掉一个git commit(多个用HEAD~n),文件仍在git add前状态(unstaged)
commitpost-review --parent mainline -r 12345提交review,-r可以保证多次commit仍然是同一个code review。如果是mainline可以post-review -r
rebasegit rebase -i HEAD~3
shift zz to save
先在分支合并commit,3表示合并3个commit。如果在mainline执行这个操作,git将会把分支上各个commit合并到分支,可能需要resolve每个commit,非常没必要。
合并多个commit为一个,且汇总commit信息。把除第一行的pick改为s(squash)变成一个commit代码,下一页修改commit信息,shift+D删除全行。命令可加上issue1表示对此分支修改commit。若在主干,则执行
git rebase -i
pullgit checkout mainline切换到mainline更新最新代码
pullgit pull从remote端下载最新代码到mainline
rebasegit checkout issue1先切换到issue1,再执行rebase
rebasegit rebase mainline先切换到issue1,再执行rebase。如果出现error,就到有冲突的文件解决冲突
rebasegit add Test.javarebase后再add存在冲突的文件
rebasegit rebase --continuerebase状态下的git commit
rebasegit log此时会见到remote端的改动出现在log中
mergegit checkout mainline转换分支mainline或issue1
mergegit merge issue1先切换到mainline,把issue1分支并入到mainline
pushgit pushpush前要先build一下。push到remote端
pushgit branch -d issue1push后删除,不要重用
git cherry-pick abcd从某个commit引入到该分支,因id在各分支中唯一
git revert abcd撤销某个commit,会在commit msg加revert。cherry-pick的反操作,cherry-pick是引入新的commit,而revert是取消目前分支的某个commit。
git blame Test.java打印某一个文件的历史commit,可以快速找到引致错误文件的commit然后git revert
git stash save将未commit的临时改动存入stash,以方便revert而不丢失代码
git stash pop将stash的改动恢复到workspace

这个表格覆盖了一个code改动的流程,包括branch、commit、pull、rebase、merge、push六个大步骤。绿色是最常用命令,黄色是对应上行的逆操作。staging时候分支代码都一样,只有commit以后代码才会不同。

其他命令:

bisect:二分法(端点为good和bad)找到导致错误的commit,需要手动标记此版本好还是坏,git决定在哪个区间继续搜。


Rebase:

Git可以在local repository创建多个分支。mainline是server的分支。比较好的操作是创建一个分支用于修改自己的改动,再merge到mainline。
git checkout -b issue3
git commit -m ""
git checkout mainline
git merge issue3
git push
这个实践很好,如果这段过程中mainline没有push的话,也仍是一条直线。但如果有commit0时候,自己的分支有两个commit(由于code review中不断改动产生多次commit),merge过程就产生一个merge point(不是任何一个checkin point,issue3这个分支push了)。最大缺点是issue3的历史记录不在mainline上不好查看而且不能删除。












引入rebase既可以合并多次commit成一个还可以只出现主干。rebase就是在commit和merge之间加入一步,将最新的remote端代码更新到分支,也叫分支的base改到最新了,所以叫rebase。rebase时候,git会将mainline的改动按步骤一个commit by一个commit将merge到分支。resolve conflict过程会看到分支的最新代码没有出现是因为working on最初的commit,所以只要管好conflict即可,不用理其他代码。







gitignore:

.gitignore文件加入如下:可忽略这些文件永远不会checkin
*.pyc
.idea/

git-config

git config --global commit.template ~/a.txt 
git commit (not git commit -m "")
vim ~/.gitconfig to change name


TortoiseGit

利用bitBucket作为remote repository. 下载TortoiseGit作为client, TortoiseGit需要下载Git.exe

然后右击Git Clone(=SVN checkout) ,如果这个选项没出现就按着shift键才右击即可,然后输入bitbucket中的clone url,文件夹一定要为空。然后创建一个文件,就要commit并且push. commit表示代码写入local repository, push表示写入到remote server




ref: https://www.youtube.com/watch?v=YC6zraqIooM

每次push都输入git url


























Friday, March 18, 2016

TeamCity简介

https://www.youtube.com/watch?v=td0bV2zu5rw
https://www.youtube.com/watch?v=w8tvDSCN2AE

http://www.cnblogs.com/WilsonWu/archive/2013/05/22/3092584.html


Run as a System account

Tuesday, March 8, 2016

Apriori



C set: candidate set
L set: large-item set
C set->L set
frequent set=L set


Python程序思路:
L2->C3
union的所有组合: ABC, ACBE, ACE, BCE
首先个数要为3个,所以ACBE去掉只剩下三组就是C3



















但是据权威参考资料,C3直接是BCE, 因为C3中的任两两元素都应为large(L2里面), 所以ABC, ACE直接不能进入C3
















































如果计算关联规则:
Python 程序思路:
所有L set包括其support数值存于dict中如A 0.5, B 0.75....BCE 0.5
然后对每一个frequent item=L set做如下:
frequent item的support值/该frequent item所有子集(不包括自己)如BCE就是B,C,E,BC,BE,CE,BCE,如果该子集的除法满足Min confidence,
规则为该子集=>frequent item-该子集。如
P(BCE/B) or count(BCE)/count(B)=0.5/0.75=0.67>0.6(min confidence). 就有B=>CE
P(BCE/C)=0.5/0.75 就有C=>BE


原理:(网球拍例子)确定了frequent set(BCE)之后,这就是确定了support也就是confidence置信度(条件概率)中的分子部分
P([BCE]/P(X))=0.6(min confidence)



http://blog.csdn.net/fufengrui/article/details/16342425

https://www.zhihu.com/question/22590018

http://blog.csdn.net/wyc09/article/details/16860923

Python简介


语法:
literals包括set literals, directory literals, list literals
大括号包括set/directory
a={}
a={'a','b'}
a={'a': 42,'b':81}

中括号表示list
alist = ['foo', 'bar', 'bar']
empty_list = []

开发环境:
可以用eclipse进行开发
IPython

安装包:
numpy: array 类(实现matrix类的基础)-1.10.4
scipy:数值计算。做矩阵计算,算特征值,算积分,算微分方程。用pip安装的时候一般出现缺少C++包的问题,解决方案请看WinPython
scikit-learn包括朴素贝叶斯
pandas读数据非常快
Matplotlib

方法一WinPython:这个软件包括Ipython, Pandas, Scikit-learn(自然包括numpy, scipy), Python GUI. 或者导入Eclipse,这样在Eclipse也可以用scipy这些包

方法二pip:

python 3.3.以上pip(8.1.1)自带,不需安装只需运行如下:

python -m pip install numpy
python -m pip install scipy










测试:
from numpy import *
from scipy import *
from sklearn import svm

还有一些常用的工具
Orange data mining

Unable to find vcvarsall.bat
  1. python -m pip install -U pip
  2. pip install -U setuptools
  3. pip install -U virtualenv

Sunday, March 6, 2016

C#之OOD

http://blog.csdn.net/hliq5399/article/details/6305335

http://www.cnblogs.com/max007super/archive/2009/02/09/1386980.html

http://www.cnblogs.com/weihai2003/archive/2008/10/24/1319003.html

http://stackoverflow.com/questions/1327544/what-is-the-equivalent-of-javas-final-in-c/1327549#1327549

http://www.cnblogs.com/chenssy/p/3388487.html

Wednesday, February 24, 2016

编程规范Coding standard

编程规范文字版:包含Comments, Environment, Functions, General, Java, Names, Tests,详细书籍Clean Code的285-314页。

Tool:
http://stackoverflow.com/questions/38635/what-static-analysis-tools-are-available-for-c

FxCop for c#:
http://www.cnblogs.com/zhuqil/archive/2010/12/07/FxCop.html
http://www.codeproject.com/Articles/30666/Steps-to-Write-Your-Own-Custom-Rule-using-FXCOP

base("clsCheck", "MyRules.connection", typeof(clsCheck).Assembly)
clsCheck: class name
MyRules.connection: (proj name).(xml file name)

Add MyRules DLL to FxCop














Now compile JustForTest to a exe and drag it to FxCop



Project MyRules:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using Microsoft.FxCop.Sdk;



namespace MyRules
{
    class clsCheck : BaseIntrospectionRule
    {
        public clsCheck()
            : base("clsCheck", "MyRules.connection", typeof(clsCheck).Assembly)
        {

        }

        public override ProblemCollection Check(Member member)
        {
            Method method = member as Method;
            bool boolFoundConnectionOpened = false;
            bool boolFoundConnectionClosed = false;
            Instruction objInstr = null;
            for (int i = 0; i < method.Instructions.Count; i++)
            {
                objInstr = method.Instructions[i];

                if (objInstr.Value != null)
                {
                    if (objInstr.Value.ToString().Contains("System.Data.SqlClient.SqlConnection"))
                    {
                        boolFoundConnectionOpened = true;
                    }
                    if (boolFoundConnectionOpened)
                    {
                        if (objInstr.Value.ToString().Contains("System.Data.Common.DbConnection.Close"))
                        {
                            boolFoundConnectionClosed = true;
                        }
                    }
                }
            }    
             if((boolFoundConnectionOpened)&&(boolFoundConnectionClosed ==false))
            {
               Resolution resolu = GetResolution(new string[] { method.ToString() });
               Problems.Add(new Problem(resolu));
            }
            return Problems;
        }
    }
}


connection.xml
<?xml version="1.0" encoding="utf-8" ?>
<Rules>
<Rule TypeName="clsCheck" Category="Database" CheckId="Shiv001">
<Name>Connection object Should be closed</Name>
<Description> Connection objects should be closed</Description>
<Owner> Shivprasad Koirala</Owner>
<Url>http://www.questpond.com</Url>
<Resolution> Call the connection close method </Resolution>
<Email></Email>
<MessageLevel Certainty="99"> Warning</MessageLevel>
<FixCategories> Breaking </FixCategories>
</Rule>
</Rules>


Project JustForTest:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net;
using System.IO;
using System.Data.SqlClient;

namespace JustForTest
{
    class Program
    {
        static void Main(string[] args)
        {

        }

        public static byte[] readFully(Stream input)
        {
            SqlConnection objConnection = new SqlConnection();
            byte[] buffer = new byte[16 * 1024];
            using (MemoryStream ms = new MemoryStream())
            {
                int read;
                while ((read = input.Read(buffer, 0, buffer.Length)) > 0)
                {
                    ms.Write(buffer, 0, read);
                }
                return ms.ToArray();
            }
        }

    }



}


Set rules in VS 2013:
https://msdn.microsoft.com/en-us/library/dd264949.aspx
http://blogs.msdn.com/b/codeanalysis/archive/2010/03/26/how-to-write-custom-static-code-analysis-rules-and-integrate-them-into-visual-studio-2010.aspx



Monday, February 15, 2016

生产者消费者模式

生产者生产速度和消费者消费速度不平衡,利用一个阻塞队列实现缓冲区,比如,如果生产者速度较快,当队列已满,就不再生产。同理,队列空的时候,消费者不再消费进入睡眠等到生产者有生产的时候才开始消费。所以

1. 这个模式实现了平衡两者间处理能力
2. 实现生产者和消费者逻辑间解耦


import java.util.Stack;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 1个生产者 3个消费者 生产、消费10次
 *
 * @作者 pt
 *
 */

public class ProducerConsumer {
Stack<Integer> items = new Stack<Integer>();
final static int NO_ITEMS = 10;

public static void main(String args[]) {
ProducerConsumer pc = new ProducerConsumer();
                Thread t1 = new Thread(pc.new Producer());
                Consumer consumer  = pc.new Consumer();

Thread t2 = new Thread(consumer);
Thread t3 = new Thread(consumer);
Thread t4 = new Thread(consumer);
t1.start();
try {
Thread.sleep(100);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
t2.start();
t3.start();
t4.start();
try {
t2.join();
t3.join();
t4.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}

class Producer implements Runnable {
public void produce(int i) {
System.out.println("Producing " + i);
items.push(new Integer(i));
}

@Override
public void run() {
int i = 0;
// 生产10次
while (i++ < NO_ITEMS) {
synchronized (items) {
produce(i);
items.notifyAll();
}
try {
// 休眠一段时间
Thread.sleep(1000);
} catch (InterruptedException e) {
}
}
}
}

class Consumer implements Runnable {
                // consumed计数器允许线程停止
AtomicInteger consumed = new AtomicInteger();

public void consume() {
if (!items.isEmpty()) {
System.out.println(Thread.currentThread().getId()+" Consuming " + items.pop());
consumed.incrementAndGet();
}
}

private boolean theEnd() {
return consumed.get() >= NO_ITEMS;
}

@Override
public void run() {
while (!theEnd()) {
synchronized (items) {
while (items.isEmpty() && (!theEnd())) {
try {
//wait same time as Producer, 主要目的是sleep,减少CPU资源消耗
items.wait(1000);
} catch (InterruptedException e) {
Thread.interrupted();
}
}
consume();

}
}
}
}
}

ref: https://zh.wikipedia.org/wiki/%E7%94%9F%E4%BA%A7%E8%80%85%E6%B6%88%E8%B4%B9%E8%80%85%E9%97%AE%E9%A2%98
http://www.infoq.com/cn/articles/producers-and-consumers-mode