一旦服务器启动,服务开始提供,并且在配置中心注册了(配置中心可以是本地的地址,也可以是zk,也可以是其他的实现),那么客户端就要开始调用了

点击看大图

服务引用

服务引用 RefererConfig.getRef()

先是获取集群支持(先忽略,主要是配置中心相关的)

configHandler.refer(interfaceClass, clusters, proxy)
开始获取接口代理

1.一旦知道接口名,Class.forName加载接口类,就开始通过proxy工厂来为服务端接口创建代理了

2.jdk的Proxy类,直接来创建代理.同时代理要传入RefererInvocationHandler
这个类可以看错是真正的stub,封装了rpc调用请求.当在客户端获取到服务接口的bean的时候,实际上调用过程被这个类拦截,进行封装发送rpc

1.当接口被调用的时候,这个拦截器险根据拦截到的请求构造一个rpc请求

2.这里就会存在一个策略.该调用哪个,以FailoverHaStrategy为例

1.选择一个服务提供方

1.如果是jvm服务,那么直接从本地的服务map中取出一个调用就行

2.如果是真正的远程服务,这时候就进入nettyClient部分了

把请求向netty的Channel中写就行了.服务端会从Channel中取进行处理,然后放回来.这样客户端就拿到结果了

注释和共享

这一篇继续从这个demo开始,分析一下这个服务是怎么发布出去的.关键的代码从motanDemoService.export();开始.

一图胜千言.

点击看大图

服务发布

服务发布ServiceConfig.export()

1.加载有的配置中心url列表/新建

2.doExport(ProtocolConfig,port,registryURLs) //配置中心地址列表

2.1导出的时候,会先判断是否存在.其实就是根据协议名,ip,接口,参数来生成一个唯一key.

2.2ConfigHandler.export(Class interfaceClass, T ref, List registryUrls) //接口.实现.配置中心url列表

2.2.1.根据协议名创建协议,这里ProtocolFilterDecorator

2.2.2.根据接口,实现类,serviceUrl,构造一个Provider,用来提供服务

2.2.3.使用协议进行导出Provider, export(Provider provider, URL url)

2.2.3.1创建一个Exporter

2.2.3.1.1.创建的时候会将服务提供方Provider和url有个映射关系,这样当一个url请求过来的时候,就知道改调用谁了.ProviderMessageRouter,讲一个请求路由注册到server上,同时包装了一个心跳包

2.2.3.2进行导出 导出就是一个服务器打开的过程/server.open();

2.2.3.2.1进入nettyServer初始化,主要就是添加handler,编码解码.和一个rpc处理的
相当于一个请求过来的时候,先进行解码,然后调用业务处理handler进行处理,处理完成后,进行编码,然后返回给客户端

服务器启动后,相当于这个服务就发布了

2.2.4.注册register(registryUrls, serviceUrl) //这一步就是将serviceUrl,向对应的jvm/rpc服务中心注册url,本地注册就是LocalRegistryService类里一个map..zk的.就是向zk写node.等等

注释和共享

工作中一直在使用rpc,但是只是对简单的原理比较熟悉.最近看到有motan的一个介绍,代码拉下来看了看,除了测试用例比较少之外.其他还是不错的,和阿里的rpc框架比起来,还是弱了一些,好处就是方便用来学习.
motan 是weibo的一个rpc框架,据说已经在线上使用了.

在学习rpc框架之前,建议看一个hello world级别的文章RPC框架几行代码就够了,写的非常好,看完基本就知道rpc的核心了.

Remote Procedure Calls中最关键的那个图,就能说明了.

rpc的flow

本地client调用本地client stub,stub对消息进行封装,通过socket发送,服务端的server stub接收到,然后解包,将里面传递的方法名,方法参数.等等信息,识别出来,调用服务端对应的服务,然后得到结果后,又通过socket返回,本地client又进行解包.就行了.

这里面会涉及到,封装,封装就是吧对象序列化,这样才能在网络中传递.

而生产环境的rpc框架需要考虑的有:

stub怎么生成,序列化怎么最高效,如何统一不同机器之前的调用,(大小端的机器等),如何识别该调用哪个机器,负载均衡.socket通信.等等.

