首页 文章 精选 留言 我的

精选列表

搜索[空间理解],共10007篇文章
优秀的个人博客,低调大师

通过Wireshark理解Nmap扫描

自从上次女神节征文获得一盒巧克力之后,我总是盼望着再来一个什么征文。那时正是写得顺手的时候,仿佛觉得自己提笔就是一本爱情小说。奈何幻觉它也是有时效性的,巧克力吃完之后,我又变回到一个技术作者的身份,还是一名文思枯竭的作者。就在这时,51CTO又有征文活动,不参加吧,舍不得京东的购书卷,参加吧,这枯竭状态能挤出什么来?两难之时,远处知识的海洋里泛起玲玲波光,定睛一看,好家伙,Wireshark!就是它了,京东的购书卷就靠它了!让我们先来看看,外国的技术人员怎么使用Wireshark的,之后的博客中,我们再讨论自己的想法。 在本文中,您将学习当***者使用NMAP端口扫描方法扫描目标时,如何使用Wireshark捕获网络数据包。 在这里,您将注意到Wireshark如何捕获用于打开和关闭端口的不同网络流量数据包。 TCP扫描 Tcp扫描将扫描TCP端口(例如端口22、21、23、445等),并通过源端口和目标端口之间的三向握手连接确保侦听端口(打开)。如果端口是开放的,则源发出带有SYN数据包的请求,响应目标发送SYN,ACK数据包,然后源发送ACK数据包,最后,源再次发送RST,ACK数据包。 键入以下NMAP命令以进行TCP扫描,并在另一只手上启动Wireshark以捕获已发送的数据包。 nmap -sT -p 445 192.168.1.102 从给定的图像中,您可以观察到端口445打开的结果。 查看通过Wireshark捕获的源与目标之间的数据包传输顺序。您会注意到它捕获了与上述相同的标志序列: 1)源将SYN数据包发送到目标 2)目标已将SYN,ACK发送到源 3)源将ACK数据包发送到目标 4)源再次发送RST,ACK到目标 让我们找出关闭端口的网络流量。 根据给定的图像,它显示了扫描端口是否关闭,则源和目标之间将无法进行三向握手连接。源发送了SYN包,如果端口关闭,则接收器将通过RST,ACK发送响应。 键入以下NMAP命令以进行TCP扫描,并在另一只手上启动Wireshark以捕获已发送的数据包。 nmap -sT -p 3389 192.168.1.102 从给定的图像中,您可以观察到端口3389关闭的结果。 查看通过Wireshark捕获的源与目标之间的数据包传输顺序。您会注意到它捕获了与上述相同的标志序列: 1)源将SYN数据包发送到目标 2)目标发送了RST,ACK数据包到源 Stealth扫描 出于充分的原因,SYN扫描是默认的也是最受欢迎的扫描选项。 它可以快速执行,在不受限制性防火墙阻碍的快速网络上每秒扫描数千个端口。 由于它从不完成TCP连接,因此它也是相对典型且隐蔽的。 如果收到一个SYN数据包(无ACK标志)作为响应,则该端口也被认为是开放的。 这种技术通常称为半开扫描,因为您没有打开完整的TCP连接。 您发送SYN数据包,就像要打开一个真实的连接,然后等待响应。 SYN,ACK表示端口正在侦听(打开) 键入以下NMAP命令以进行TCP扫描,并在另一只手上启动Wireshark以捕获已发送的数据包。 nmap -sS -p 22 192.168.1.102 从给定的图像中,您可以观察到端口22打开的结果。 查看通过Wireshark捕获的源和目标之间的数据包传输顺序: 1)源将SYN数据包发送到目标 2)目的地向源发送了SYN,ACK数据包 3)源将RST数据包发送到目标 现在使用隐身扫描找出关闭端口的流量。 当源在特定端口上发送SYN数据包时,如果端口关闭,则目标将通过发送RST数据包进行回复。 键入以下NMAP命令以进行TCP扫描,并在另一只手上启动Wireshark以捕获已发送的数据包。 nmap -sS -p 3389 192.168.1.102 从给定的图像中,您可以观察到端口3389关闭的结果。 查看通过Wireshark捕获的源和目标之间的数据包传输顺序。 1)源将SYN数据包发送到目标 2)目标向目标发送了RST,ACK数据包 Fin扫描 通常在数据传输完成后,使用FIN数据包终止源端口和目标端口之间的TCP连接。Nmap代替SYN数据包,通过使用FIN数据包开始FIN扫描。 如果端口是开放的,则当通过源端口发送FIN数据包时,目标端口将没有响应。 Fin-Scan仅在Linux机器上可用,而在最新版本的Windows上不可用。 键入以下NMAP命令以进行TCP扫描,并在另一只手上启动Wireshark以捕获已发送的数据包。 nmap -sF -p 22 192.168.1.102 从给定的图像中,您可以观察到端口22打开的结果。 查看通过Wireshark捕获的源和目标之间的数据包传输顺序: 1)源将FIN数据包发送到目标 2)目的地未回复来源 同样,如果对任何关闭都执行Fin扫描,则将向源端口发送FIN数据包至特定端口,而目标端口将通过发送RST,ACK数据包进行回复。 键入以下NMAP命令以进行TCP扫描,并在另一只手上启动Wireshark以捕获已发送的数据包。 nmap -sF -p 3389 192.168.1.102 从给定的图像中,您可以观察到端口3389关闭的结果。 查看通过Wireshark捕获的源和目标之间的数据包传输顺序。 1)源将SYN数据包发送到目标 2)目标已将RST数据包发送到目标 Null扫描 空扫描是一系列TCP数据包,其序列号为“零”(0000000),并且由于未设置任何标志,因此目的地将不知道如何回复请求。它将丢弃该数据包,并且不会发送任何答复,这表明该端口已打开。 空扫描仅在Linux机器上可用,而在最新版本的Windows上不可用。 键入以下NMAP命令以进行TCP扫描,并在另一只手上启动Wireshark以捕获已发送的数据包。 nmap -sN -p 22 192.168.1.102 从给定的图像中,您可以观察到端口22打开的结果。 查看通过Wireshark捕获的源和目标之间的数据包传输顺序: 1)源将Null数据包发送到目标 2)目的地未回复来源 如果端口关闭,则当源在特定端口上发送空数据包时,目标将发送RST ACK数据包作为响应。 键入以下NMAP命令以进行TCP扫描,并在另一只手上启动Wireshark以捕获已发送的数据包。 nmap -sN -p 3389 192.168.1.102 从给定的图像中,您可以观察到端口3389关闭的结果。 查看通过Wireshark捕获的源和目标之间的数据包传输顺序: 1)源将Null(无)数据包发送到目标 2)目标已将RST,ACK发送到源 UDP扫描 UDP扫描通过将UDP数据包发送到每个目标端口来进行。 这是一个无连接协议。 对于某些常见端口(例如53和161),将发送协议特定的有效负载以提高响应速率,服务将使用UDP数据包进行响应,证明其已打开。重发后,如果未收到响应,则将端口分类为openn|filtered。 这意味着端口可能是开放的,或者数据包筛选器正在阻止通信。 键入以下NMAP命令以进行TCP扫描,并在另一只手上启动Wireshark以捕获已发送的数据包。 nmap -sU -p 161 192.168.1.119 从给定的图像中,您可以观察到端口161打开的结果。 查看通过Wireshark捕获的源和目标之间的数据包传输顺序: 1)源将UDP数据包发送到目标 2)目标发送了带有一些数据的UDP数据包到源 类似地,如果源在关闭端口上向目标发送了UDP数据包,则目标发送了带有无法访问的ICMP数据包端口的回复,并带有相应的错误。 键入以下NMAP命令以进行TCP扫描,并在另一只手上启动Wireshark以捕获已发送的数据包。 nmap -sU -p 53 192.168.1.119 从给定的图像中,您可以观察到端口53关闭的结果。 查看通过Wireshark捕获的源和目标之间的数据包传输顺序: 1)源将UDP数据包发送到目标 2)目标发送的ICMP数据包端口无法到达源 Xmas扫描 这些扫描旨在处理TCP标头的PSH,URG和FIN标志,设置FIN,PSH和URG标志,从而像圣诞树一样照亮数据包。当源将FIN,PUSH和URG数据包发送到特定端口时,如果该端口已打开,则目标将丢弃该数据包,并且将不向源发送任何答复。 Xmas Scan仅在Linux机器上可用,而在最新版本的Windows上不可用。 键入以下NMAP命令以进行TCP扫描,并在另一只手上启动Wireshark以捕获已发送的数据包。 nmap -sX -p 22 192.168.1.102 从给定的图像中,您可以观察到端口22打开的结果。 查看通过Wireshark捕获的源和目标之间的数据包传输顺序: 1)源将FIN,PUSH和URG数据包发送到目标 2)目的地未回复来源 同样,如果源将FIN,PUSH和URG数据包发送到特定端口,并且如果端口关闭,则目标将向源发送RST,ACK数据包。 键入以下NMAP命令以进行TCP扫描,并在另一只手上启动Wireshark以捕获已发送的数据包。 nmap -sX -p 3389 192.168.1.102 从给定的图像中,您可以观察到端口3389关闭的结果。 查看通过Wireshark捕获的源和目标之间的数据包传输顺序: 1)源将FIN,PUSH和URG数据包发送到目标 2)目标RST,ACK数据包到源 亲爱的读者请注意,此为译文,原文来自网络,早前找好的参考资料,现在原文的地址我也忘了,如果涉及版权问题,请及时联系我。

