0%

一、说明

利用redis操作的原子性,实现Java 多线程并发的情况下实现计数器。

我本机测试多个线程操作之后,结果会出现一定的延迟,但是最终数字是ok的

应该是redis内部做了一个类似于队列的功能。

需要注意的是,得使用redis连接的线程池,不然会出现异常

这里有一个:JedisUtil 下面用到了

二、 代码实现

2.1 redis操作类

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
package com.hisen.thread.count_click_by_redis;

import com.hisen.utils.JedisUtil;
import redis.clients.jedis.JedisPool;
/**
* @author hisenyuan
* @description 操作redis的线程类
*/
public class ClickRedis {

/**
* 必须使用线程池,而且线程池要大于并发数,否则会出现redis超时
*/
private static JedisPool jedis = JedisUtil.getPool();

public static void click() {
jedis.getResource().incrBy("hisen", 1);
}

public static int getCount() {
return Integer.parseInt(jedis.getResource().get("hisen"));
}

public static void declare() {
jedis.getResource().del("hisen");
jedis.close();
}
}

2.2 线程类,模拟点击

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.hisen.thread.count_click_by_redis;

/**
* @author hisenyuan
* @description 执行点击的线程类
*/
public class CountClickByRedisThread extends Thread{

private int id;
public CountClickByRedisThread(int id) {
this.id = id;
}

@Override
public void run() {
super.run();
ClickRedis.click();
int count = ClickRedis.getCount();
System.out.println("task:" + id + "\t 执行完毕\t线程编号:" + this.getId() + "\t当前值:" + count);
}
}

2.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
40
41
42
package com.hisen.thread.count_click_by_redis;

import Java.util.concurrent.ArrayBlockingQueue;
import Java.util.concurrent.ThreadPoolExecutor;
import Java.util.concurrent.TimeUnit;

public class Main {

public static void main(String[] args) throws InterruptedException {
/**
* 5 - corePoolSize:核心池的大小
* 10 - maximumPoolSize:线程池最大线程数,它表示在线程池中最多能创建多少个线程
* 200 - keepAliveTime:表示线程没有任务执行时最多保持多久时间会终止。
* unit - unit:参数keepAliveTime的时间单位,有7种取值
* workQueue:一个阻塞队列,用来存储等待执行的任务
*/
ThreadPoolExecutor executor = new ThreadPoolExecutor(
50,
100,
200,
TimeUnit.MICROSECONDS,
new ArrayBlockingQueue<Runnable>(50));
// 开启50个线程
for (int i = 0; i < 50; i++) {
executor.execute(new CountClickByRedisThread(i));
}
System.out.println("已经开启所有的子线程");
// 启动一次顺序关闭,执行以前提交的任务,但不接受新任务。
executor.shutdown();
// 判断所有线程是否已经执行完毕
while (true) {
if (executor.isTerminated()) {
System.out.println("所有的子线程都结束了!");
// 清除redis数据
ClickRedis.declare();
break;
}
Thread.sleep(100);
}
}

}

零、rabbitMQ介绍

rabbitMQ详细介绍

  1. 如果某个queue有多个订阅,消息分均分到消费者,而不是所有人都收到全部
  2. 接收消息有ack(acknowledgment)机制,发送消息是没有这个机制的
  3. 生产者将消息发送到Exchange(交换器),由Exchange将消息路由到一个或多个Queue中(或者丢弃)。

一、在ununtu上安装

1.1 安装

1
2
3
4
5
6
7
echo 'deb http://www.rabbitmq.com/debian/ testing main' | sudo tee /etc/apt/sources.list.d/rabbitmq.list

wget -O- https://www.rabbitmq.com/rabbitmq-release-signing-key.asc | sudo apt-key add -

sudo apt-get update

sudo apt-get install rabbitmq-server

1.2 配置

1
2
3
4
5
6
7
8
9
10
11
# 打开管理页面功能
sudo rabbitmq-plugins enable rabbitmq_management
# 查看安装的插件
sudo rabbitmqctl list_users
# 查看用户
sudo rabbitmqctl list_users
# 新增管理员用户
sudo rabbitmqctl add_user admin admin
# 授予管理员权限
sudo rabbitmqctl set_user_tags admin administrator
# 管理页面地址,用刚设置的账户登录管理页面

http://127.0.0.1:15672

二、Java小Demo

2.1 遇到的问题

  1. ACCESS_REFUSED - Login was refused using authentication mechanism PLAIN.
    帐号密码错误,建议使用2.3配置的账户,guest账户不靠谱

  2. connection error
    ip或者port错误,确认信息是否正确,虚拟机的话看看端口映射是否正常

