Jacey's Journal

我是乱写的 & 千万别当真

对象

创建对象的两种方式:

1
2
3
4
5
let user = new Object(); // 构造函数法
let user = {
name: "Tom",
age: 20,
}; // 健/值

属性的访问和设置值,删除变量,有两种方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// method 1:
user.name = "Jim"; // set
console.log(user.name); // get
delete user.name;

// method 2:
user["name"] = "jim";
console.log(user["name"]);
delete user["name"];

// method 2好处:
const key = "name";
user[key] = "Jimmy";
console.log(user[key])
// *** 这种方式,可以将key用变量代替。方括号中的内容可以由计算得出。如:
console.log(user["na" + "me"]);

属性值的限制和简写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// 在能过{}方式创建对象时,可使用多字词来做为属性的key
const user = {
name: "Tom",
"come from": "China",
age: 22
}
// 但访问和设置值时,不能使用 "." 来访问,如下是错误的:
user.com from = "New name"; // <--- 报错

但可以使用方括号的方式:
user["come from"] = "US";

const key = "come from";
console.log(user[key]);

// 普通变量名不能使用 let, in 等关键字,但属性名可以使用,无此限制:
const user = {
for: 123
};

// 如果创建的key/value名字相同,可简写。如:
const name = "Tom";
const age = 22;
const user = {
name:name,
age:age
};
// 可简写为:
const user = {
name,
age,
};
// 在函数中更方便使用:
function makeUser(name, age) {
return {
name,
age
}
}

循环获取对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let user = {
name: "Tom",
5: 55555,
age: 10
}
// 获取值,可使用 for…in来循环,如下:
for(let k in user) {
console.log(k);
}
/* 输出时,整数属性的key会放在前面(已经转为了字符串型),其余按创建顺序:
5
name
age
*/

// 判断一个key是否存在:
console.log("name" in user); // output: true

引用和复制

对象的值是引用。

1
2
3
4
5
6
7
8
9
10
11
12
let user1 = {
name: "Tom",
age: 10,
};

let user2 = user1;

user2.age = 20;
console.log(user1.age); // output: 20

// 对象的比较,是比较两个对象是否是引用了同一个对象。
console.log(user1 == user2); //output: true

如果要通过赋值,将两个对象分来,就需要使用克隆。javascript内置有Object.assign,这这个克隆不够深:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let user1 = {
name: "Tom",
age: 10,
child:{
name: "apple"
}
};

let clone = {};
Object.assign(clone, user1);

user1.name = "Jimmy";
user1.child.name = "banana";

console.log(clone);
// output: { name: 'Tom', age: 10, child: { name: 'banana' } }
// 可看到第一次克隆了,但第二层仍是引用。

真正的深层克隆,可用 JavaScript 库 lodash 中的 _.cloneDeep(obj) 函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
_ = require('lodash');

let user1 = {
name: "Tom",
age: 10,
child:{
name: "apple"
}
};

let clone = {};
clone = _.cloneDeep(user1);

user1.name = "Jimmy";
user1.child.name = "banana";

console.log(clone);
// output: 可看到修改原对象,并没影响到克隆的新对象:
// { name: 'Tom', age: 10, child: { name: 'apple' } }

对象方法

可以给对象创建方法,类似其它语言的 class method:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
let user = {
name: "Tom",
age: 22,
}

user.sayHello = function () {
console.log("hello");
}

user.sayHello();
// output:
// hello

// 也可以把已存在的方法赋值给函数方法:

hello = function () {
console.log("hello");
}

user.sayHello = hello;
user.sayHello();
// output:
// hello

类方法可写在类构造时:

1
2
3
4
5
6
7
8
9
10
11
let user = {
name: "Tom",
age: 22,
sayHello: function () {
console.log("hello " + this.name); // 在对象里,可通过 this引用属性。(不加this会报错)
}
}

user.sayHello();
// output:
// hello Tom

javascript 中的this,比其它语言中的this关键字更加宽范。他指定的内容是在运行时计算出来的。如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const user = {name: "Tom"};
const admin = {name: "boss"};

function sayHello(){
console.log("hello " + this.name);
}

user.sayHi = sayHello;
admin.sayHi = sayHello;

user.sayHi();
admin.sayHi();
// output:
// hello Tom
// hello boss

this 并没有严格的与某个对象绑定。在javascript中,this比较自由。

注意:箭头头函数没有 this,如果在箭头函数中引用this,会取来自箭头函数外部的this,如:

1
2
3
4
5
6
7
8
9
let user = {
firstName: "Ilya",
sayHi() {
let arrow = () => alert(this.firstName);
arrow();
}
};

user.sayHi(); // Ilya

构造器 new

除了常规的 {…}来创建对象,也可以使用构造器。对于要多次创建的对象,用构造器更便捷。构造器函数实际上是个常规函数,有两个约定:

  1. 以大写字母开头
  2. 只能由 new 来执行

构造器执行时,由三个流程:

  1. 创建空对象{},并赋值给 this
  2. 执行函数体。
  3. 返回this

如:

1
2
3
4
5
6
7
8
9
10
function User(name) {
// this = {}
this.name = name;
this.role = "worker";
// return this
}

const u1 = new User("Tom");

console.log(u1); // User { name: 'Tom', role: 'worker' }

new 可以让任何函数以这种方式执行。但一般我们用于构建函数时,以大写字母命名,由new执行。

如果任何函数都可以被new来使用,那如何判断这个调用,是否是被new创建的呢?在函数内部,可使用 new.target 函数来测试是否是由new来创建的。如果不是new创建的,由返回undefined;否则,返回函数。

1
2
3
4
5
6
7
8
9
10
11
function User(name) {
if (!new.target) {
return new User(name);
}
this.name = name;
this.role = "worker";
}

const u1 = User("Tom");

console.log(u1); // User { name: 'Tom', role: 'worker' }

构造器的return