优秀的个人博客,低调大师

深入理解 JUC:CountDownLatch

CountDownLatch 是一个同步辅助工具,用于阻塞当前一个或多个线程以等待其它线程中的操作完成。在构造 CountDownLatch 对象时,我们需要指定一个非负的 count 值,一般情况下,调用 CountDownLatch#await 方法的线程需要阻塞等待该 count 值变为 0 时才能够继续往下执行。具体示例可以参考 理清 CountDownLatch 与 CyclicBarrier 的区别。 CountDownLatch 实现内幕 CountDownLatch 在实现上同样依赖于 AQS 组件,在 CountDownLatch 的内部定义了一个 Sync 内部类,该类继承自 AbstractQueuedSynchronizer,并复用 AQS 的 state 字段以记录 count 值的变化。CountDownLatch 的核心方法均委托 Sync 进行处理,实现如下: public class CountDownLatch { private final Sync sync; public CountDownLatch(int count) { if (count < 0) { throw new IllegalArgumentException("count < 0"); } this.sync = new Sync(count); } public void await() throws InterruptedException { sync.acquireSharedInterruptibly(1); } public boolean await(long timeout, TimeUnit unit) throws InterruptedException { return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout)); } public void countDown() { sync.releaseShared(1); } public long getCount() { return sync.getCount(); } } 下面我们主要分析一下 CountDownLatch#await 和 CountDownLatch#countDown 方法的实现。 首先来看一下 CountDownLatch#await 方法,该方法用于阻塞当前线程,直到 count 值变为 0,或者被其它线程中断。具体实现上,CountDownLatch 直接将请求委托给 AQS 的 AbstractQueuedSynchronizer#acquireSharedInterruptibly 方法进行处理,所以我们下面主要来看一下 CountDownLatch 针对模板方法 AbstractQueuedSynchronizer#tryAcquireShared 的实现,如下: protected int tryAcquireShared(int acquires) { return (getState() == 0) ? 1 : -1; } 实现上非常简单,获取并判定状态值是否为 0,如果是则说明当前线程获取资源成功,能够继续往下执行,否则说明当前线程获取资源失败,需要被添加到同步队列阻塞等待。CountDownLatch 还为 CountDownLatch#await 方法定义了超时版本 CountDownLatch#await(long, TimeUnit)。 继续来看一下 CountDownLatch#countDown 方法,该方法用于将 count 值减 1,如果发现 count 值变为 0,则唤醒阻塞等待的线程。具体实现上,CountDownLatch 同样直接将请求委托给 AQS 的 AbstractQueuedSynchronizer#releaseShared 方法进行处理,所以我们下面主要来看一下 CountDownLatch 针对模板方法 AbstractQueuedSynchronizer#tryReleaseShared 的实现,如下: protected boolean tryReleaseShared(int releases) { // Decrement count; signal when transition to zero for (; ; ) { // 获取 state 状态值 int c = getState(); // 如果已经为 0 则说明没有资源可以释放,直接返回 false,避免 state 变为负值 if (c == 0) { return false; } // 资源数减 1 int nextc = c - 1; if (compareAndSetState(c, nextc)) { // 如果为 0,则说明资源全部释放完毕,此时需要唤醒等待的线程 return nextc == 0; } } } 具体实现如代码注释,比较简单。需要注意的一点就是上面步骤中的判 0 操作,刚开始看的时候可能觉得有点多余,但是这一步的主要作用在于保证 state 值不会变为负值。当前 count 值变为 0 时,上述方法会返回 true,接下去 AQS 会执行唤醒之前因为 count 值不为 0 而被打入同步队列的线程。 总结 CountDownLatch 的实现机制到此就分析完了,因为依赖于 AQS,所以 CountDownLatch 在实现上变得十分简单,但是功能却很强大。下一篇我们将要分析与 CountDownLatch 极为容易混淆的 CyclicBarrier 组件,从实现层面去理清二者的区别。 参考 JDK 1.8 源码 The java.util.concurrent Synchronizer Framework