2.2 配置用户

1
2
3
4
5
6
# 添加普通用户
sudo rabbitmqctl add_user hisen hisen
# 添加权限
rabbitmqctl set_permissions -p "/" hisen ".*" ".*" ".*"
# 列出用户权限
rabbitmqctl list_user_permissions hisen

2.3 添加maven依赖

1
2
3
4
5
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.0.0</version>
</dependency>

2.4 发送端代码

Read more »

使用ubuntu官方发布的docker镜像进行二次修改

这是一个菜鸟的脚本,执行命令应该是使用 & 连接,一个RUN命令搞定

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
FROM    ubuntu
MAINTAINER Fisher "hisenyuan@gmail.com"
RUN /bin/echo 'root:hisen' |chpasswd
RUN useradd hisen
RUN /bin/echo 'hisen:hisen' |chpasswd
RUN /bin/echo -e "LANG=\"en_US.UTF-8\"" >/etc/default/local
# 显示系统位数
RUN uname -p
# 清空源
RUN echo "" > /etc/apt/sources.list
# 更换为阿里云源
RUN echo "deb http://mirrors.aliyun.com/ubuntu/ xenial main restricted universe multiverse" >> /etc/apt/sources.list
RUN echo "deb http://mirrors.aliyun.com/ubuntu/ xenial-security main restricted universe multiverse" >> /etc/apt/sources.list
RUN echo "deb http://mirrors.aliyun.com/ubuntu/ xenial-updates main restricted universe multiverse" >> /etc/apt/sources.list
RUN echo "deb http://mirrors.aliyun.com/ubuntu/ xenial-proposed main restricted universe multiverse" >> /etc/apt/sources.list
RUN echo "deb http://mirrors.aliyun.com/ubuntu/ xenial-backports main restricted universe multiverse" >> /etc/apt/sources.list
RUN echo "deb-src http://mirrors.aliyun.com/ubuntu/ xenial main restricted universe multiverse" >> /etc/apt/sources.list
RUN echo "deb-src http://mirrors.aliyun.com/ubuntu/ xenial-security main restricted universe multiverse" >> /etc/apt/sources.list
RUN echo "deb-src http://mirrors.aliyun.com/ubuntu/ xenial-updates main restricted universe multiverse" >> /etc/apt/sources.list
RUN echo "deb-src http://mirrors.aliyun.com/ubuntu/ xenial-proposed main restricted universe multiverse" >> /etc/apt/sources.list
RUN echo "deb-src http://mirrors.aliyun.com/ubuntu/ xenial-backports main restricted universe multiverse" >> /etc/apt/sources.list
# 更新源
RUN apt-get update
# 安装软件
RUN apt-get -y install vim
RUN apt-get -y install curl
RUN apt-get -y install wget
RUN apt-get -y install net-tools
RUN apt-get -y install iputils-ping
RUN apt-get -y install git
# 创建软件文件夹
RUN mkdir -p /usr/hisen/soft/Java
RUN mkdir -p /usr/hisen/soft/tomcat
RUN mkdir -p /usr/hisen/soft/maven
RUN mkdir -p /usr/hisen/soft/download
# 添加本地软件包到指定文件夹(会自动解压,软件压缩包必须放在docker同级目录)
ADD jdk-8u151-linux-x64.tar.gz /usr/hisen/soft/Java/
ADD apache-tomcat-8.5.24.tar.gz /usr/hisen/soft/tomcat/
ADD apache-maven-3.5.2-bin.tar.gz /usr/hisen/soft/maven/

# 配置环境变量
# Java
ENV JAVA_HOME=/usr/hisen/soft/Java/jdk1.8.0_151
ENV JRE_HOME=$JAVA_HOME/jre
ENV CLASSPATH=.:$CLASSPATH:$JAVA_HOME/lib:$JRE_HOME/lib
ENV PATH=$PATH:$JAVA_HOME/bin:$JRE_HOME/bin
# tomcat
ENV CATALINA_HOME=/usr/hisen/soft/tomcat/apache-tomcat-8.5.24
ENV CLASSPATH=.:$JAVA_HOME/lib:$CATALINA_HOME/lib
ENV PATH=$PATH:$CATALINA_HOME/bin
# maven
ENV MAVEN_HOME=/usr/hisen/soft/maven/apache-maven-3.5.2
ENV MAVEN_OPTS="-Xms256m -Xmx512m"
ENV PATH=${MAVEN_HOME}/bin:$PATH