构造器一般无 return 语句

  • 如果构造器返回一个对象,则使用这个对象返回,而代替this
  • 如果构造器返回一个原始类型,则忽略。就像没这句一亲。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function User1(name) {
this.name = name;
return {name: "foo"}
}

function User2(name) {
this.name = name;
return "bar";
}
const u1 = new User1("Tom");
const u2 = new User2("Tom");

console.log(u1); // { name: 'foo' }
console.log(u2); // User2 { name: 'Tom' }

可选链

对于一个对象,我们可能会像这样的方式访问其属性值:user.address.city.name。但有可能其address未设置,而直接访问 user.address.city.name 就会报错,而我们想其返回undefined亦或null.

我们可以在user到name中,从根一步步的判断是否存在,存在再取下一步。这种方式可行,但代码太难看。

在javascript中有个可选链的操作,可优雅的达到此目的,使用方法为 value?.prop,他执行下面的操作:

  • 如果value为undefined或null,则返回undefined
  • 如果value存在,则返回 value.prop

如下:

1
2
3
4
5
6
7
8
9
10
const user = {
address: {
city: {
name: "beijing"
}
}
};

console.log(user?.addr?.city?.name); // undefined
console.log(user?.address?.city?.name); // beijing

可选链可用于函数和计算属性:

?.()或 ?.[]

也可用于删除属性:

delete user?.address

总之,可选链 ?. 语法有三种形式:

  1. obj?.prop —— 如果 obj 存在则返回 obj.prop,否则返回 undefined
  2. obj?.[prop] —— 如果 obj 存在则返回 obj[prop],否则返回 undefined
  3. obj.method?.() —— 如果 obj.method 存在则调用 obj.method(),否则返回 undefined

Symbol

复习一下javascript中的原始类型,共有7种:

  • string
  • number
  • bigint
  • boolean
  • symbol
  • undefined
  • null

Symbol 的值是一个唯一的标识符,每次调用时不同。他在内部是不同的值,但在打印时,他的默认的 toString() 方法只展示其信息和id标识,并不显示唯一的标识信息值。如:

1
2
3
4
5
6
7
8
9
10
11
const id1 = Symbol("id");
const id2 = Symbol("id");

console.log(id1); // Symbol(id)
console.log(id1 == id2); // false

const id = Symbol("id desc");
console.log(id); // Symbol(id desc)
console.log(id.toString()); // Symbol(id desc)
console.log(id.description); // id desc

Symbol 的作用

Symbol 的最大作用,是允许我们创建对象的隐藏属性。比如我们引用第三方代码中的user对象,但我们想添加个标识符,又不想破坏原有代码,我们就可以起一个特殊的键来存放我们的属性值,而原有代码因不知道这个键,而不会感知到我们添加了内容。同时,Symbol对象在迭代时,会被忽略。

如:

1
2
3
4
5
6
7
8
let user = {     // 第三方代码中的数据
name: "Tom",
};

gender = Symbol("id");
user.gender = "male"; // 添加一个属性

console.log(user.gender); // male

在迭代时,Symbol对象会被忽略,如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const id1 = Symbol("id1");

let user = {
name: "Tom",
[id1]: "333"
}

const id2 = Symbol("id2");
user[id2] = id2;

console.log(Object.keys(user)); // [ 'name' ]

for (let k in user) {
console.log(k); // name
}

当克隆一个对象时, Symbol 会被克隆:

1
2
3
4
5
6
7
8
9
let user = {
name: "Tom",
[Symbol("id")]: "333"
}

let clone = {};
Object.assign(clone, user);

console.log(clone); // { name: 'Tom', [Symbol(id)]: '333' }

全局 symbol

系统中有个全局 Symbol 符号表。当通过 Symbol.for(key)来创建Symbol时,他会创建并返回一个Symbol对象,同时记录在全局符号表中。当符号表中存在同名的Symbol时,则会返回已存在的Symbol。

反之,也可通过 Symbol.keyFor(sym:symbol)来把Symbol转换为标识符。

1
2
3
4
5
6
7
8
9
let s1 = Symbol.for("id");
let s2 = Symbol.for("id");
let s3 = Symbol.for("id3");
console.log(s1 == s2); // true
console.log(s1 == s3); // false

const sss = Symbol("symbol not exists");
console.log(Symbol.keyFor(s1)); // id
console.log(Symbol.keyFor(sss)); // undefined

对象和原始值

JavaScript 允许我们像使用对象一样使用原始类型。

原始类型是一个原始值,就是7种原始类型中的任何一种的值。

对象,可以存储多个属性;可以使用{…}的方式创建且可以包含函数

原始类型,有很多的方法,而为了实现这些操作方法,内部通过包装器来实现。而包装器只在使用期间存在,用后即毁。如:

1
2
3
const str = "Hello world!";

console.log(str.toUpperCase());// HELLO WORLD!

在访问字符串的 toUpperCase方法时,

  1. str是个原始值。访问其方法,需要创建一个包装对象,此对象有其用到的方法
  2. 包装器的方法被执行,将返回值给调用者。
  3. 中间的包装器对象销毁

注意:原始类型的构造器仅供内部使用,自己的代码中不要使用,容易出现问题。而不在new的构造方法可以使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
console.log(`typeof new Number(0): ${typeof new Number(0)}`);
console.log(`typeof Number(0): ${typeof Number(0)}`);

if (new Number(0)) {
console.log("new Number(0) is TRUE");
} else {
console.log("new Number(0) is false");
}

if ( Number(0)) {
console.log(" Number(0) is TRUE");
} else {
console.log(" Number(0) is false");
}
/*
typeof new Number(0): object
typeof Number(0): number
new Number(0) is TRUE
Number(0) is false
*/

undefined和null没包装器,也没任何方法。

maven 的生命周期

Maven 的生命周期就是为了对所有的构建过程进行抽象和统一。用maven在构建时,有三套生命周期:

  • clean
  • default
  • site