先跑个demo熟悉一下.

下载motan源码,导入ide,然后先启动服务端,MotanApiExportDemo,这个类,然后控制台会打出服务已经启动.然后运行MotanApiClientDemo,会发现一个控制台打出motan,服务端打出hello motan.就说明跑起来了.

如果控制台日志没有.修改对应resources下面的log4j.properties文件.首行添加log4j.rootLogger=debug,stdout ,会设置默认日志级别为debug,并且在控制台输出.
或者直接fork我这个

后面会逐步分析,希望坚持下来.

注释和共享

不说废话,直接上。本文主要包括tycho的使用,版本号的自动更新。

eclipse插件开发中,依赖的管理是个问题。如果采用常规的搞个lib目录,然后加到MF文件中。一旦依赖越来越多。或者要更换版本号就变得非常麻烦。所以要用到tycho

首先说明一下目录结构。一个parent的maven工程,一个plugin工程,。两个features。一个是deps的。一个是plugin的,这个依赖deps是独立的mvn项目。可以先不用管。一个deps依赖工程(这个依赖工程独立)。
在主pom下。

步骤如下。

关于依赖部分

  1. 新建一个普通的mvn工程,比如deps。打包类型写成<packaging>bundle</packaging>,同时在pom.xml中添加build部分
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
<build>
<plugins>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<version>3.0.1</version>
<extensions>true</extensions>
<configuration>
<niceManifest>true</niceManifest>
<manifestLocation>META-INF</manifestLocation>
<instructions>
<Bundle-SymbolicName>${project.artifactId}</Bundle-SymbolicName>
<Embed-Dependency>*</Embed-Dependency>
<Embed-Transitive>true</Embed-Transitive>
<Embed-Directory>lib</Embed-Directory>
<Bundle-ClassPath>{maven-dependencies}</Bundle-ClassPath>
<_exportcontents>*</_exportcontents>
<_failok>true</_failok>
<_nouses>true</_nouses>
<Import-Package></Import-Package>
</instructions>
</configuration>
</plugin>
</plugins>
</build>

然后正常添加一些依赖到这个工程中。然后执行一下mvn clean install ,你就会发现本地mvn仓库生成了一个jar包,这个jar里直接打包了所有的jar

关于插件部分

  1. 新建一个类型为pom的parent工程。用来包含下面的子工程,通过
    1
    2
    3
    4
    <modules>
    <module>xxx.plugin.1</module>
    <module>xxx.plugin.2</module>
    </modules>

来管理。同时 添加如下的插件

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
<properties>
<tycho.version>0.24.0</tycho.version>
</properties>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<executions>
<execution>
<id>attach-sources</id>
<phase>none</phase>
</execution>
</executions>
<version>2.4</version>
</plugin>
<plugin>
<groupId>org.eclipse.tycho</groupId>
<artifactId>tycho-maven-plugin</artifactId>
<version>${tycho.version}</version>
<extensions>true</extensions>
</plugin>
<plugin>
<groupId>org.eclipse.tycho</groupId>
<artifactId>target-platform-configuration</artifactId>
<version>${tycho.version}</version>
<configuration>
<pomDependencies>consider</pomDependencies>
</configuration>
</plugin>
</plugins>
</build>
  1. 新建一个专门用来管理依赖的features,这样用户就可以安装这个features,来把所有的依赖安装到eclipse插件目录了。packing是eclipse-feature

  2. 新建一个plugin,新建一个这个插件对应的feature。然后通过右键转换成mvn工程。如图

mvn转换

  1. 新建一个repository项目,这个项目主要就是为了发布,生成发布相关的文件。新建Update set project。然后添加两个features。转换成mvn。其中packing改成eclipse-repository。
    注意,这里的版本号必须对应。比如plugin.xml的版本是1.5.0.qualifier,那么对应pom中必须是1.5.0-SNAPSHOT

然后去根目录下执行一下mvn clean install,就会在repository目录生成需要部署的zip文件或者web site文件

遗留问题是以后要是升级版本号怎么办。。不用担心。。主pom中添加

1
2
3
4
5
<plugin>
<groupId>org.eclipse.tycho</groupId>
<artifactId>tycho-versions-plugin</artifactId>
<version>${tycho.version}</version>
</plugin>