# 监听端口
EXPOSE 22
EXPOSE 80
EXPOSE 8080
CMD /usr/sbin/sshd -D

###命令介绍
MySQL的EXPLAIN命令用于SQL语句的查询执行计划(QEP)。

这条命令的输出结果能够让我们了解MySQL 优化器是如何执行

执行下面的SQL

1
explain select * FROM book where name like '活%'

得到下面的数据

idselect_typetablepartitionstypepossible_keyskeykey_lenrefrowsfileredExtra
编号查询方式表名分区连接方式索引(可能)索引索引长度作用列行数百分比额外
1SIMPLEbooknullALLnullnullnullnull11411.11Using where

###建表语句

Read more »

简单理解IOC和AOP

IOC:Inversion of Control,依赖倒置

生活场景助记

如有有天你想喝一瓶矿泉水,你可以去小区便利店,告诉老板你要买矿泉水,然后老板卖给你。

但是你可能需要想这下雨天怎么去小卖部?是否要带伞?去了之后是否有我想要的水等一系列问题。

解决这个问题:

  1. 使用外卖!到平台注册,告诉平台你需要什么水。
  2. 平台给你送到家,你只管付钱拿到水之后直接喝,不用考虑上述问题。

是不是和Spring的做法很类似呢?Spring就是小卖部,你就是A对象,水就是B对象
第一:在Spring中声明一个类:A
第二:告诉Spring,A需要B

Read more »

Zookeeper的作用

zookeeper用来注册服务和进行负载均衡,哪一个服务由哪一个机器来提供必需让调用者知道,简单来说就是ip地址和服务名称的对应关系。

当然也可以 通过硬编码的方式把这种对应关系在调用方业务代码中实现,但是如果提供服务的机器挂掉调用者无法知晓,如果不更改代码会继续请求挂掉的机器提供服务。

zookeeper通过心跳机制可以检测挂掉的机器并将挂掉机器的ip和服务对应关系从列表中删除。至于支持高并发,简单来说就是横向扩展,在不更改代码 的情况通过添加机器来提高运算能力。

通过添加新的机器向zookeeper注册服务,服务的提供者多了能服务的客户就多了。

dubbo

是管理中间层的工具,在业务层到数据仓库间有非常多服务的接入和服务提供者需要调度,dubbo提供一个框架解决这个问题。

注意这里的dubbo只是一个框架,至于你架子上放什么是完全取决于你的,就像一个汽车骨架,你需要配你的轮子引擎。

这个框架中要完成调度必须要有一个分布式的注册中心,储存所有服务的元数据,你可以用zk,也可以用别的,只是大家都用zk。

zookeeper和dubbo的关系

Dubbo的将注册中心进行抽象,是得它可以外接不同的存储媒介给注册中心提供服务,有ZooKeeper,Memcached,Redis等。

引入了ZooKeeper作为存储媒介,也就把ZooKeeper的特性引进来。

  1. 负载均衡:单注册中心的承载能力是有限的,在流量达到一定程度的时 候就需要分流,负载均衡就是为了分流而存在的,一个ZooKeeper群配合相应的Web应用就可以很容易达到负载均衡;
  2. 资源同步:单单有负载均衡还不 够,节点之间的数据和资源需要同步,ZooKeeper集群就天然具备有这样的功能;
  3. 命名服务:将树状结构用于维护全局的服务地址列表,服务提供者在启动 的时候,向ZK上的指定节点/dubbo/${serviceName}/providers目录下写入自己的URL地址,这个操作就完成了服务的发布
  4. Mast选举,分布式锁等。

参考

https://www.cnblogs.com/xiaofei1208/p/7077733.html

名词解释

  1. 脏读: 对于两个事物 T1, T2, T1 读取了已经被 T2 更新但还没有被提交的字段. 之后, 若 T2 回滚, T1读取的内容就是临时且无效的.
  2. 不可重复读: 对于两个事物 T1, T2, T1 读取了一个字段, 然后 T2 更新了该字段. 之后, T1再次读取同一个字段, 值就不同了.
  3. 幻读: 对于两个事物 T1, T2, T1 从一个表中读取了一个字段, 然后 T2 在该表中插入了一些新的行. 之后, 如果 T1 再次读取同一个表, 就会多出几行.

数据库的4个事物隔离级别

√:可能会出现
×:为不会出现

name名称级别脏读不可重复读幻读
Read uncommitted读未提交1
Read committed读提交2×
Repeatable read重复读3××
Serializable序列化4×××