这三套生命周期,涵盖了项目的清理、编译、测试、打包、部署等。

各周期的阶段

clean周期

在clean周期内,有以下三个阶段(phase),意义如其phase的名字:

  • pre-clean
  • clean
  • post-clean

default周期

default周期是最重要的周期,主要有几下几个阶段:

  • validate
  • initialize
  • generate-sources
  • process-sources
  • generate-resources
  • process-resources
  • compile
  • process-classes
  • generate-test-sources
  • process-test-sources
  • generate-test-resources
  • process-test-resources
  • test-compile
  • process-test-classes
  • test
  • prepare-package
  • package
  • pre-integration-test
  • integration-test
  • post-integration-test
  • verify
  • install
  • deploy

site

site主要是用来发布站点,有以下3个phase:

  • pre-site
  • site
  • post-site
  • site-deploy,将生成的项目站点发布到远程服务器上

命令行和生命周期

默认的mvn clean,执行的是test周期的pre-clean、clean阶段。

mvn test,是执行的默认周期的validate直到test阶段。

mvn clean install,是执行的test周期的pre-clean、clean,以及default周期的validate直到install阶段。

插件的目标和绑定

maven定义了项目的周期,但具体的周期的执行并不是maven自己来做,而是交给了众多的插件来完成的。比如maven-depedency-plugin就能分期项目的依赖关系。

maven的生命周期,和插件的目标相互绑定,才能完全相关的任务。如maven-compiler-plugin的compile目标,绑定(也在默认情况下绑定了)default周期的compile周期,就能完成项目的编译任务。maven内置了这么多插件的绑定。

test周期内的默认绑定

在test周期内,有maven-test-plugin绑定了部分test周期的阶段,如下:

阶段 目标
pre-test
test maven-test-plugin:test
post-test

default周期的默认绑定

default周期,因使用的打包类型不同,默认绑定的插件周和周期也不同(如jar、war、pom、maven-plugin、ear等)。如jar/war包的默认绑定如下:

周期 目标 说明
process-resources maven-resources-plugin:resources 复制主资源到主输出目录
compile maven-compiler-plugin:compile 编译代码到主输出目录
process-test-resources maven-resources-plugin:testResources 复制测试资源到测试输出目录
test-compile maven-compiler-plugin:testCompile 编译测试代码到测试输出目录
test maven-surefire-plugin:test 测试
package maven-jar-plugin:jar 创建项目的jar包
install maven-install-plugin:install 将输出的项目构件到本地仓库
deploy maven-deploy-plugin:deploy 将位置子网的构件发布到远程仓库

site周期的绑定

阶段 目标
pre-site
site maven-site-plugin:site
post-site
deploy maven-site-plugin:deploy

其它和插件及周期相关的

从命令行直接运行插件目标

一般是通过mvn运行maven生命周期中的阶段(phase),间接运行phase绑定了插件对应的目标(goal)。我们也可以直接运行插件的目标,如下就是运行maven-dependency-plugin插件的tree目标:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$ mvn org.apache.maven.plugins:maven-dependency-plugin:2.8:tree   # 这儿可简写为 mvn dependency:tree
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building Archetype - demo-mvn 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- maven-dependency-plugin:2.8:tree (default-cli) @ demo-mvn ---
[INFO] net.codox.demo:demo-mvn:jar:1.0-SNAPSHOT
[INFO] \- org.springframework:spring-aop:jar:3.1.0.RELEASE:compile
[INFO] +- aopalliance:aopalliance:jar:1.0:compile
[INFO] +- org.springframework:spring-asm:jar:3.1.0.RELEASE:compile
[INFO] +- org.springframework:spring-beans:jar:3.1.0.RELEASE:compile
[INFO] \- org.springframework:spring-core:jar:3.1.0.RELEASE:compile
[INFO] \- commons-logging:commons-logging:jar:1.1.1:compile
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1.198 s
[INFO] Finished at: 2017-10-07T22:17:11+08:00
[INFO] Final Memory: 7M/81M
[INFO] ------------------------------------------------------------------------

插件的解析

上节的命令 mvn org.apache.maven.plugins:maven-dependency-plugin:2.8:tree 可简写为 mvn dependency:tree,即插件的groupId:artifactId:version,直接简写成了dependency,这个是使用了插件前缀来简写的。

  • 插件也有仓库,和配置maven仓库的repositories一样,只是插件仓库的配置项为pluginRepositories。一般不需要配置自己的插件仓库。
  • 如果在pom文件中配置插件时(build标签内),如果是官方插件(即groupId为org.apache.maven.plugins),则可省略groupId。

根据坐标定位插件,需要其groupId:artifactId:version三项,具体的解析如下:

插件前缀

插件前缀和groupId:artifactId是一一对应的,这个写在仓库的无数据中的groupId/maven-metadata.xml中。如maven的官方插件的maven-metadata.xml中列举了其对应的插件前缀:http://repo1.maven.org/maven2/org/apache/maven/plugins/maven-metadata.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<metadata>
<plugins>
<plugin>
<name>Apache Maven ACR Plugin</name>
<prefix>acr</prefix>
<artifactId>maven-acr-plugin</artifactId>
</plugin>
<!-- ***** -->
<plugin>
<name>Apache Maven Dependency Plugin</name>
<prefix>dependency</prefix>
<artifactId>maven-dependency-plugin</artifactId>
</plugin>
<!-- ***** -->
</plugins>
</metadata>

version的解析

当没写version时,maven会自动解析。分两种情况:

  • maven的内置超级pom文件,为核心插件设置了版本。如果使用的插件是这类,则已经内置了版本号。
  • 对非核心插件,会去检查仓库中的可用版本(获取插件的meta信息),并做出选择。meta信息中有latest版本、release版本的值。对于maven 2,则会使用latest版本(即最新版本,有可能是SNAPSHOT版,也即可能有风险),在maven 3中,使用relaese版本(即最近的发布版)