然后 在根目录执行 mvn -Dtycho.mode=maven org.eclipse.tycho:tycho-versions-plugin:set-version -DnewVersion=1.6.0 即可全部替换pom和plugin的版本号

另外如果项目导入后在eclipse中报错,一般是因为缺少了上面说的那个依赖。可以直接复制到eclipse的plugin依赖目录。或者在mvn clean install之后,通过安装 依赖feature的方式解决。

附上一些文档

EclipseTycho
Building OSGi project with Maven and tycho

注释和共享

ansible简单使用

发布在 学习笔记

由于线下机器太多.有没有日志平台,所以查询日志比较麻烦.发现了ansible,按照官方文档(ubuntu)

1
2
3
4
$ sudo apt-get install software-properties-common
$ sudo apt-add-repository ppa:ansible/ansible
$ sudo apt-get update
$ sudo apt-get install ansible

一步步执行,第二步执行的时候,可能会报错

1
sudo: add-apt-repository: command not found

这时候.先执行

1
2
$ sudo add-apt-repository ppa:git-core/ppa
$ sudo apt-get update

然后接着执行上面的第二步就行了..

安装完成后,配置集群

1
cat /etc/ansible/hosts

1
2
3
4
[servergroup1]
192.168.1.1 ansible_ssh_user=root ansible_ssh_pass=root
[servergroup2]
192.168.2.1 ansible_ssh_user=root ansible_ssh_pass=root

后面的账号和密码,如果你使用ssh key登陆的话就不需要了.但是如果有很多机器,需要加到known_hosts就太多了.
这时候可以参考Batch_key
这个脚本.稍微修改一下,就能批量生成了

然后就是执行命令了

ansible常见用法为ansible host-pattern -m 模块 -a 命令,host-pattern类似于简化的正则表达式,而模块可以通过ansible-doc -l命令来查询。下面是一些常用模块的使用方法:

安装软件:ansible servergroup1 -m apt -a 'name=gcc state=present' 或者ansible local -m yum -a "name=nmap state=installed"

执行命令:ansible servergroup1 -m shell -a 'uptime'

拷贝文件:ansible servergroup1 -m copy -a 'src=/tmp/server dest=/tmp/server'

文件属性:ansible servergroup1 -m file -a 'dest=/tmp/server mode=755 owner=root group=root'

还有一个playbook的,看上去就是一个任务定义.我也暂时用不上..

参考文档:

  1. http://docs.ansible.com/ansible/intro_installation.html

  2. http://lifeonubuntu.com/ubuntu-missing-add-apt-repository-command/

  3. http://www.cnblogs.com/feisky/p/4102613.html

注释和共享

python3.5 安装 Paramiko

发布在 学习笔记

最近由于一些需求,要搞一下python,于是周末搞了搞.要连接服务器,进行一些服务器的操作,于是安装这个Paramiko包,

直接pip install paramiko 结果.报错,最关键的一句是:

1
error: Unable to find vcvarsall.bat