优秀的个人博客,低调大师

TensorFlow On Flink 原理解析

作者:陈戊超(仲卓),阿里巴巴技术专家 深度学习技术在当代社会发挥的作用越来越大。目前深度学习被广泛应用于个性化推荐、商品搜索、人脸识别、机器翻译、自动驾驶等多个领域,此外还在向社会各个领域迅速渗透。 背景 当前,深度学习的应用越来越多样化,随之涌现出诸多优秀的计算框架。其中 TensorFlow,PyTorch,MXNeT 作为广泛使用的框架更是备受瞩目。在将深度学习应用于实际业务的过程中,往往需要结合数据处理相关的计算框架如:模型训练之前需要对训练数据进行加工生成训练样本,模型预测过程中需要对处理数据的一些指标进行监控等。在这样的情况下,数据处理和模型训练分别需要使用不同的计算引擎,增加了用户使用的难度。 本文将分享如何使用一套引擎搞定机器学习全流程的解决方案。先介绍一下典型的机器学习工作流程。如图所示,整个流程包含特征工程、模型训练、离线或者是在线预测等环节。 在此过程中,无论是特征工程、模型训练还是模型预测,中间都会产生日志。需要先用数据处理引擎比如 Flink 对这些日志进行分析,然后进入特征工程。再使用深度学习的计算引擎 TensorFlow 进行模型训练和模型预测。当模型训练好了以后再用 tensor serving 做在线的打分。 上述流程虽然可以跑通,但也存在一定的问题,比如: 同一个机器学习项目在做特征工程、模型训练、模型预测时需要用到 Flink 和 TensorFlow 两个计算引擎,部署相对而言更复杂。 TensorFlow 在分布式的支持上还不够友好,运行过程中需要指定机器的 IP 地址和端口号;而实际生产过程经常是运行在一个调度系统上比如 Yarn,需要动态分配 IP 地址和端口号。 TensorFlow 的分布式运行缺乏自动的 failover 机制。 针对以上问题,我们通过结合 Flink 和 TensorFlow,将 TensorFlow 的程序跑在 Flink 集群上的这种方式来解决,整体流程如下: 特征工程用 Flink 去执行,模型训练和模型的准实时预测目标使 TensorFlow 计算引擎可以跑在 Flink 集群上。这样就可以用 Flink 一套计算引擎去支持模型训练和模型的预测,部署上更简单的同时也节约了资源。 Flink 计算简介 Flink 是一款开源大数据分布式计算引擎,在 Flink 里所有的计算都抽象成 operator,如上图所示,数据读取的节点叫 source operator,输出数据的节点叫 sink operator。source 和 sink 中间有多种多样的 Flink operator 去处理,上图的计算拓扑包含了三个 source 和两个 sink。 机器学习分布式拓扑 机器学习分布式运行拓扑如下图所示: 在一个机器学习的集群当中,经常会对一组节点(node)进行分组,如上图所示,一组节点可以是 worker(运行算法),也可以是 ps(更新参数)。 如何将 Flink 的 operator 结构与 Machine Learning 的 node、Application Manager 角色结合起来?下面将详细讲解 flink-ai-extended 的抽象。 Flink-ai-extended 抽象 首先,对机器学习的 cluster 进行一层抽象,命名为 ML framework,同时机器学习也包含了 ML operator。通过这两个模块,可以把 Flink 和 Machine Learning Cluster 结合起来,并且可以支持不同的计算引擎,包括 TensorFlow。 如下图所示: 在 Flink 运行环境上,抽象了 ML Framework 和 ML Operator 模块,负责连接 Flink 和其他计算引擎。 ML Framework ML Framework 分为 2 个角色。 Application Manager(以下简称 am) 角色,负责管理所有 node 的节点的生命周期。 node 角色,负责执行机器学习的算法程序。 在上述过程中,还可以对 Application Manager 和 node 进行进一步的抽象,Application Manager 里面我们单独把 state machine 的状态机做成可扩展的,这样就可以支持不同类型的作业。 深度学习引擎,可以自己定义其状态机。从 node 的节点抽象 runner 接口,这样用户就可以根据不同的深度学习引擎去自定义运行算法程序。 ML Operator ML Operator 模块提供了两个接口: addAMRole,这个接口的作用是在 Flink 的作业里添加一个 Application Manager 的角色。Application Manager 角色如上图所示就是机器学习集群的管理节点。 addRole,增加的是机器学习的一组节点。 利用 ML Operator 提供的接口,可以实现 Flink Operator 中包含一个Application Manager 及 3 组 node 的角色,这三组 node 分别叫 role a、 role b,、role c,三个不同角色组成机器学习的一个 cluster。如上图代码所示。Flink 的 operator 与机器学习作业的 node 一一对应。 机器学习的 node 节点运行在 Flink 的 operator 里,需要进行数据交换,原理如下图所示: Flink operator 是 java 进程,机器学习的 node 节点一般是 python 进程,java 和 python 进程通过共享内存交换数据。 TensorFlow On Flink TensorFlow 分布式运行 TensorFlow 分布式训练一般分为 worker 和 ps 角色。worker 负责机器学习计算,ps 负责参数更新。下面将讲解 TensorFlow 如何运行在 Flink 集群中。 TensorFlow Batch 训练运行模式 Batch 模式下,样本数据可以是放在 HDFS 上的,对于 Flink 作业而言,它会起一个source 的 operator,然后 TensorFlow 的 work 角色就会启动。如上图所示,如果 worker 的角色有三个节点,那么 source 的并行度就会设为 3。同理下面 ps 角色有 2 个,所以 ps source 节点就会设为 2。而 Application Manager 和别的角色并没有数据交换,所以 Application Manager 是单独的一个节点,因此它的 source 节点并行度始终为 1。这样 Flink 作业上启动了三个 worker 和两个 ps 节点,worker 和 ps 之间的通讯是通过原始的 TensorFlow 的 GRPC 通讯来实现的,并不是走 Flink 的通信机制。 TensorFlow stream 训练运行模式 如上图所示,前面有两个 source operator,然后接 join operator,把两份数据合并为一份数据,再加自定义处理的节点,生成样本数据。在 stream 模式下,worker 的角色是通过 UDTF 或者 flatmap 来实现的。 同时,TensorFlow worker node 有3 个,所以 flatmap 和 UDTF 相对应的 operator 的并行度也为 3, 由于ps 角色并不去读取数据,所以是通过 flink source operator 来实现。 下面我们再讲一下,如果已经训练好的模型,如何去支持实时的预测。 使用 Python 进行预测 使用 Python 进行预测流程如图所示,如果 TensorFlow 的模型是分布式训练出来的模型,并且这个模型非常大,比如说单机放不下的情况,一般出现在推荐和搜索的场景下。那么实时预测和实时训练原理相同,唯一不同的地方是多了一个加载模型的过程。 在预测的情况下,通过读取模型,将所有的参数加载到 ps 里面去,然后上游的数据还是经过和训练时候一样的处理形式,数据流入到 worker 这样一个角色中去进行处理,将预测的分数再写回到 flink operator,并且发送到下游 operator。 使用 Java 进行预测 如图所示,模型单机进行预测时就没必要再去起 ps 节点,单个 worker 就可以装下整个模型进行预测,尤其是使用 TensorFlow 导出 save model。同时,因为 saved model 格式包含了整个深度学习预测的全部计算逻辑和输入输出,所以不需要运行 Python 的代码就可以进行预测。 此外,还有一种方式可以进行预测。前面 source、join、UDTF 都是对数据进行加工处理变成预测模型可以识别的数据格式,在这种情况下,可以直接在 Java 进程里面通过 TensorFlow Java API,将训练好的模型 load 到内存里,这时会发现并不需要 ps 角色, worker 角色也都是 Java 进程,并不是 Python 的进程,所以我们可以直接在 Java 进程内进行预测,并且可以将预测结果继续发给 Flink 的下游。 总结 在本文中,我们讲解了 flink-ai-extended 原理,以及Flink 结合 TensorFlow 如何进行模型训练和预测。希望通过本文大分享,大家能够使用 flink-ai-extended, 通过 Flink 作业去支持模型训练和模型的预测。