获取帮助

最方便的为使用maven-help-plugin来获取插件的信息,如:

1
2
3
4
mvn help:describe -Dplugin=org.apache.maven.plugins:maven-compiler-plugin:2.1.4   # 完整写法
mvn help:describe -Dplugin=org.apache.maven.plugins:maven-compiler-plugin # 简化版本,这样使用最新版来描述
mvn help:describe -Dplugin=compiler # 简化坐标为插件前缀
mvn help:describe -Dplugin=compiler -Ddetail # 更详细信息

对于maven的插件,可去官方网站寻找资料: maven插件官网

对于以前大师的codehaus的插件,好像这个站在2015年关了。新的替代者: MojoHaus

前言

java中的多线程组件比较多,前些天看了一下CountDownLatch的源码,今天拿 CountDownLatch来详细分析一下。

样例、代码、说明

分析之前,我们先看一个比较简单的用法。

样例

我们假设一项工作:

  1. 需要先由3个人做准备;
  2. 准备好后再由2个人进行计算;
  3. 计算完后,由上级来进行输出。
  4. 假设刚开始,全部人员就位。

代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
package thread.demo.codox.net;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;

public class CountDown {

// ID生成器
private static AtomicInteger idGenerator = new AtomicInteger(1);

// 主线程,用来进行主要的准备工作和进行调用。
public static void main(String[] args) throws InterruptedException {
// 假设工作流程要先做准备,由3个线程来做准备工作。
// 做完准备后,由多个Worker线程进行处理,处理完毕后,由主线程进行打印。

// prepLatch 为准备好了的latch, workLatch为工作线程完成了的latch。
CountDownLatch prepLatch = new CountDownLatch(3);
CountDownLatch workLatch = new CountDownLatch(2);

// 生成线程池,并发准备纯种、工作线程全放到线程池。
ExecutorService es = Executors.newFixedThreadPool(10);
es.execute(new PrepareWorker(CountDown.idGenerator.incrementAndGet(), prepLatch));
es.execute(new PrepareWorker(CountDown.idGenerator.incrementAndGet(), prepLatch));
es.execute(new PrepareWorker(CountDown.idGenerator.incrementAndGet(), prepLatch));

es.execute(new Worker(CountDown.idGenerator.incrementAndGet(), prepLatch,workLatch));
es.execute(new Worker(CountDown.idGenerator.incrementAndGet(),prepLatch, workLatch));

workLatch.await();
System.out.println("Main thread end!");
es.shutdown();
}

static class PrepareWorker extends Thread {
private CountDownLatch startLatch;
private int id;

public PrepareWorker(int id, CountDownLatch latch) {
this.id = id;
this.startLatch = latch;
}

@Override
public void run() {
System.out.println("Thread " + id + " prepare started!");

try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread " + id + " prepare end!");
startLatch.countDown();
}
}

static class Worker extends Thread {
private int id;
private CountDownLatch latch;
private CountDownLatch preLatch;

public Worker(int id, CountDownLatch preLatch, CountDownLatch latch) {
this.id = id;
this.preLatch = preLatch;
this.latch = latch;
}

@Override
public void run() {
try {
preLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Woker " + id + " workout!");
latch.countDown();
}
}
}
/* OUTPUT:
Thread 3 prepare started!
Thread 4 prepare started!
Thread 2 prepare started!
Thread 4 prepare end!
Thread 2 prepare end!
Thread 3 prepare end!
Woker 5 workout!
Woker 6 workout!
Main thread end!
*/

说明

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 唯一的构造函数,由一个正数。当countDown(),并到0时,在await()中阻塞的调用会返回。如果用负数初始化,会报
* IllegalArgumentException异常。
*/
CountDownLatch(int count)
// 一直阻塞,直到countDown()到0
void await()
// 阻塞,直到countDown()到0,或超时
boolean await(long timeout, TimeUnit unit)
// 每调用一次,由构造函数初始化的值count减1,直到0,这时在await()中阻塞的函数返回。
void countDown()
// 返回当前的count值。(实际底层使用的是int值。构造函数初始化时也是用int来初始化的。)
long getCount()

实现分析

构成

我们来看LatchDownLatch的构造函数。在内部,使用一个继承了AQS(后续简称AQS)的同步器来实现,并重新实现了AQS 的 int tryAcquireShared(int acquires) 和 boolean tryAcquireShared(int acquires)。

其中,tryReleaseShared(int)用在CountDownLatch的countDown();tryAcquireShared(int) 用在CountDownLatch的await()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
   public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
private static final class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 4982264981922014374L;

Sync(int count) {
setState(count);
}

int getCount() {
return getState();
}

protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}

protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
}

private final Sync sync;

函数分析

countDown

CountDownLatch的countDown()实现:

1
2
3
4
   // ID: snippet-1-1
public void countDown() {
sync.releaseShared(1); // -> snippet-1-2
}

sync.releaseShared(int)是来自Sync类继承的抽象类AQS:

1
2
3
4
5
6
7
8
9
10
11
12
13
// ID: snippet-1-2
// With in AbstractQueuedSynchronizer
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) { // <- AbstractQueuedSynchronizer中这个调用直接抛异常。实际用的Sync中重载版本
// -> snippet-1-3
doReleaseShared();
return true;
}
return false;
}
protected boolean tryReleaseShared(int arg) {
throw new UnsupportedOperationException();
}

在Sync中的版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// ID: snippet-1-3
// With in CountDownLatch.Sync
protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0; // return snippet-1-2:6
}
// return snippet-1-2:9
}

这里先获取到状态值,并计算减1(即countDown)之后的值,然后把检查和设置原值做为一个原子操作来执行。如果原值变化了,没执行成功,则重新尝试;否则,返回countDown之后是否为0。使用(失败-重试方式的)乐观锁而不是synchronized方式的悲观锁,这在多线程中有较好的性能。