oracle

Oracle 支持的 2 种事务隔离级别:READ COMMITED, SERIALIZABLE.

Oracle 默认的事务隔离级别为: READ COMMITED

mysql

Mysql 支持 4 中事务隔离级别.

Mysql 默认的事务隔离级别为: REPEATABLE READ

延伸

参考:https://www.cnblogs.com/andy6/p/6045679.html

定义

最多有两棵子树的有序树,称为二叉树。二叉树是一种特殊的树。

性质

这里规定二叉树的根结点的层次为1。

  1. 性质1:则二叉树的第i 层最多有2i-1个结点(在此二叉树的层次从1开始,i≥1)
  2. 性质2:深度为k的二叉树最多有2k-1个结点。(k≥1)
  3. 性质3:对任何一棵二叉树T, 如果其叶结点个数为n0, 度为2的非叶结点个数为n2, 则有
    n0 = n2 + 1
  4. 性质4:具有 n(n>0)个结点的完全二叉树的深度为⎣log2n⎦+1;⎦x⎦表示不超过x的最大整数。
  5. 性质5:如果对一棵有n个结点的完全二叉树的结点按层序编号(从第1层到第⎣l og2n⎦ +1层,每层从左到右),则对任一结点i(1≤i≤n),有:
    5.1 (1)如果i=1,则结点i无双亲,是二叉树的根;如果i>1,则其双亲是结点⎣i/2⎦。
    5.2 (2) 如果2i<=n, 则结点i的左孩子结点是2i;否则,结点i为叶子结点,无左孩子结点。
    5.3 (3)如果2i+1<=n,则结点i的右孩子是结点2i+1; 否则,结点i为叶子结点,无右孩子结点。

完整代码

https://github.com/hisenyuan/btree

二叉链表的实现

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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
package com.hisen.interview.tiger20171110.btree;

/**
* @author : yhx
* @date : 2017/11/10 18:42
* @descriptor : 二叉树的 - 二叉链表实现
*/
public class LinkBTree implements BTree {

private Object data;
private BTree lChild;
private BTree rChild;

public LinkBTree() {
this.clearTree();
}

public LinkBTree(Object data) {
this.data = data;
this.rChild = null;
this.lChild = null;
}

@Override
public void addLfetTree(BTree lChild) {
this.lChild = lChild;
}

@Override
public BTree getLfetTree() {
return lChild;
}

@Override
public void addRightTree(BTree rChild) {
this.rChild = rChild;
}

@Override
public BTree getRightTree() {
return rChild;
}

@Override
public void clearTree() {
this.data = null;
this.rChild = null;
this.lChild = null;
}

@Override
public int getDeep() {
return deep(this);
}


@Override
public Object getRootData() {
return data;
}

@Override
public boolean hasLeftTree() {
if (lChild != null) {
return true;
}
return false;
}

@Override
public boolean hasRightTree() {
if (rChild != null) {
return true;
}
return false;
}

@Override
public boolean isEmptyTree() {
if ((lChild == null && rChild == null && data == null) || this == null) {
return true;
}
return false;
}

@Override
public boolean isLeaf() {
if (lChild == null && rChild == null) {
return true;
}
return false;
}

@Override
public void removeLeftTree() {
lChild = null;
}

@Override
public void removeRightTree() {
rChild = null;
}

@Override
public BTree getRoot() {
return this;
}

@Override
public void setRootData() {
this.data = data;
}

@Override
public int size() {
return size(this);
}

private int size(BTree bTree) {
if (bTree == null) {
return 0;
} else if (bTree.isLeaf()) {
return 1;
} else {
if (bTree.getLfetTree() == null) {
return size(bTree.getRightTree()) + 1;
} else if (bTree.getRightTree() == null) {
return size(bTree.getLfetTree()) + 1;
} else {
return size(bTree.getLfetTree()) + size(bTree.getRightTree()) + 1;
}
}
}

/**
* 计算二叉树的高度
*/
private int deep(BTree bTree) {
if (bTree.isEmptyTree()) {
return 0;
} else if (bTree.isLeaf()) {
return 1;
} else {
if (bTree.getLfetTree() == null) {
return deep(bTree.getRightTree()) + 1;
} else if (bTree.getRightTree() == null) {
return deep(bTree.getLfetTree()) + 1;
} else {
return Math.max(deep(bTree.getLfetTree()), deep(bTree.getRightTree())) + 1;
}
}
}
}

二叉树的各种遍历

遍历方式:前序、中序、后序、层次

Read more »