google一圈.最终找到一种最简单地方法.其他的安装vs.安装MinGW都太复杂了.

  1. 安装PyCrypto 第三方版
    因为paramiko依赖PyCrypto,上面那个错就是他报错出来的.安装PyCrypto第三方版
    1
    2
    pip install --use-wheel --no-index --find-links=https://github.com/sfbahr/PyCrypto-Wheels/raw/master/pycrypto-2.6.1-cp35-none-win_amd64.whl pycrypto
    `

安装完成后,再次安装paramiko即可.
2.修改nt.py

安装完上面的步骤,写一个简单的程序测试下

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
#-*- coding: utf-8 -*-
#!/usr/bin/python
import paramiko
import threading

def ssh2(ip,username,passwd,cmd):
try:
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(ip,22,username,passwd,timeout=5)
for m in cmd:
stdin, stdout, stderr = ssh.exec_command(m)
out = stdout.readlines()
#屏幕输出
for o in out:
print(o)
print('%s\tOK\n'%(ip))
ssh.close()
except :
print('%s\tError\n'%(ip))


if __name__=='__main__':
cmd = ['find /home/admin/logs/ -mtime +3 -name \'*.log.*\' -exec rm -rf {} \;']#你要执行的命令列表
username = "admin" #用户名
passwd = "password" #密码
threads = [] #多线程
ip = "127.0.0.1"
print("Begin......")
a=threading.Thread(target=ssh2,args=(ip,username,passwd,cmd))
a.start()
input()

运行报错.
ImportError: No module named 'winrandom'

so一下..找到一个办法
http://stackoverflow.com/questions/24804829/another-one-about-pycrypto-and-paramiko

找到python3.5的安装目录的
Lib\site-packages\Crypto\Random\OSRNG的nt.py文件将

1
import winrandom

改成

1
from . import winrandom

再次运行ok.非常简单

注释和共享

最近在工作中,遇到一个问题,项目中某处使用了json-lib的2.4-jdk15版本.问题最终简化为

1
2
3
4
5
double amount = 6264583.33;
String jsonString = "{\"pi\":" + amount + "}";
JSONObject jsonObject = JSONObject.fromObject(jsonString);
System.out.println("转换前:" + jsonString);
System.out.println("转换后:" + jsonObject);

这个值输出的将会是6264583.5 这个值.这个问题.先google一下,很快赵到了
http://sourceforge.net/p/json-lib/bugs/116/ 于是,大概问题知道了.是json-lib的一个bug,但是这个bug怎么来的呢.结合这个bug下面的评论和debug代码,先以pi这个例子,很快走到了.

json_create

可以看到json-lib走到了apache common-lang(2.5这个版本) 的NumberUtils.createNumber处,此时String的还是对的.
继续单步,来到这个方法里面

json_mant_dec

可以看到这里小数部分,整数部分也都还是对的.继续向下走.我擦.画风不太对.居然采用了先尝试float,发现没问题.然后就继续尝试double,我擦.直接数据就丢失了呀..

json_float

json_float_fluent

好吧..那么现在问题就便成了更简单的一个问题.

使用NumberUtils.createNumber 的bug.在bug issue里,有人提到.这个bug,apache官方已知.好的.
https://issues.apache.org/jira/browse/LANG-693
然后在这里有官方的一次修复,修复记录在这里.3.2版本已经修复.
http://svn.apache.org/viewvc?view=revision&revision=1484263
可以看到是对小数部分的长度进行了判断.如果小于7位,就用float转换,如果大于7,小于16,就用double,如果还大,就用BigDecimal.

json_common_lang3_fix

1
2
n = org.apache.commons.lang3.math.NumberUtils.createNumber("3.14159265358");
System.out.println("lang3_createNumber_3.14159265358---->" + n + "->精度正常");

于是我继续debug,看common-lang3的修复情况,好像确实是修复了.但是对于我出现的问题1.6264583.33 这个数字,还是出现了精度丢失,因为这里小数部分小于7位,所以尝试使用float转换,直接丢失精度

lang3_float_loss

修复不完善..

于是提个bug :https://issues.apache.org/jira/browse/LANG-1187 等回复.

继续.公司内部一般使用fastjson,那么如果我使用fastjson,有问题吗? 发现没有问题.

1
2
3
4
5
Object o = com.alibaba.fastjson.JSONObject.parse("3.14159265358");
System.out.println("fastjson_createNumber_3.14159265358---->" + o + "->精度正常");

o = com.alibaba.fastjson.JSONObject.parse("6264583.33");
System.out.println("fastjson_createNumber_6264583.33---->" + o + "->精度正常");

fastjson_decimal

可以看到,这里做转换的时候传递了一个是否是bigdecimal的标识.而这个标识默认是开启的.而且即使不开启..

fastjson_first_decimal

最坏的情况也是个double.所以数据不会丢失.

再顺便说一下,double的6264583.33 为什么转换到float会精度丢失,先看一下浮点数在计算机中怎么表示的
double_present
找到一张图,这是double的标识和浮点数的计算.

而浮点数则是32位,1位符号位,8位幂,23位尾数,看测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//double标识测试
double d = 6264583.3d;
long l = Double.doubleToLongBits(d);
System.out.println(Long.toBinaryString(l));

//float想要表示这个数字
float f = 6264583.3f;
int value = Float.floatToIntBits(f);
System.out.println(Integer.toBinaryString(value));

//double表示这个值
d = 6264583.d;
l = Double.doubleToLongBits(d);
System.out.println(Long.toBinaryString(l));

输出结果(做一下分割对齐)

1
2
3
1 00000101010    111111001011100000111010101000111101011100001010010
1 00101010 1111110010111000001111
1 00000101010 111111001011100000111100000000000000000000000000000

注意看,第一行是6264583.33的double表示.而同样想要用float表示这个数字,发现幂,符号位,都是对的.但是因为尾数只有23位,所以四舍五入,将完整double的后几位进位1,变成了这个二进制表示法,这时候已经不准确了,
而这个数字呢.看第三行,会发现实际上是6264583.5的精确值表示.尾数位0都是可以省略的,因为按照公式计算也没啥作用.

如有问题,欢迎评论讨论.

附录:
完整的测试代码

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
public class App {
public static void main(String[] args) {
//http://sourceforge.net/p/json-lib/bugs/116/

//2.4版本有问题
double pi = 3.14159265358;
String jsonString = "{\"pi\":" + pi + "}";
JSONObject jsonObject = JSONObject.fromObject(jsonString);
System.out.println("转换前:" + jsonString);
System.out.println("转换后:" + jsonObject);

double amount = 6264583.33;
jsonString = "{\"pi\":" + amount + "}";
jsonObject = JSONObject.fromObject(jsonString);
System.out.println("转换前:" + jsonString);
System.out.println("转换后:" + jsonObject);

//测试2.4版本引入的lang,这里面
Number n = org.apache.commons.lang.math.NumberUtils.createNumber("3.14159265358");
System.out.println("lang2_createNumber_3.14159265358---->" + n + "->精度丢失");

n = org.apache.commons.lang.math.NumberUtils.createNumber("6264583.33");
System.out.println("lang2_createNumber_6264583.33---->" + n + "->精度丢失");

//测试3.4版本,里面是根据小数部分的长度,选择是否使用float还是double,当小数部分大于7的时候,就会使用double
n = org.apache.commons.lang3.math.NumberUtils.createNumber("3.14159265358");
System.out.println("lang3_createNumber_3.14159265358---->" + n + "->精度正常");

//这种情况就会有问题,虽然小数部分是33,两位,但是实际上是个浮点数.所以还会丢失精度
n = org.apache.commons.lang3.math.NumberUtils.createNumber("6264583.33");
System.out.println("lang3_createNumber_6264583.33---->" + n + "->精度丢失");

//测试fastjson

Object o = com.alibaba.fastjson.JSONObject.parse("3.14159265358");
System.out.println("fastjson_createNumber_3.14159265358---->" + o + "->精度正常");

o = com.alibaba.fastjson.JSONObject.parse("6264583.33");
System.out.println("fastjson_createNumber_6264583.33---->" + o + "->精度正常");

//double标识测试
double d = 6264583.33d;
long l = Double.doubleToLongBits(d);
System.out.println(Long.toBinaryString(l));

//float想要表示这个数字
float f = 6264583.33f;
int value = Float.floatToIntBits(f);
System.out.println(Integer.toBinaryString(value));

//double表示这个值
d = 6264583.5d;
l = Double.doubleToLongBits(d);
System.out.println(Long.toBinaryString(l));
}
}

注释和共享

zookeeper伪集群部署

发布在 学习笔记

zookeeper是用来管理分布式环境的系统主要用来服务发现,配置管理,同步.大致原理是zookeeper 自身集群的每个节点都维护这一个目录树,内容相同,每个节点的数据一致性由zookeeper自身的算法来解决.下篇尝试.zookeeper本篇主要说明如果部署zookeeper的分布式环境.

下载

zookeeper由apache在管理,下载地址:http://www.apache.org/dyn/closer.cgi/zookeeper/.下载完成后,随便放个目录好了..

配置

本次创建3个节点.
1 . 存储目录准备
首先给每个伪节点创建一个目录.用来存储每个节点保存的目录信息.真实的分布式环境将对应在不同的机器上.
这里我在D:\zookeeper,创建三个目录,分别是zk1,zk2,zk3.
然后为每个集群编写一个myid文件,标识集群id

2 . 启动配置文件
下载完成后,在conf目录会看到由一个zoo_sample.cfg实例配置文件,我们可以以这个为模板.来为分布式环境的每个zookeeper节点配置一个节点的数据目录,端口.其他节点的信息等.

我们在conf目录例创建三个配置文件,分别为zk1.cfg,zk2.cfg,zk3.cfg;
里面的值
zk1.cfg

1
2
3
4
5
6
7
8
tickTime=2000 
initLimit=10
syncLimit=5
dataDir=D:/zookeeper/zk1
clientPort=2181
server.1=127.0.0.1:2888:3888
server.2=127.0.0.1:2889:3889
server.3=127.0.0.1:2890:3890

zk2.cfg

1
2
3
4
5
6
7
8
tickTime=2000 
initLimit=10
syncLimit=5
dataDir=D:/zookeeper/zk2
clientPort=2182
server.1=127.0.0.1:2888:3888
server.2=127.0.0.1:2889:3889
server.3=127.0.0.1:2890:3890

zk1.cfg

1
2
3
4
5
6
7
8
tickTime=2000 
initLimit=10
syncLimit=5
dataDir=D:/zookeeper/zk3
clientPort=2183
server.1=127.0.0.1:2888:3888
server.2=127.0.0.1:2889:3889
server.3=127.0.0.1:2890:3890

这里的server.1.2.3这就是每个机器对应的myid的值.
server.1=127.0.0.1:2888:3888解释一下这条配置.前面的2888是各个节点用来互相交流.选取leader的端口.后面这个端口,3888是各个节点用来和leader沟通的节点.而clientPort 是开放出去,等待客户端连接的端口.

启动

分别启动三个实例,在zookeeper的安装目录下.进如bin目录,复制三个zkServer.cmd 文件,要是linux就不用这么麻烦了..

分别加上一行

set ZOOCFG=../conf/zk1.cfg

最终这个文件像这样

1
2
3
4
5
6
7
8
9
setlocal
call "%~dp0zkEnv.cmd"

set ZOOMAIN=org.apache.zookeeper.server.quorum.QuorumPeerMain
set ZOOCFG=../conf/zk1.cfg
echo on
java "-Dzookeeper.log.dir=%ZOO_LOG_DIR%" "-Dzookeeper.root.logger=%ZOO_LOG4J_PROP%" -cp "%CLASSPATH%" %ZOOMAIN% "%ZOOCFG%" %*

endlocal

然后直接双击启动zkServer1.cmd,zkServer2.cmd,zkServer3.cmd

刚启动第一个之后,你会看到有报错,是zookeeper进行选举的时候报错的.因为第一个zk节点.从自己的启动配置里,知道还有两个节点,于是尝试连接.但是连接不上,再启动另外两个.都启动后,报错消失

然后在D:\zookeeper中可以看到由数据写入.
zk数据目录

测试

启动bin目录的zkCli.cmd,自动连接本机的2181端口.也可以自己指定
zkCli.cmd –server 127.0.0.1:2181,127.0.0.1:2182,127.0.0.1:2183
对客户端来说.连接上了一个列表之后,如果一台挂了,并不会影响.系统依旧可以运行.

然后执行一些简单的操作
zk测试结果

显示根目录下、文件: ls / 使用 ls 命令来查看当前 ZooKeeper 中所包含的内容。
显示根目录下、文件: ls2 / 查看当前节点数据并能看到更新次数等数据。
创建文件,并设置初始内容: create /zktest “test” 创建一个新的 znode节点“ zk ”以及与它关联的字符串。
获取文件内容: get /zktest 确认 znode 是否包含我们所创建的字符串。
修改文件内容: set /zktest “zkbak” 对 zk 所关联的字符串进行设置。
删除文件: delete /zktest 将刚才创建的 znode 删除。
退出客户端: quit
帮助命令: help

可以关掉一个服务器,会发现客户端依然正常.可以执行get set等操作.

注释和共享

前几天被前辈问到这个可重入锁,结果忘掉了.于是抽空整个了解一下

目录

  1. 什么是可重入锁
  2. 为什么要可重入
  3. 如何实现可重入锁
  4. 有不可重入锁吗
  5. demo代码展示
  6. 参考文章

1 . 什么是可重入锁

锁的概念就不用多解释了,当某个线程A已经持有了一个锁,当线程B尝试进入被这个锁保护的代码段的时候.就会被阻塞.而锁的操作粒度是”线程”,而不是调用(至于为什么要这样,下面解释).同一个线程再次进入同步代码的时候.可以使用自己已经获取到的锁,这就是可重入锁
java里面内置锁(synchronize)和Lock(ReentrantLock)都是可重入的

2 . 为什么要可重入

如果线程A继续再次获得这个锁呢?比如一个方法是synchronized,递归调用自己,那么第一次已经获得了锁,第二次调用的时候还能进入吗? 直观上当然需要能进入.这就要求必须是可重入的.可重入锁又叫做递归锁,再举个例子.

1
2
3
4
5
6
7
8
9
10
11
12
public class Widget {
public synchronized void doSomething() {
...
}
}

public class LoggingWidget extends Widget {
public synchronized void doSomething() {
System.out.println(toString() + ": calling doSomething");
super.doSomething();//若内置锁是不可重入的,则发生死锁
}
}

这个例子是java并发编程实战中的例 子.synchronized 是父类Widget的内置锁,当执行子 类的方法的时候,先获取了一次Widget的锁,然后在执行super的时候,就要获取一次,如果不可重入,那么就跪了.

3 . 如何实现可重入锁

为每个锁关联一个获取计数器和一个所有者线程,当计数值为0的时候,这个所就没有被任何线程只有.当线程请求一个未被持有的锁时,JVM将记下锁的持有者,并且将获取计数值置为1,如果同一个线程再次获取这个锁,技术值将递增,退出一次同步代码块,计算值递减,当计数值为0时,这个锁就被释放.
ReentrantLock里面有实现

4 . 有不可重入锁吗

这个还真有.Linux下的pthread_mutex_t锁是默认是非递归的。可以通过设置PTHREAD_MUTEX_RECURSIVE属性,将pthread_mutex_t锁设置为递归锁。如果要自己实现不可重入锁,同可重入锁,这个计数器只能为1.或者0,再次进入的时候,发现已经是1了,就进行阻塞.jdk里面没有默认的实现类.

5 . demo代码展示

5.1 内置锁的可重入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class ReentrantTest {
public void method1() {
synchronized (ReentrantTest.class) {
System.out.println("方法1获得ReentrantTest的内置锁运行了");
method2();
}
}

public void method2() {
synchronized (ReentrantTest.class) {
System.out.println("方法1里面调用的方法2重入内置锁,也正常运行了");
}
}

public static void main(String[] args) {
new ReentrantTest().method1();
}
}

5.2 lock对象的可重入

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
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockTest {
private Lock lock = new ReentrantLock();

public void method1() {
lock.lock();
try {
System.out.println("方法1获得ReentrantLock锁运行了");
method2();
} finally {
lock.unlock();
}
}

public void method2() {
lock.lock();
try {
System.out.println("方法1里面调用的方法2重入ReentrantLock锁,也正常运行了");
} finally {
lock.unlock();
}
}

public static void main(String[] args) {
new ReentrantLockTest().method1();
}
}

5.3 不同线程不可访问同一锁

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
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ReentrantTwoThreadTest {
private static Lock lock = new ReentrantLock();

private static class T1 extends Thread {
@Override
public void run() {
System.out.println("线程1启动");
lock.lock();
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
System.out.println("线程2终止");
}
}

private static class T2 extends Thread {
@Override
public void run() {
System.out.println("线程2启动");
lock.lock();
lock.unlock();
System.out.println("线程2终止");
}
}


public static void main(String[] args) {
new T1().start();
Thread.sleep(100);
new T2().start();
}
}

6. 参考文章

  1. 可重入锁测试
  2. 生产者消费者的一个更真实的例子
  3. 浅谈Java中的锁
  4. java并发编程实战

注释和共享

bystander

寻找窄门


全栈工程师


China