在java中,有很多的多线程类中,使用了compareAndSet(后续简称CAS)操作。这个compareAndSetState(int,int),调用了Unsafe.compareAndSwapInt(Object, long, int, int)。这个方法的实现,需要CPU的支持,是基于CPU的 CMPXCHG 指令。

在snippet-1-3中,是尝试取出state、设置state的过程。其中的state是在Sync构造时设置的值,这个值是放在AQS类中的一个由volatile标识的field。volatile可以避免 脏读。volatile和CAS一样,也需要CPU的支持。

在tryReleaseShared执行完成以后,如果count减少到0,则返回snippet-1-2:6,运行doReleaseShared函数,来唤醒其它在CountDownLatch上等待的线程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// ID: snippet-1-4
private void doReleaseShared() {
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
unparkSuccessor(h);
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
}

在AQS中,其核心数据是一个改造过的CLH自旋锁队列。是一个双端队列,Node节点的pre和next分别标示前前、后节点。节点数据包含当前线程和状态信息。

wait()

当需要阻塞的地方,就调用CountDownLatch的wait(),这时候阻塞等待countDown。阻塞时,调用的是AQS的acquireSharedInterruptibly(1):

1
2
3
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}

在AQS的这个方法中,

1
2
3
4
5
6
7
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0) // 先直接获取锁,如果失败则进入自旋
doAcquireSharedInterruptibly(arg); // 如果当前status小于0,则在这个函数中自旋
}

而这儿的tryAcquireShared(int),在AQS是protected方法,即要求子类实现的。这儿是在CountDownLatch中实现的:查询其state,如果是0则返回1否则返回-1。

我们再来看AQS的doAcquireSharedInterruptibly(int):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
final Node node = addWaiter(Node.SHARED); // 以安全的方式添加一个共享节点到队列尾
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head) { //如果前一个节点是head,则当前节点就是等待获取锁的节点
int r = tryAcquireShared(arg); // 尝试获取锁。
if (r >= 0) {
setHeadAndPropagate(node, r); //获取锁成功,重置head节点
p.next = null; // help GC
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}

队列中的head和tail是lazy initialize。来看看addWaiter(Node):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) { //当前的tail不是空,则将新建的节点加到队尾。
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node); // tail是空,则初始化一下head和tail
return node;
}

进阶

  • CLH锁,MCS锁,自旋锁,排队自旋锁
  • CAS实现原理和CPU指令
  • AbstractQueuedSynchronizer的扩展

有车的人,肯定快到购车日时,会收到很多的电话和短信:推销车险。

我觉得我买的最值的是去年给我家里的第一辆车、一辆6年多的车购买的车险,车险是从人保官网购买的,当时打完折是这样的:

保单信息

但这个保单还送个“大礼包”,我使用过的有至少值 1000 元了:

  • 验车券。可供验车费用一次,验车应该要交费400多的样子,用券就不用交钱了。
  • 汽车保养券。可上门或到店保养一次。我保养了我的另一辆车,小保养的机油机滤+工时费,价值500元
  • 宽途洗车券(6次)。我洗车很少,一共用了5次,至少价值100元吧。
  • 还有一些没用上的券:代验车券、玻璃修复券、更换防雾霾滤芯券(这个年前应该用一下的,不小心过期了)、钣金修复券等。

同时,通过人保的推荐系统,还有返京东E卡,这半年我的推荐收入至少有 2000 块。他的推荐规则是这样的:

  • 使用我的推荐码( ***130玖4妻14691***)在人保官网购买车险,购买车险者会额外多送50元京东 E 卡,我也会得50元京东 E 卡。
  • 我推荐的用户,再推荐人购买,我也会额外多得10元的京东 E 卡。

这半年,我从京东买东西基本没花过现金了。看看我的收益,还不错。近7年前一直用人保,感觉挺好。当然从人保官网购买,比 4s 店、电话销售的便宜不少,与淘宝车险持平(配送人员,和淘宝车险都相同)。(注:特别便宜的保险要看看保险合同,是否有限制驾驶人和行驶区域。如果有限制驾驶人和行驶区域,意味着其它人,或在限制的行驶区域之外出现交通事故,保险公司免责,即不承担理赔责任,这种保险一定要注意)。现在购买车险的渠道很多,一定要通过靠谱的渠道购买。

推荐奖品

最后,车险快到期的朋友,欢迎大家使用我的 人保推荐码 推荐码在人保官网购买车险:130玖4妻14691 (记得换为全数字),购买地址: http://www.epicc.com.cn/branch/beijing/pcjdsg/

bye-bye 2016
转眼间2016又过去了。

2016,不好,但也不坏,至少还活着。

要感谢的

首先,要感谢家人健康。家人的健康比什么都重要。人活着,最差的是需要别人的帮助,起次是能自理自足,好的是在有能力的基础下帮助别人。生病,全家都需要付出精力。今年虽然有过一些小病,但均是门诊可以搞定的。
现在我不断的咳嗽,是现在北方的空气太差导致的?不会是肺癌、肺吸虫、×××之类的大病吧?^O^

其它,今年开车较多。每天下下班要近70km,则70552=18200km。虽然有些假期不需要开车,但有时候周末或假期还要远途不是么?我觉得今年至少要开了1.5万公里吧。感谢我家的车没撂挑子,感谢路上的其他司机的“不杀之恩”,感谢专业碰瓷的没招惹我,感谢路上的行要对我的忍让。虽然我偶有不规矩的驾驶行为,2017争取更文明驾驶。

今年的进步

感觉今年没什么进步,哈哈,工作的,学习的,生活的。

今年孩子上小学了,嗯,小学。觉得上小学也挺辛苦的,每天要6:30起床,春夏秋冬。。。不过我也得跟着6:30起床送娃。