优秀的个人博客,低调大师

简单理解CAS以及compareAndSet

CAS:Compare and Swap, 比较并交换。 CAS的作用是将指定内存地址的内容与所给的某个值相比,如果相等,则将其内容替换为指令中提供的新值,如果不相等,则更新失败。这一比较并交换的操作是原子的,不可以被中断。CAS是通过硬件命令保证了原子性,且硬件级别的原子性比高级语言的软件级别的运行速度要快地多。虽然CAS也包含了多个操作,但其的运算是固定的(就是个比较),这样的锁定性能开销很小。 CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。 CAS有效地说明了“我认为位置V应该包含值A;如果包含该值,则将B放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可。 AtomicInteger类compareAndSet通过原子操作实现了CAS操作,最底层基于汇编语言实现。 public final boolean compareAndSet(V expect, V update) 工作原理如图:内存值跟期望值相同时,内存值跟期望值不同时,

优秀的个人博客,低调大师

ConcurrentHashMap 1.8原理解析

JDK 1.8中,Hash家族有这么一些存在,HashMap,HashTable,LinkedHashMap,ConcurrentHashMap。这里面支持线程安全的有HashTable以及ConcurrentHashMap。对Hash有一个基本了解可以参考本人的从Hash到一致性Hash原理(深度好文) 。 那既然说到ConcurrentHashMap,自然要讨论的就是它的线程安全性和效能。我们先来看一下HashTable的线程安全性以及效能的低下。 public synchronized V put(K key, V value) { // Make sure the value is not null if (value == null) { throw new NullPointerException(); } // Makes sure the key is not already in the hashtable. Entry<?,?> tab[] = table; int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF) % tab.length; @SuppressWarnings("unchecked") Entry<K,V> entry = (Entry<K,V>)tab[index]; for(; entry != null ; entry = entry.next) { if ((entry.hash == hash) && entry.key.equals(key)) { V old = entry.value; entry.value = value; return old; } } addEntry(hash, key, value, index); return null; } public synchronized V get(Object key) { Entry<?,?> tab[] = table; int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF) % tab.length; for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) { if ((e.hash == hash) && e.key.equals(key)) { return (V)e.value; } } return null; } 我们对比一下HashMap 1.7的这两个方法(因为1.8点HashMap会生成红黑树,我们暂时先不考虑红黑树的问题) public V put(K key, V value) { if (key == null) return putForNullKey(value); int hash = hash(key); int i = indexFor(hash, table.length); for (Entry<K,V> e = table[i]; e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } modCount++; addEntry(hash, key, value, i); return null; } public V get(Object key) { if (key == null) return getForNullKey(); Entry<K,V> entry = getEntry(key); return null == entry ? null : entry.getValue(); } final int hash(Object k) { int h = 0; if (useAltHashing) { if (k instanceof String) { return sun.misc.Hashing.stringHash32((String) k); } h = hashSeed; } h ^= k.hashCode(); // This function ensures that hashCodes that differ only by // constant multiples at each bit position have a bounded // number of collisions (approximately 8 at default load factor). //一种算法,进行4次位移,得到相对比较分散的链表 h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4); } 在这里我们可以看到,他们除了key的hash算法不同,HashTable的比较简单,只是用key的哈希值与0x7FFFFFFF(十进制2147483647,二进制1111111111111111111111111111111)进行一次与运算,即为只要相同二进制位上不论0,1全部都变成1,再对table数组的长度取模。而HashMap 1.7则为key的哈希值进行各位无符号位移加异或运算,取得最终哈希值。然后是HashTable不接收null的key,而HashMap接受。他们最大的区别就在于synchronized显示器锁了。 synchronized的本质是所有对象的字节码中有一个monitor的对象头,任何线程拿到了这个monitor的对象头就可以对这个对象进行操作,而拿不到monitor对象头的线程就只能等待,直到拿到了monitor的线程放弃,其他线程才能争夺这个对象头来对对象进行操作。那么问题来了,当大量线程高并发的时候,只要有一个线程拿到了这个对象头,其他线程对这个对象是既不能读也不能写。而对于HashMap来说,如果多线程对其进行操作,那么任意线程都可以胡乱修改里面值的内容造成脏读,所以HashMap是线程不安全的。 那么我们今天的主角登场了ConcurrentHashMap。我们同样来看一下这两个方法。以下是1.8的源码,首先我们要清楚的是1.8跟1.7已经完全不同,1.7是使用segements(16个segement),每个segement都有一个table(Map.Entry数组),相当于16个HashMap,同步机制为分段锁,每个segment继承ReentrantLock;而1.8只有1个table(Map.Entry数组),同步机制为CAS + synchronized保证并发更新。如果不搞清楚这个问题,那么你看1.8的源码可能会很懵逼。 public V put(K key, V value) { return putVal(key, value, false); } final V putVal(K key, V value, boolean onlyIfAbsent) { //无论key还是value,不允许空 if (key == null || value == null) throw new NullPointerException(); //此处获取hash值的方法与HashTable类似 int hash = spread(key.hashCode()); int binCount = 0; //无限循环 for (Node<K,V>[] tab = table;;) { Node<K,V> f; int n, i, fh; //如果节点数组为null,或者长度为0,初始化节点数组 if (tab == null || (n = tab.length) == 0) tab = initTable(); //如果节点数组的某个节点为null,则put的时候就会采用无锁竞争来获取该节点的头把交椅 else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) { if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value, null))) break; // no lock when adding to empty bin } //需要扩容的时候先扩容,再写入 else if ((fh = f.hash) == MOVED) tab = helpTransfer(tab, f); else { //如果hash冲突的时候,即多线程操作时,大家都有一样的hash值 V oldVal = null; synchronized (f) { //锁定节点数组的该节点 if (tabAt(tab, i) == f) { //如果当前该节点为链表形态 if (fh >= 0) { binCount = 1; for (Node<K,V> e = f;; ++binCount) { K ek; //找链表中找到相同的key,把新value替代老value if (e.hash == hash && ((ek = e.key) == key || (ek != null && key.equals(ek)))) { oldVal = e.val; if (!onlyIfAbsent) e.val = value; break; } Node<K,V> pred = e; //如果找不到key,就添加到链表到末尾 if ((e = e.next) == null) { pred.next = new Node<K,V>(hash, key, value, null); break; } } } //如果当前为红黑树形态,进行红黑树到查找和替代(存在相同的key),或者放入红黑树到新叶节点上(key不存在) else if (f instanceof TreeBin) { Node<K,V> p; binCount = 2; if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key, value)) != null) { oldVal = p.val; if (!onlyIfAbsent) p.val = value; } } } } if (binCount != 0) { //如果链表长度超过了8,链表转红黑树 if (binCount >= TREEIFY_THRESHOLD) treeifyBin(tab, i); if (oldVal != null) return oldVal; break; } } } //统计节点个数,检查是否需要扩容 addCount(1L, binCount); return null; } public V get(Object key) { Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek; int h = spread(key.hashCode()); if ((tab = table) != null && (n = tab.length) > 0 && (e = tabAt(tab, (n - 1) & h)) != null) { if ((eh = e.hash) == h) { if ((ek = e.key) == key || (ek != null && key.equals(ek))) return e.val; } else if (eh < 0) return (p = e.find(h, key)) != null ? p.val : null; while ((e = e.next) != null) { if (e.hash == h && ((ek = e.key) == key || (ek != null && key.equals(ek)))) return e.val; } } return null; } 读取的时候,我们没有看见锁到存在,说明读不受多线程影响。 对比ConcurrentHashMap和HashTable,我们可以明显的看到,ConcurrentHashMap在写的时候,并没有锁住整个节点数组,在新节点上使用的是无锁竞争,在老节点上锁住的仅仅是一个节点,读的时候如果不是恰好读到写线程写入相同Hash值的位置,不受影响(可以认为我们的操作一般是读多写少,这种几率也比较低)。而HashTable是对整个节点数组进行锁定,读到时候不能写,写的时候不能读,这么一对比就可以明显感觉到性能差距是巨大的。 虽然ConcurrentHashMap的并发性能还算比较优异,但在亿级计算中,却依然会成为性能瓶颈,具体可以参考本人的Fork/Join框架原理和使用探秘 至于这里为什么会慢,我认为在这种超高并发下,节点数组的单节点的的写写竞争是互斥的,其次,由于红黑树具有读快写慢的特性,它要不断保持树的平衡而不断返转,所以才会使得高并发写的性能急剧下降。

优秀的个人博客,低调大师

理解并实现 ResNet(Keras)

本文为 AI 研习社编译的技术博客,原标题 : Understanding and Coding a ResNet in Keras 作者 |Priya Dwivedi @ Deep Learning Analytics 翻译 | linlh、通夜 编辑 | 邓普斯•杰弗、Pita 原文链接: https://towardsdatascience.com/understanding-and-coding-a-resnet-in-keras-446d7ff84d33 ResNet 是残差网络(Residual Network)的缩写,是一种作为许多计算机视觉任务主干的经典神经网络。这个模型是2015年ImageNet挑战赛的获胜者,ResNet最根本的突破在于它使得我们可以训练成功非常深的神经网路,如1

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册