今年更大的重点放在了生活和带娃了。因妻子下半年的有自己的事忙,大部分的时间需要带娃。平时下班较晚,到家晚上8点算早的,到家10点是经常的。回来要辅导和检查孩子作业、督促刷牙洗脸睡觉,偶尔要晚上11点才能上床,有次还近12点了。周末,要带娃、上各种培训班,偶儿去下游乐园或看个电影,可自己安排的时间很少。我觉得工作忙,对孩子的习惯培训也有耽误,对孩子有亏欠。但自己已经尽力了。

工作上,这个行业基本没有不加班的。因家里事情较多,工作上做的不够出色。继续努力,争取后续几个月更出色。

虽然没什么进步,但感觉自己的身体素质,远不如前了。

明年展望

家人健康,工作进步,孩子学习成绩不差。另外就是,每个月的收入够还信用卡的。。。

摘要

今天有个兄弟公司咨询我有关迁移DNS的技术相关的事情,了解如何做准备,如何能稳妥的把DNS由第三方服务商迁移到自建的DNS上。最近可能一是由于大公司对DNS解析安全越来越重视,或者最近DNS攻击越来越严重,可能觉得自己维护DNS解析更有底气。但迁移DNS这件事,有时候会比较复杂,并且容易出错(更多的是不平滑,在迁移过程中网站访问有时好有时坏、有的地区好有的地区不好)。

由于域名解析是个从上到下的过程(DNS系统可以说是全球最庞大、最基础的“云”服务),不但要了解如何搭建DNS,还要和注册商打交交道,以及碰到问题时如何排查。尤其是当自己使用的ns名称不存在时,可能要创建NS的名字。

名词解释

通用的来讲DNS就是解析,但在DNS行业内,要用更细化的名字。在这里我解释的可能不太准确,但目的是用大家能理解的词简单解释一下,更细的请参阅技术书籍或手册:

注册局/域名注册管理机构

每个域名的tld由一家注册局来负责运营,如.com类域名由Verisign负责,.cn域名由CNNIC负责。注册局接收注册商的请求来管理domain、contact和host

注册商/域名注册服务机构

接收最终用户的请求,并向注册局注册和管理域名。我们注册域名,一直是向注册商注册,注册商有系统与注册局通信。

顶级域

域名是由右向左以“.”分割的符号,越往右级别越高。一个顶级域有相应的注册局来运营和管理。如cn就是一个顶级域。

后缀

一个顶级域,可能有多个域名后缀,一个域名后缀+一个个性字符,就能组成一个域名。如cn、com.cn、net.cn均是域名后缀。

域名

一组字符+域名后缀,组成一个域名。如codox.cn、codox.net、foo.com.cn、bar.org.cn等。

主机记录

域名之后,可以创建多个解析记录,可用于web站点等,如journal.codox.cn。又叫完全限定名(fully qualified domain name,FQDN)

hostObject/dns记录

域名(主域名,不是指主机记录)使用的NS,如codox.cn使用的是dns7.hichina.com和dns8.hichina.com

使用场景

对于有可能涉及到以下几种:

  • 伪自建:不走寻常路,自己用的域名后缀比较奇特,注册后修改DNS为第三方/或以前的自建的解析服务器时时,报“DNS服务器不合法”之类错误
  • 自建DNS,NS服务解析自己的域名:如自己的域名为codox.cn,使用的DNS为ns1.codox.cn/ns2.codox.cn
  • 自建DNS,但有多个不同后缀的域名,使用同一组DNS

伪自建

有时候会碰到客户申请了域名foo.site域名做个人网站,使用第三方解析,如ns1.myns.com/ns2.myns.com。但在注册商处修改foo.site的DNS为ns1.myns.com/ns2.myns.com时报错,提示“DNS不合法”、”DNS不存在”之类的。com后缀的域名使用这组DNS没事,site域名就不行?为什么呢?

域名在使用某个DNS时,必须要通过注册商在注册局注册、必须要通过注册商在注册局注册、必须要通过注册商在注册局注册。如foo.site域名要使用ns1.myns.com这个DNS,必须要有注册商在site注册局注册一下ns1.myns.com这个DNS(注册时只需要填写这个ns1.myns.com名字即可,不需要额外信息)。只要有一个注册商在site注册局注册过后,以后任何***.site域名均可以使用ns1.myns.com这个ns了。你懂的,ns2.myns.com类似。

有的同学可能发现,这个ns(.com)的后缀和用这个ns的域名的后缀(.site)不同,这类情况在注册ns时不需要填写ns对应的服务器的ip,纯报备,所以部分注册商对这个流程进行了优化:当有人用这种跨后缀的ns时,如果域名所有的注册局不存在,就注册一下。

当然,为什么要注册,要走这个流程?反正ns1.myns.com这个NS也不会写入site的顶级域DNS系统,为何多此一举?我也觉得烦琐,多此一举,但这是域名注册系统的标准(EPP)规定的。

自建DNS

有的大公司,或有技术追求想自己搭建dns做自己域名的解析的,可能会有此需求。我们以我的域名来做例子:

1. 域名:codox.cn,域名已经注册。
2. 使用的DNS:ns1.codox.cn、ns2.codox.cn,尚未创建。
3. DNS服务器2台,ip分别为:1.1.1.1、2.2.2.2。服务器已经购买。

创建hostObject

这是第一步:要向cn注册局注册ns1.codox.cn和ns2.codox.cn这两个DNS,并创建dns对应的ip。

如何创建?每家注册商的操作方式不同,以阿里云和godaddy为例子说明:

在阿里云创建hostObject

在阿里云创建hostObject,叫“域名服务器注册”。位置:登陆控制台后,在左侧点击“域名”打开域名列表,点击codox.cn域名后面的“管理”打开域名控制台(就是打开管理解析记录的那儿后,点击页面上方导航栏的“基本管理”)。在域名控制台,点击左侧的“DNS修改/创建”,右侧会看到有个“域名服务器注册/修改”的按钮,点进去后就有注册的位置了。以我的例子,要选择“解析国内英文域名”

在godaddy创建hostObject

在godaddy(中文界面)叫“创建主机名”,在控制面板点击域名进行域名详情的“设置”标签,下面有个“主机名”栏目,点击“管理”可看到已添加的hostObject,当然也可以创建。

修改DNS

兵分两路,创建dns的同时,可能要同步搭建dns服务器上的解析服务器软件,如bind。此处blabla相信老司机很熟悉了。

创建后,除了要添加www.codox.cn.、jounal.codox.cn.等解析记录外,**一定要记得添加下面的几条记录,否则域名会不正常**

1
2
3
4
codox.cn.		IN	NS	ns1.codox.cn.
codox.cn. IN NS ns2.codox.cn.
ns1.codox.cn. IN A 1.1.1.1
ns2.codox.cn. IN A 2.2.2.2

添加完后,运行以下命令看这正常不:

1
2
3
4
5
6
7
dig		codox.cn	NS	@1.1.1.1
dig codox.cn NS @2.2.2.2
dig ns1.codox.cn @1.1.1.1
dig ns2.codox.cn @1.1.1.1
dig ns1.codox.cn @2.2.2.2
dig ns2.codox.cn @2.2.2.2
dig othe.your.record.codox.cn @1.1.1.1

以上检查确认没问题后,就可以在注册商处修改域名的NS记录为ns1.codox.cn和ns2.codox.cn了。

注意:修改后,原来的dns服务器上的解析记录在48小时内不要删除,并且保持上面的解析记录正确。

自建DNS-共用

如果上面codox.cn使用了ns1/2.codox.cn这组dns后,我又注册了个codox.net域名,也要同样使用ns1/2.codox.cn,怎么办呢?要在.net注册局创建hostObject,因为只要我用了这组dns,别人的net域名肯定没用过,也肯定没人帮我创建过。所以我的codox.net域名使用这组dns,就要在.net的注册局创建这组dns(其实是报备吧)。但如何创建呢?分两种:

1. 需要手工创建。对于需要手工创建的,由用codox.cn这个域名的账号登陆,管理codox.cn这个域名,按上述的“创建hostObject”的方式创建dns,并且用途要用于解析“.net域名”。
2. 自动创建。对于有些注册商,你使用的这种交叉注册局的ns,如果在.net注册局不存在ns1.codox.cn,会自动创建。如godaddy。

其它问题

  • 一个域名可以使用几个NS?
    最多可以写13个ns,如google.com就用了13个ns名字。
  • 一个ns可以使用多个ip吗?
    可以,最多可以使用13个IP。并且可以添加ipv6地址。
  • 我的自建dns工作正常,使用这个dns的域名的网站也能访问,但dig mydomain.com ns时,就是不返回ns记录,为什么?
    你的域名在注册局的dns中,把域名正常授权给了您的自建dns。但你的自建dns中,mydomain.com NS记录没写。在dig或nslookup时,当从顶级域的dns中获取到mydomain.com NS ns*.mydomain.com后,仍会去ns*.mydomain.com上查询一次mydomain.com的NS记录,如果没写就返回空了。但一般www.mydomain.com的主机记录能正常使用,网站能正常访问。
  • 什么样的hostObject注册时需要填写ip,什么样的不需要?
    当dns名字的后缀,和将要使用这个ns的域名-的后缀,两个后缀归同一家注册局时,就必须要填写ip,且要这个域名的持有人才能注册。否则就不需要填写ip,任何注册商都可以。

前言

春节期间家里长期没人,想做个大门口的监控。现在有些不错的“智能”摄像头,能做到有活物报警等,但考虑到未来的扩展性和灵活性,再加上玩票,选择了树莓派。

材料准备

采购

  • 树莓派2代B型 raspberry pi2 model B 现货 喜迎春节[交易快照]200.00
  • 包邮 树莓派摄像头500万像素RASPBERRY PI RPI CAMERA OV5647模块[交易快照] 72.90
  • EDUP EP-N8508GS黄金版 迷你USB无线网卡 树莓派专用[交易快照] 27.00
  • 树莓派摄像头延长线500mm 加长排线 50CM Raspberry Pi camera[交易快照] 3.00
  • YS-18 HC-SR501 人体红外感应模块 进口探头 防盗感应开关模块[交易快照] 3.70
  • 电磁式蜂鸣器5V 采用SOT塑封管 5V有源蜂鸣器 蜂鸣器[交易快照] 0.39
  • LED灯实验包 包含10个LED+10个电阻 适合树莓派MCU入门[交易快照] 2.97
  • 40P 杜邦公头转母头 杜邦线 公对母 面包线 20CM长 40根一排 特价[交易快照] 2.81

自有

  • SD 卡16G
  • USB电源转换器
  • USB 线
  • 胶带、双面胶、硬纸壳(给摄像头和人体感觉模块粘了个外壳)
  • 冰雪聪明才智

配置

树莓派系统

https://www.raspberrypi.org/downloads/raspbian/ 下载 RASPBIAN JESSIE 镜像,然后按 镜像安装说明把下载的 raspbian 写到 SD 卡中,把 sd 卡插入树莓派;把 usb 无线网卡插到树莓派;把网张插入树莓派;把 usb 线插入树莓派,不出意外的话树莓派上的有些灯就亮了,启动了。

ssh 登陆树莓派(默认主机名是raspberrypi,默认密码是 raspberry),先配置无线网卡,vi 打开文件 /etc/wpa_supplicant/wpa_supplicant.conf,插入下面4行:
network={
ssid=”YOUR_SSID”
psk=”YOUR_WIFI_PASSWORD”
}

然后试试能否用无线连上。

更新系统

  • 把更新源换为日本的(日本的感觉比较快)(把所有行注释掉,添加一行

    1
    deb http://ftp.jaist.ac.jp/raspbian jessie main contrib non-free rpi
    1
    2
    3
    4
    5
    6
    sudo apt-get update
    sudo apt-get upgrade
    sudo rpi-update
    sudo apt-get install python-rpi.gpio
    sudo apt-get install python3-rpi.gpio
    sudo reboot

系统配置(扩展文件系统和打开摄像头模块)

sudo raspi-config
选择1 Expand Filesystem,回车
选择 5 Enable Camera,Enable,回车
然后到主菜单后,按右箭头,选中 Finish 后,回国,是否重启时,选择重启;
关机(拔电源)后,把摄像头装上(注意别装在 Dispaly 槽了,两个插口差不多,我为这事搞了一晚上奇怪为何摄像头检测不到呢)

应用

把摄像头和人体红外感应模块穿过防盗门,贴在门外,然后把红外感应模块和树莓派连起来;我在树莓派上又装了一个 LED 灯和蜂鸣器
下面是代码(python3是现学的,写的比较渣。本来是用的多线程和锁机制,以让录像的同时 led 闪、蜂鸣器响,但不知道为何重复触发时树莓派就死机,就单线程了):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
#!/usr/bin/env python3

import RPi.GPIO as GPIO
import os
import time
import threading

HCPIN = 12
LEDPIN = 11
BUZZPIN = 13

phone_dir = '/mnt/doorkeeper/'
temp_dir = '/home/pi/temp'
#mutex = threading.Lock()
#log_lock= threading.Lock()

#初始化
def init():
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BOARD)
GPIO.setup(HCPIN,GPIO.IN)
GPIO.setup(LEDPIN,GPIO.OUT)
GPIO.setup(BUZZPIN,GPIO.OUT)

def capture():

mylog('prepare start to capture.')
filename="video-" + time.strftime("%Y%m%d%H%M%S", time.localtime()) + ".h264"
h264absfile = os.path.join(temp_dir,filename)

#if mutex.acquire(1):
try:
mylog("capture start.")
os.system("raspivid -w 1280 -h 720 -t 15000 -fps 20 -o " + h264absfile )
mylog("capture end.")

# 文件存在,开始编码转换为mp4
if os.path.exists(h264absfile):
subdir = time.strftime("%Y%m%d", time.localtime())
mp4dir = os.path.join(phone_dir, subdir)

if not os.path.exists(mp4dir):
mylog("mkdir " + mp4dir)
os.mkdir(mp4dir)
mp4filename= filename + ".mp4"
mp4absfile = os.path.join(mp4dir, mp4filename)
mylog("encode " + mp4absfile+ " start.")
os.system("MP4Box -quiet -fps 20 -add " + h264absfile + " " + mp4absfile)
os.remove(h264absfile)
mylog("encode h264:" + h264absfile + " to mp4file:" + mp4absfile + " end.")
else:
mylog("encode cancel(file " + h264absfile + " not exists.")
finally:
pass
# mutex.release()

#else:
# mylog("acquire lock fail, capture cancel.")


#感应器侦测函数
def detct():
while(1):
#normal()
#如果感应器针脚输出为True,则打印信息并执行蜂鸣器函数
if GPIO.input(HCPIN) == True:
mylog("somebody there!")
shine(2)
else:
#mylog("none")
time.sleep(2)


def shine(t):
#t1 = threading.Thread(target=capture)
#t1.start()

trys = int(t) * 1;
try:
while trys > 0:
GPIO.output(LEDPIN,GPIO.HIGH)
GPIO.output(BUZZPIN,GPIO.HIGH)
time.sleep(0.1)
GPIO.output(LEDPIN,GPIO.LOW)
GPIO.output(BUZZPIN,GPIO.LOW)
time.sleep(0.1)
trys -= 1
## 获取
capture()
finally:
GPIO.output(LEDPIN,GPIO.LOW)
GPIO.output(BUZZPIN,GPIO.LOW)

def normal():
mount_nfs_flag = os.popen("mount -l | grep doorkeeper | wc -l").read()
mount_nfs_flag = int(mount_nfs_flag)
if mount_nfs_flag <= 0:
mylog("mount fail? remount nfs")
os.system("sudo mount.nfs 192.168.0.117:/volume1/doorkeeper /mnt/doorkeeper -o nolock")

min=int(time.strftime("%M"))
sec=int(time.strftime("%S"))
if min % 10 == 0 and sec % 60 <= 4 :
t1 = threading.Thread(target=capture)
t1.start()

trys = 2
while trys > 0:
GPIO.output(LEDPIN,GPIO.HIGH)
GPIO.output(BUZZPIN,GPIO.HIGH)
time.sleep(0.25)
GPIO.output(LEDPIN,GPIO.LOW)
GPIO.output(BUZZPIN,GPIO.LOW)
time.sleep(0.25)
trys -= 1

def mylog(l):
#if log_lock.acquire(5):
try:
print(time.strftime("%Y-%m-%d %H:%M:%S"), l)
f = open('/var/log/doorkeep.log', 'a')
f.write(time.strftime("%Y-%m-%d %H:%M:%S ") + l + "\n")
f.close()
finally:
pass
# log_lock.release()


time.sleep(5)
init()
shine(2)
detct()

GPIO.cleanup()

效果

上门的效果,外壳好黑好丑(没找到合适的壳):

上门效果

下面是拍到的打扫楼道的阿姨。发现物业较差,好几天才打扫一次。

其它

问题

这个摄像头不是鱼眼摄像头,范围有限;在楼道较暗,如果楼道的声控灯没开,效果很差。

扩展

可以通过 bypy 把文件同步到云盘。可以写个 app,有人时发送消息,查看录像;可接个 mic 和小喇叭来对话;可换个鱼眼+红外摄像头;可再接个陀机。

看来,得搞台 ECS了。。。

新的地址

每次注册个域名,写几篇,然后就会荒废。再过段时间,发现域名过期了,再过段时间发现主机也过期了,然后可能就忘记续费了。then。。。以前写的内容也没了。
这次放到了免费的地方,也不用维护。自己想写了,po上去就好了。

0%