• 关于

    单处理器可以做什么

    的搜索结果

回答

在线程中使用 System.Windows.Forms.Timer 是不能触发 Tick 事件的,为什么?如何在线程中使用定时器呢?就看本文介绍。 一. System.Windows.Forms.Timer System.Windows.Forms.Timer 要求要有UI 消息泵, 所以通常只在主线程上使用. System.Windows.Forms.Timer 用于以用户定义的事件间隔触发事件。 Windows 计时器是为单线程环境设计的,其中,UI 线程用于执行处理。 它要求用户代码有一个可用的 UI 消息泵,而且总是在同一个线程中操作,或者将调用封送到另一个线程. 且看MSDN的用法解释: 实现在用户定义的时间间隔引发事件的计时器。此计时器最宜用于 Windows 窗体应用程序中,并且必须在窗口中使用。 二. System.Timers.Timer System.Timers.Timer 组件是基于服务器的计时器,它使您能够指定在应用程序中引发 Elapsed 事件的周期性间隔。 然后可以操控此事件以提供定期处理。例如,假设您有一台关键性服务器,必须每周 7 天、每天 24 小时都保持运行。 可以创建一个使用 Timer 的服务,以定期检查服务器并确保系统开启并在运行。如果系统不响应,则该服务可以尝试重新启动服务器或通知管理员。 基于服务器的 Timer 是为在多线程环境中用于辅助线程而设计的。 服务器计时器可以在线程间移动来处理引发的 Elapsed 事件,这样就可以比 Windows 计时器更精确地按时引发事件。 有关基于服务器的计时器的更多信息,请参见“基于服务器的计时器介绍”。 在 Visual Studio 和 .NET Framework 中有三种计时器控件:基于服务器的计时器(可以在“工具箱”的“组件”选项卡上看到)、基于 Windows 的标准计时器(可以在“工具箱”的“Windows 窗体”选项卡上看到)和线程计时器(只能以编程方式使用)。 基于 Windows 的计时器从 Visual Basic 1.0 版起就存在于该产品中,并且基本上未做改动。该计时器针对在 Windows 窗体应用程序中使用而进行了优化。 基于服务器的计时器是传统的计时器为了在服务器环境上运行而优化后的更新版本。 线程计时器是一种简单的、轻量级计时器,它使用回调方法而不是使用事件,并由线程池线程提供支持。 在 Win32 体系结构中有两种类型的线程:UI 线程和辅助线程。UI 线程绝大多数时间处于空闲状态,等待消息循环中的消息到来。 一旦接收到消息,它们就进行处理并等待下一个消息到来。另外,辅助线程用来执行后台处理而且不使用消息循环。 Windows 计时器和基于服务器的计时器在运行时都使用 Interval 属性。线程计时器的时间间隔在 Timer 构造函数中设置。 计时器的设计目的各不相同,它们的线程处理明确地指出了这一点: 1.Windows 计时器是为单线程环境设计的,其中,UI 线程用于执行处理。Windows 计时器的精度限定为 55 毫秒。 这些传统计时器要求用户代码有一个可用的 UI 消息泵,而且总是在同一个线程中操作,或者将调用封送到另一个线程。对于 COM 组件来说,这样会降低性能。 2.基于服务器的计时器是为在多线程环境下与辅助线程一起使用而设计的。由于它们使用不同的体系结构,因此基于服务器的计时器可能比 Windows 计时器精确得多。 服务器计时器可以在线程之间移动来处理引发的事件。 3.对消息不在线程上发送的方案中,线程计时器是非常有用的。例如,基于 Windows 的计时器依赖于操作系统计时器的支持,如果不在线程上发送消息,与计时器相关的事件将不会发生。 在这种情况下,线程计时器就非常有用。 “答案来源于网络,供您参考” 希望以上信息可以帮到您!

牧明 2019-12-02 02:17:14 0 浏览量 回答数 0

回答

在Java5之前的版本,使用双重检查锁定创建单例Singleton时,如果多个线程试图同时创建Singleton实例,则可能有多个Singleton实例被创建。从Java5开始,使用Enum创建线程安全的Singleton很容易。但如果面试官坚持双重检查锁定,那么你必须为他们编写代码。记得使用volatile变量。 为什么枚举单例在Java中更好 枚举单例是使用一个实例在Java中实现单例模式的新方法。虽然Java中的单例模式存在很长时间,但枚举单例是相对较新的概念,在引入Enum作为关键字和功能之后,从Java5开始在实践中。本文与之前关于Singleton的内容有些相关,其中讨论了有关Singleton模式的面试中的常见问题,以及10个Java枚举示例,其中我们看到了如何通用枚举可以。这篇文章是关于为什么我们应该使用Eeame作为Java中的单例,它比传统的单例方法相比有什么好处等等。 Java枚举和单例模式 Java中的枚举单例模式是使用枚举在Java中实现单例模式。单例模式在Java中早有应用,但使用枚举类型创建单例模式时间却不长.如果感兴趣,你可以了解下构建者设计模式和装饰器设计模式。 1)枚举单例易于书写 这是迄今为止最大的优势,如果你在Java5之前一直在编写单例,你知道,即使双检查锁定,你仍可以有多个实例。虽然这个问题通过Java内存模型的改进已经解决了,从Java5开始的volatile类型变量提供了保证,但是对于许多初学者来说,编写起来仍然很棘手。与同步双检查锁定相比,枚举单例实在是太简单了。如果你不相信,那就比较一下下面的传统双检查锁定单例和枚举单例的代码: 在Java中使用枚举的单例 这是我们通常声明枚举的单例的方式,它可能包含实例变量和实例方法,但为了简单起见,我没有使用任何实例方法,只是要注意,如果你使用的实例方法且该方法能改变对象的状态的话,则需要确保该方法的线程安全。默认情况下,创建枚举实例是线程安全的,但Enum上的任何其他方法是否线程安全都是程序员的责任。 你可以通过EasySingleton.INSTANCE来处理它,这比在单例上调用getInstance()方法容易得多。 具有双检查锁定的单例示例 下面的代码是单例模式中双重检查锁定的示例,此处的getInstance()方法检查两次,以查看INSTANCE是否为空,这就是为什么它被称为双检查锁定模式,请记住,双检查锁定是代理之前Java5,但Java5内存模型中易失变量的干扰,它应该工作完美。 你可以调用DoubleCheckedLockingSingleton.getInstance()来获取此单例类的访问权限。 现在,只需查看创建延迟加载的线程安全的Singleton所需的代码量。使用枚举单例模式,你可以在一行中具有该模式,因为创建枚举实例是线程安全的,并且由JVM进行。 人们可能会争辩说,有更好的方法来编写Singleton而不是双检查锁定方法,但每种方法都有自己的优点和缺点,就像我最喜欢在类加载时创建的静态字段Singleton,如下面所示,但请记住,这不是一个延迟加载单例: 单例模式用静态工厂方法 这是我最喜欢的在Java中影响Singleton模式的方法之一,因为Singleton实例是静态的,并且最后一个变量在类首次加载到内存时初始化,因此实例的创建本质上是线程安全的。 你可以调用Singleton.getSingleton()来获取此类的访问权限。 2)枚举单例自行处理序列化 传统单例的另一个问题是,一旦实现可序列化接口,它们就不再是Singleton,因为readObject()方法总是返回一个新实例,就像Java中的构造函数一样。通过使用readResolve()方法,通过在以下示例中替换Singeton来避免这种情况: 如果Singleton类保持内部状态,这将变得更加复杂,因为你需要标记为transient(不被序列化),但使用枚举单例,序列化由JVM进行。 3)创建枚举实例是线程安全的 如第1点所述,因为Enum实例的创建在默认情况下是线程安全的,你无需担心是否要做双重检查锁定。 总之,在保证序列化和线程安全的情况下,使用两行代码枚举单例模式是在Java5以后的世界中创建Singleton的最佳方式。你仍然可以使用其他流行的方法,如你觉得更好,欢迎讨论。

珍宝珠 2020-02-07 16:58:59 0 浏览量 回答数 0

回答

用cache实现session###### 你这个问题描述 真66 不是同一个浏览器  你居然想session共享? 貌似我没在开源看到过过。 单点登录和session共享 ??######回复 @easymbol : 服务器需要存储uid和session之间的映射吧……密码改了,全部的session都要过期######不是,如果写单点登陆的话,那么也就不会出现这个问题了,但是不想用单点登陆,想直接操作session来处理,所以考虑的方向到了session共享###### 当然是考虑做监听啊###### 你如果把这个功能做出来,请告诉我你公司的网址###### 这不太可能吧###### 只能每次操作里面去验证密码是否正确了###### 不需要那么麻烦   在修改密码的的逻辑里添加session相关验证就可以了,当然也可以反过来在session验证的时候添加上密码验证就可以了   没必要搞什么session共享     按照你的这个描述感觉系统应该挺小的,你要是上了session共享后,你会发现牛刀无地用;当然要是用来做研究的可以上    共享session推荐你个中间件mycat###### 如果密码修改了,将除了最后登录的那个客户端以外的其它客户端生成的所有 session 设置为过期,然后其它客户端自然会被踢出,并要求重新登录   这里的前提是,每个客户端拥有各自不同的 session######如何做到在一个浏览器修改密码,seesion过期######这种操作有想过,但是会引出一个问题,就是其他的用户会在同一时段同时请求用户表的查询操作...###### 不同的浏览器之间做session共享...这就有点牛逼了...######最靠谱的办法就是Jfinal说的那样,登录的时候把用户和session映射存起来,修改密码的时候,把其他session给过期就行了,不同的浏览器的session管理机制都不一样,而且在网页代码中,不开发浏览器插件的情况下是无法去改变这些东西的,通常意义的单点登录,是在本地安装有对应的客户端程序才能实现的,纯网页是实现不了的

kun坤 2020-06-06 14:56:36 0 浏览量 回答数 0

阿里云试用中心,为您提供0门槛上云实践机会!

0元试用32+款产品,最高免费12个月!拨打95187-1,咨询专业上云建议!

回答

用JS在前台组装好,再发送到后台 放在session唯一弊端就是,用户关闭浏览器了,你在登录就可以马上在获取验证码。然后打包一下你的站。 我建议放在数据库,文件系统,或者内存中。放在session也可以的。弊端就是我说的了。没别的了 缓存储存下吧,电话号码和验证码做个对应; 我觉可以前端js处理,完成全部一起发送到后台,还有就是也可以下一步获取到上一步的参数,把他用input的hidden保存。 存入redis 如果是单服务器, 这种没什么弊端, 你可以额外设置一个很短的过期时间, 如果到了时间, 你马上删除session的信息. 对于分布式集群, balancer应该有能力识别你的session id并且把请求分发到相关的服务器. 所以你也不必担心发到陌生的服务导致不能识别.

一枚小鲜肉帅哥 2020-05-31 21:06:44 0 浏览量 回答数 0

回答

分布,不如,用JS在前台组装好,再发送到后台######当然可以,没什么不行######io读写慢呀...###### 放在session唯一弊端就是,用户关闭浏览器了,你在登录就可以马上在获取验证码。然后打包一下你的站,短信炸弹生成了。  ######我建议放在数据库,文件系统,或者内存中。放在session也可以的。弊端就是我说的了。没别的了######缓存储存下吧,电话号码和验证码做个对应;######我觉可以前端js处理,完成全部一起发送到后台,还有就是也可以下一步获取到上一步的参数,把他用input的hidden保存。######存入redis###### 如果是单服务器, 这种没什么弊端, 你可以额外设置一个很短的过期时间, 如果到了时间, 你马上删除session的信息. 对于分布式集群, balancer应该有能力识别你的session id并且把请求分发到相关的服务器. 所以你也不必担心发到陌生的服务导致不能识别.

kun坤 2020-06-08 17:58:12 0 浏览量 回答数 0

回答

分布式事务的解决方案有如下几种: 全局消息基于可靠消息服务的分布式事务TCC最大努力通知方案1:全局事务(DTP模型)全局事务基于DTP模型实现。DTP是由X/Open组织提出的一种分布式事务模型——X/Open Distributed Transaction Processing Reference Model。它规定了要实现分布式事务,需要三种角色: AP:Application 应用系统 它就是我们开发的业务系统,在我们开发的过程中,可以使用资源管理器提供的事务接口来实现分布式事务。 TM:Transaction Manager 事务管理器 分布式事务的实现由事务管理器来完成,它会提供分布式事务的操作接口供我们的业务系统调用。这些接口称为TX接口。事务管理器还管理着所有的资源管理器,通过它们提供的XA接口来同一调度这些资源管理器,以实现分布式事务。DTP只是一套实现分布式事务的规范,并没有定义具体如何实现分布式事务,TM可以采用2PC、3PC、Paxos等协议实现分布式事务。RM:Resource Manager 资源管理器 能够提供数据服务的对象都可以是资源管理器,比如:数据库、消息中间件、缓存等。大部分场景下,数据库即为分布式事务中的资源管理器。资源管理器能够提供单数据库的事务能力,它们通过XA接口,将本数据库的提交、回滚等能力提供给事务管理器调用,以帮助事务管理器实现分布式的事务管理。XA是DTP模型定义的接口,用于向事务管理器提供该资源管理器(该数据库)的提交、回滚等能力。DTP只是一套实现分布式事务的规范,RM具体的实现是由数据库厂商来完成的。有没有基于DTP模型的分布式事务中间件?DTP模型有啥优缺点?方案2:基于可靠消息服务的分布式事务这种实现分布式事务的方式需要通过消息中间件来实现。假设有A和B两个系统,分别可以处理任务A和任务B。此时系统A中存在一个业务流程,需要将任务A和任务B在同一个事务中处理。下面来介绍基于消息中间件来实现这种分布式事务。 title 在系统A处理任务A前,首先向消息中间件发送一条消息消息中间件收到后将该条消息持久化,但并不投递。此时下游系统B仍然不知道该条消息的存在。消息中间件持久化成功后,便向系统A返回一个确认应答;系统A收到确认应答后,则可以开始处理任务A;任务A处理完成后,向消息中间件发送Commit请求。该请求发送完成后,对系统A而言,该事务的处理过程就结束了,此时它可以处理别的任务了。 但commit消息可能会在传输途中丢失,从而消息中间件并不会向系统B投递这条消息,从而系统就会出现不一致性。这个问题由消息中间件的事务回查机制完成,下文会介绍。消息中间件收到Commit指令后,便向系统B投递该消息,从而触发任务B的执行;当任务B执行完成后,系统B向消息中间件返回一个确认应答,告诉消息中间件该消息已经成功消费,此时,这个分布式事务完成。上述过程可以得出如下几个结论: 消息中间件扮演者分布式事务协调者的角色。 系统A完成任务A后,到任务B执行完成之间,会存在一定的时间差。在这个时间差内,整个系统处于数据不一致的状态,但这短暂的不一致性是可以接受的,因为经过短暂的时间后,系统又可以保持数据一致性,满足BASE理论。 上述过程中,如果任务A处理失败,那么需要进入回滚流程,如下图所示: title 若系统A在处理任务A时失败,那么就会向消息中间件发送Rollback请求。和发送Commit请求一样,系统A发完之后便可以认为回滚已经完成,它便可以去做其他的事情。消息中间件收到回滚请求后,直接将该消息丢弃,而不投递给系统B,从而不会触发系统B的任务B。此时系统又处于一致性状态,因为任务A和任务B都没有执行。 上面所介绍的Commit和Rollback都属于理想情况,但在实际系统中,Commit和Rollback指令都有可能在传输途中丢失。那么当出现这种情况的时候,消息中间件是如何保证数据一致性呢?——答案就是超时询问机制。 title 系统A除了实现正常的业务流程外,还需提供一个事务询问的接口,供消息中间件调用。当消息中间件收到一条事务型消息后便开始计时,如果到了超时时间也没收到系统A发来的Commit或Rollback指令的话,就会主动调用系统A提供的事务询问接口询问该系统目前的状态。该接口会返回三种结果: 提交 若获得的状态是“提交”,则将该消息投递给系统B。回滚 若获得的状态是“回滚”,则直接将条消息丢弃。处理中 若获得的状态是“处理中”,则继续等待。消息中间件的超时询问机制能够防止上游系统因在传输过程中丢失Commit/Rollback指令而导致的系统不一致情况,而且能降低上游系统的阻塞时间,上游系统只要发出Commit/Rollback指令后便可以处理其他任务,无需等待确认应答。而Commit/Rollback指令丢失的情况通过超时询问机制来弥补,这样大大降低上游系统的阻塞时间,提升系统的并发度。 下面来说一说消息投递过程的可靠性保证。 当上游系统执行完任务并向消息中间件提交了Commit指令后,便可以处理其他任务了,此时它可以认为事务已经完成,接下来消息中间件一定会保证消息被下游系统成功消费掉!那么这是怎么做到的呢?这由消息中间件的投递流程来保证。 消息中间件向下游系统投递完消息后便进入阻塞等待状态,下游系统便立即进行任务的处理,任务处理完成后便向消息中间件返回应答。消息中间件收到确认应答后便认为该事务处理完毕! 如果消息在投递过程中丢失,或消息的确认应答在返回途中丢失,那么消息中间件在等待确认应答超时之后就会重新投递,直到下游消费者返回消费成功响应为止。当然,一般消息中间件可以设置消息重试的次数和时间间隔,比如:当第一次投递失败后,每隔五分钟重试一次,一共重试3次。如果重试3次之后仍然投递失败,那么这条消息就需要人工干预。 title title 有的同学可能要问:消息投递失败后为什么不回滚消息,而是不断尝试重新投递? 这就涉及到整套分布式事务系统的实现成本问题。 我们知道,当系统A将向消息中间件发送Commit指令后,它便去做别的事情了。如果此时消息投递失败,需要回滚的话,就需要让系统A事先提供回滚接口,这无疑增加了额外的开发成本,业务系统的复杂度也将提高。对于一个业务系统的设计目标是,在保证性能的前提下,最大限度地降低系统复杂度,从而能够降低系统的运维成本。 不知大家是否发现,上游系统A向消息中间件提交Commit/Rollback消息采用的是异步方式,也就是当上游系统提交完消息后便可以去做别的事情,接下来提交、回滚就完全交给消息中间件来完成,并且完全信任消息中间件,认为它一定能正确地完成事务的提交或回滚。然而,消息中间件向下游系统投递消息的过程是同步的。也就是消息中间件将消息投递给下游系统后,它会阻塞等待,等下游系统成功处理完任务返回确认应答后才取消阻塞等待。为什么这两者在设计上是不一致的呢? 首先,上游系统和消息中间件之间采用异步通信是为了提高系统并发度。业务系统直接和用户打交道,用户体验尤为重要,因此这种异步通信方式能够极大程度地降低用户等待时间。此外,异步通信相对于同步通信而言,没有了长时间的阻塞等待,因此系统的并发性也大大增加。但异步通信可能会引起Commit/Rollback指令丢失的问题,这就由消息中间件的超时询问机制来弥补。 那么,消息中间件和下游系统之间为什么要采用同步通信呢? 异步能提升系统性能,但随之会增加系统复杂度;而同步虽然降低系统并发度,但实现成本较低。因此,在对并发度要求不是很高的情况下,或者服务器资源较为充裕的情况下,我们可以选择同步来降低系统的复杂度。 我们知道,消息中间件是一个独立于业务系统的第三方中间件,它不和任何业务系统产生直接的耦合,它也不和用户产生直接的关联,它一般部署在独立的服务器集群上,具有良好的可扩展性,所以不必太过于担心它的性能,如果处理速度无法满足我们的要求,可以增加机器来解决。而且,即使消息中间件处理速度有一定的延迟那也是可以接受的,因为前面所介绍的BASE理论就告诉我们了,我们追求的是最终一致性,而非实时一致性,因此消息中间件产生的时延导致事务短暂的不一致是可以接受的。 方案3:最大努力通知(定期校对)最大努力通知也被称为定期校对,其实在方案二中已经包含,这里再单独介绍,主要是为了知识体系的完整性。这种方案也需要消息中间件的参与,其过程如下: title 上游系统在完成任务后,向消息中间件同步地发送一条消息,确保消息中间件成功持久化这条消息,然后上游系统可以去做别的事情了;消息中间件收到消息后负责将该消息同步投递给相应的下游系统,并触发下游系统的任务执行;当下游系统处理成功后,向消息中间件反馈确认应答,消息中间件便可以将该条消息删除,从而该事务完成。上面是一个理想化的过程,但在实际场景中,往往会出现如下几种意外情况: 消息中间件向下游系统投递消息失败上游系统向消息中间件发送消息失败对于第一种情况,消息中间件具有重试机制,我们可以在消息中间件中设置消息的重试次数和重试时间间隔,对于网络不稳定导致的消息投递失败的情况,往往重试几次后消息便可以成功投递,如果超过了重试的上限仍然投递失败,那么消息中间件不再投递该消息,而是记录在失败消息表中,消息中间件需要提供失败消息的查询接口,下游系统会定期查询失败消息,并将其消费,这就是所谓的“定期校对”。 如果重复投递和定期校对都不能解决问题,往往是因为下游系统出现了严重的错误,此时就需要人工干预。 对于第二种情况,需要在上游系统中建立消息重发机制。可以在上游系统建立一张本地消息表,并将 任务处理过程 和 向本地消息表中插入消息 这两个步骤放在一个本地事务中完成。如果向本地消息表插入消息失败,那么就会触发回滚,之前的任务处理结果就会被取消。如果这量步都执行成功,那么该本地事务就完成了。接下来会有一个专门的消息发送者不断地发送本地消息表中的消息,如果发送失败它会返回重试。当然,也要给消息发送者设置重试的上限,一般而言,达到重试上限仍然发送失败,那就意味着消息中间件出现严重的问题,此时也只有人工干预才能解决问题。 对于不支持事务型消息的消息中间件,如果要实现分布式事务的话,就可以采用这种方式。它能够通过重试机制+定期校对实现分布式事务,但相比于第二种方案,它达到数据一致性的周期较长,而且还需要在上游系统中实现消息重试发布机制,以确保消息成功发布给消息中间件,这无疑增加了业务系统的开发成本,使得业务系统不够纯粹,并且这些额外的业务逻辑无疑会占用业务系统的硬件资源,从而影响性能。 因此,尽量选择支持事务型消息的消息中间件来实现分布式事务,如RocketMQ。 方案4:TCC(两阶段型、补偿型)TCC即为Try Confirm Cancel,它属于补偿型分布式事务。顾名思义,TCC实现分布式事务一共有三个步骤: Try:尝试待执行的业务 这个过程并未执行业务,只是完成所有业务的一致性检查,并预留好执行所需的全部资源Confirm:执行业务 这个过程真正开始执行业务,由于Try阶段已经完成了一致性检查,因此本过程直接执行,而不做任何检查。并且在执行的过程中,会使用到Try阶段预留的业务资源。Cancel:取消执行的业务 若业务执行失败,则进入Cancel阶段,它会释放所有占用的业务资源,并回滚Confirm阶段执行的操作。下面以一个转账的例子来解释下TCC实现分布式事务的过程。 假设用户A用他的账户余额给用户B发一个100元的红包,并且余额系统和红包系统是两个独立的系统。 Try 创建一条转账流水,并将流水的状态设为交易中将用户A的账户中扣除100元(预留业务资源)Try成功之后,便进入Confirm阶段Try过程发生任何异常,均进入Cancel阶段Confirm 向B用户的红包账户中增加100元将流水的状态设为交易已完成Confirm过程发生任何异常,均进入Cancel阶段Confirm过程执行成功,则该事务结束Cancel 将用户A的账户增加100元将流水的状态设为交易失败在传统事务机制中,业务逻辑的执行和事务的处理,是在不同的阶段由不同的部件来完成的:业务逻辑部分访问资源实现数据存储,其处理是由业务系统负责;事务处理部分通过协调资源管理器以实现事务管理,其处理由事务管理器来负责。二者没有太多交互的地方,所以,传统事务管理器的事务处理逻辑,仅需要着眼于事务完成(commit/rollback)阶段,而不必关注业务执行阶段。 TCC全局事务必须基于RM本地事务来实现全局事务TCC服务是由Try/Confirm/Cancel业务构成的, 其Try/Confirm/Cancel业务在执行时,会访问资源管理器(Resource Manager,下文简称RM)来存取数据。这些存取操作,必须要参与RM本地事务,以使其更改的数据要么都commit,要么都rollback。 这一点不难理解,考虑一下如下场景: title 假设图中的服务B没有基于RM本地事务(以RDBS为例,可通过设置auto-commit为true来模拟),那么一旦[B:Try]操作中途执行失败,TCC事务框架后续决定回滚全局事务时,该[B:Cancel]则需要判断[B:Try]中哪些操作已经写到DB、哪些操作还没有写到DB:假设[B:Try]业务有5个写库操作,[B:Cancel]业务则需要逐个判断这5个操作是否生效,并将生效的操作执行反向操作。 不幸的是,由于[B:Cancel]业务也有n(0<=n<=5)个反向的写库操作,此时一旦[B:Cancel]也中途出错,则后续的[B:Cancel]执行任务更加繁重。因为,相比第一次[B:Cancel]操作,后续的[B:Cancel]操作还需要判断先前的[B:Cancel]操作的n(0<=n<=5)个写库中哪几个已经执行、哪几个还没有执行,这就涉及到了幂等性问题。而对幂等性的保障,又很可能还需要涉及额外的写库操作,该写库操作又会因为没有RM本地事务的支持而存在类似问题。。。可想而知,如果不基于RM本地事务,TCC事务框架是无法有效的管理TCC全局事务的。 反之,基于RM本地事务的TCC事务,这种情况则会很容易处理:[B:Try]操作中途执行失败,TCC事务框架将其参与RM本地事务直接rollback即可。后续TCC事务框架决定回滚全局事务时,在知道“[B:Try]操作涉及的RM本地事务已经rollback”的情况下,根本无需执行[B:Cancel]操作。 换句话说,基于RM本地事务实现TCC事务框架时,一个TCC型服务的cancel业务要么执行,要么不执行,不需要考虑部分执行的情况。 TCC事务框架应该提供Confirm/Cancel服务的幂等性保障一般认为,服务的幂等性,是指针对同一个服务的多次(n>1)请求和对它的单次(n=1)请求,二者具有相同的副作用。 在TCC事务模型中,Confirm/Cancel业务可能会被重复调用,其原因很多。比如,全局事务在提交/回滚时会调用各TCC服务的Confirm/Cancel业务逻辑。执行这些Confirm/Cancel业务时,可能会出现如网络中断的故障而使得全局事务不能完成。因此,故障恢复机制后续仍然会重新提交/回滚这些未完成的全局事务,这样就会再次调用参与该全局事务的各TCC服务的Confirm/Cancel业务逻辑。 既然Confirm/Cancel业务可能会被多次调用,就需要保障其幂等性。 那么,应该由TCC事务框架来提供幂等性保障?还是应该由业务系统自行来保障幂等性呢? 个人认为,应该是由TCC事务框架来提供幂等性保障。如果仅仅只是极个别服务存在这个问题的话,那么由业务系统来负责也是可以的;然而,这是一类公共问题,毫无疑问,所有TCC服务的Confirm/Cancel业务存在幂等性问题。TCC服务的公共问题应该由TCC事务框架来解决;而且,考虑一下由业务系统来负责幂等性需要考虑的问题,就会发现,这无疑增大了业务系统的复杂度。

1210119897362579 2019-12-02 00:14:25 0 浏览量 回答数 0

问题

Nginx性能为什么如此吊

小柒2012 2019-12-01 21:20:47 15038 浏览量 回答数 3

回答

引用来自“chentian08”的答案 引用来自“中山野鬼”的答案 引用来自“Jack.arain”的答案 MP3一般是1152个采样为一帧来编码的,知道采样率,声道,很容易计算出1秒多少帧。一般音频播放缓冲搞个1秒左右就够了。 单纯解码mp3也可以试试  mpg123。。。 貌似楼主这些概念还没有。哈。我到现在还不清楚,为什么他是做解码,而从数据流中取了 1024 * 24bits。 刚接接触这方面的知识,确实不太了解,你有这方面的学习资料或例子什么的,能不能发点给我或者加我QQ:735838956@qq.com。其实我的目的就是,想做个拥有MP3播放器功能的东西,然后又跟网络有点关系,想实现的功能主要有:本地MP3文件的播放,这个有很多方法,但是具体怎么实现,我真的想知道,我就是想在发送数据的时候,同时把它播放出来,结果就出现了上面提到的问题了; 发送MP3文件,这个应该就是先读取一个大小,再发送; 接收MP3数据,然后播放,这个应该就是接受到数据后,解码播放; 我 00年搞AC3 DSP算法优化时,碰过音频,后来就没碰了。你要看资料,先看标准吧。标准理解清楚还是必要的。如果标准中很多俗语或名词搞不清楚,那你还需要补充知识。 ######回调函数用用还是应该的。应该是回调机制没处理好。需要有定时器。指定时间间隔,处理,填BUF。另一种可能,源码有BUG,如果源码没问题,不会占用那么高的CPU。音频解码占用的计算资源很少的。 ###### 引用来自“中山野鬼”的答案 回调函数用用还是应该的。应该是回调机制没处理好。需要有定时器。指定时间间隔,处理,填BUF。另一种可能,源码有BUG,如果源码没问题,不会占用那么高的CPU。音频解码占用的计算资源很少的。 如果是你,你会怎么做,因为这些音频数据还要通过网络发出,除了一帧帧地读,我想不出其它办法,刚接触音频开发,请指点。 ###### 引用来自“chentian08”的答案 引用来自“中山野鬼”的答案 回调函数用用还是应该的。应该是回调机制没处理好。需要有定时器。指定时间间隔,处理,填BUF。另一种可能,源码有BUG,如果源码没问题,不会占用那么高的CPU。音频解码占用的计算资源很少的。 如果是你,你会怎么做,因为这些音频数据还要通过网络发出,除了一帧帧地读,我想不出其它办法,刚接触音频开发,请指点。 应该是网络接入吧。如果是网络发送自然是编码后数据,不会是PCM数据。不过从你的描述很奇怪。只有PCM数据才需要 24bits,1024作为一个block,已备频域处理。 如果是我做,至少开1s的解码BUF。无非是采样率最多X4个bytes。你觉得大吗?哈。 ###### 引用来自“中山野鬼”的答案 引用来自“chentian08”的答案 引用来自“中山野鬼”的答案 回调函数用用还是应该的。应该是回调机制没处理好。需要有定时器。指定时间间隔,处理,填BUF。另一种可能,源码有BUG,如果源码没问题,不会占用那么高的CPU。音频解码占用的计算资源很少的。 如果是你,你会怎么做,因为这些音频数据还要通过网络发出,除了一帧帧地读,我想不出其它办法,刚接触音频开发,请指点。 应该是网络接入吧。如果是网络发送自然是编码后数据,不会是PCM数据。不过从你的描述很奇怪。只有PCM数据才需要 24bits,1024作为一个block,已备频域处理。 如果是我做,至少开1s的解码BUF。无非是采样率最多X4个bytes。你觉得大吗?哈。 什么叫1s的解码BUF。1s是什么意思? ###### 引用来自“chentian08”的答案 引用来自“中山野鬼”的答案 引用来自“chentian08”的答案 引用来自“中山野鬼”的答案 回调函数用用还是应该的。应该是回调机制没处理好。需要有定时器。指定时间间隔,处理,填BUF。另一种可能,源码有BUG,如果源码没问题,不会占用那么高的CPU。音频解码占用的计算资源很少的。 如果是你,你会怎么做,因为这些音频数据还要通过网络发出,除了一帧帧地读,我想不出其它办法,刚接触音频开发,请指点。 应该是网络接入吧。如果是网络发送自然是编码后数据,不会是PCM数据。不过从你的描述很奇怪。只有PCM数据才需要 24bits,1024作为一个block,已备频域处理。 如果是我做,至少开1s的解码BUF。无非是采样率最多X4个bytes。你觉得大吗?哈。 什么叫1s的解码BUF。1s是什么意思? 1s==1秒钟。后面我已经给出公式了。采样率 X 4 bytes。当然这是一个通道的容量。 ###### MP3一般是1152个采样为一帧来编码的,知道采样率,声道,很容易计算出1秒多少帧。一般音频播放缓冲搞个1秒左右就够了。 单纯解码mp3也可以试试  mpg123。。。######        99%是代码或者做法有bug. 这种任务量感觉非常的少. 应该被秒杀才对.  现在的CPU很霸气的. 给逻辑的每一步加时间输出,  定位耗时点.###### 引用来自“Jack.arain”的答案 MP3一般是1152个采样为一帧来编码的,知道采样率,声道,很容易计算出1秒多少帧。一般音频播放缓冲搞个1秒左右就够了。 单纯解码mp3也可以试试  mpg123。。。 貌似楼主这些概念还没有。哈。我到现在还不清楚,为什么他是做解码,而从数据流中取了 1024 * 24bits。 ###### 引用来自“中山野鬼”的答案 引用来自“Jack.arain”的答案 MP3一般是1152个采样为一帧来编码的,知道采样率,声道,很容易计算出1秒多少帧。一般音频播放缓冲搞个1秒左右就够了。 单纯解码mp3也可以试试  mpg123。。。 貌似楼主这些概念还没有。哈。我到现在还不清楚,为什么他是做解码,而从数据流中取了 1024 * 24bits。 刚接接触这方面的知识,确实不太了解,你有这方面的学习资料或例子什么的,能不能发点给我或者加我QQ:735838956@qq.com。其实我的目的就是,想做个拥有MP3播放器功能的东西,然后又跟网络有点关系,想实现的功能主要有:本地MP3文件的播放,这个有很多方法,但是具体怎么实现,我真的想知道,我就是想在发送数据的时候,同时把它播放出来,结果就出现了上面提到的问题了; 发送MP3文件,这个应该就是先读取一个大小,再发送; 接收MP3数据,然后播放,这个应该就是接受到数据后,解码播放;

kun坤 2020-06-06 23:55:47 0 浏览量 回答数 0

问题

聊聊JStorm的最佳实践

爵霸 2019-12-01 21:54:49 3569 浏览量 回答数 0

问题

【教程免费下载】深入理解计算机系统(英文版第3版)

玄学酱 2019-12-01 22:08:27 3332 浏览量 回答数 1

回答

1.阻塞与同步2.BIO与NIO对比3.NIO简介4.缓冲区Buffer5.通道Channel6.反应堆7.选择器8.NIO源码分析9.AIO1.阻塞与同步1)阻塞(Block)和非租塞(NonBlock):阻塞和非阻塞是进程在访问数据的时候,数据是否准备就绪的一种处理方式,当数据没有准备的时候阻塞:往往需要等待缞冲区中的数据准备好过后才处理其他的事情,否則一直等待在那里。非阻塞:当我们的进程访问我们的数据缓冲区的时候,如果数据没有准备好则直接返回,不会等待。如果数据已经准备好,也直接返回2)同步(Synchronization)和异步(Async)的方式:同步和异步都是基于应用程序私操作系统处理IO事件所采用的方式,比如同步:是应用程序要直接参与IO读写的操作。异步:所有的IO读写交给搡作系统去处理,应用程序只需要等待通知。同步方式在处理IO事件的时候,必须阻塞在某个方法上靣等待我们的IO事件完成(阻塞IO事件或者通过轮询IO事件的方式).对于异步来说,所有的IO读写都交给了搡作系统。这个时候,我们可以去做其他的事情,并不拓要去完成真正的IO搡作,当搡作完成IO后.会给我们的应用程序一个通知同步:阻塞到IO事件,阻塞到read成则write。这个时候我们就完全不能做自己的事情,让读写方法加入到线程里面,然后阻塞线程来实现,对线程的性能开销比较大,参考:https://blog.csdn.net/CharJay_Lin/article/details/812598802.BIO与NIO对比block IO与Non-block IO1)区别IO模型 IO NIO方式 从硬盘到内存 从内存到硬盘通信 面向流(乡村公路) 面向缓存(高速公路,多路复用技术)处理 阻塞IO(多线程) 非阻塞IO(反应堆Reactor)触发 无 选择器(轮询机制)2)面向流与面向缓冲Java NIO和IO之间第一个最大的区别是,IO是面向流的.NIO是面向缓冲区的。Java IO面向流意味着毎次从流中读一个成多个字节,直至读取所有字节,它们没有被缓存在任何地方,此外,它不能前后移动流中的数据。如果需要前后移动从流中读取的教据,需要先将它缓存到一个缓冲区。Java NIO的缓冲导向方法略有不同。数据读取到一个它稍后处理的缓冲区,霱要时可在缓冲区中前后移动。这就增加了处理过程中的灵活性。但是,还需要检查是否该缓冲区中包含所有您需要处理的数裾。而且,需确保当更多的数据读入缓冲区时,不要覆盖缓冲区里尚未处理的数据。3)阻塞与非阻塞Java IO的各种流是阻塞的。这意味着,当一个线程调用read() 或 write()时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了。 Java NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取。而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可以继续做其他的事情。 非阻塞写也是如此。一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。 线程通常将非阻塞IO的空闲时间用于在其它通道上执行IO操作,所以一个单独的线程现在可以管理多个输入和输出通道(channel)。4)选择器(Selector)Java NIO的选择器允许一个单独的线程来监视多个输入通道,你可以注册多个通道使用一个选择器,然后使用一个单独的线程来“选择"通道:这些通里已经有可以处理的褕入,或者选择已准备写入的通道。这选怿机制,使得一个单独的线程很容易来管理多个通道。5)NIO和BIO读取文件BIO读取文件:链接BIO从一个阻塞的流中一行一行的读取数据image | left | 469x426NIO读取文件:链接通道是数据的载体,buffer是存储数据的地方,线程每次从buffer检查数据通知给通道image | left | 559x3946)处理数据的线程数NIO:一个线程管理多个连接BIO:一个线程管理一个连接3.NIO简介在Java1.4之前的I/O系统中,提供的都是面向流的I/O系统,系统一次一个字节地处理数据,一个输入流产生一个字节的数据,一个输出流消费一个字节的数据,面向流的I/O速度非常慢,而在Java 1.4中推出了NIO,这是一个面向块的I/O系统,系统以块的方式处理处理,每一个操作在一步中产生或者消费一个数据库,按块处理要比按字节处理数据快的多。在NIO中有几个核心对象需要掌握:缓冲区(Buffer)、通道(Channel)、选择器(Selector)。参考:链接image2.png | center | 851x3834.缓冲区Buffer缓冲区实际上是一个容器对象,更直接的说,其实就是一个数组,在NIO库中,所有数据都是用缓冲区处理的。在读取数据时,它是直接读到缓冲区中的; 在写入数据时,它也是写入到缓冲区中的;任何时候访问 NIO 中的数据,都是将它放到缓冲区中。而在面向流I/O系统中,所有数据都是直接写入或者直接将数据读取到Stream对象中。在NIO中,所有的缓冲区类型都继承于抽象类Buffer,最常用的就是ByteBuffer,对于Java中的基本类型,基本都有一个具体Buffer类型与之相对应,它们之间的继承关系如下图所示:image3.png | center | 650x3681)其中的四个属性的含义分别如下:容量(Capacity):缓冲区能够容纳的数据元素的最大数量。这一个容量在缓冲区创建时被设定,并且永远不能改变。上界(Limit):缓冲区的第一个不能被读或写的元素。或者说,缓冲区中现存元素的计数。位置(Position):下一个要被读或写的元素的索引。位置会自动由相应的 get( )和 put( )函数更新。标记(Mark):下一个要被读或写的元素的索引。位置会自动由相应的 get( )和 put( )函数更新。2)Buffer的常见方法如下所示:flip(): 写模式转换成读模式rewind():将 position 重置为 0 ,一般用于重复读。clear() :compact(): 将未读取的数据拷贝到 buffer 的头部位。mark(): reset():mark 可以标记一个位置, reset 可以重置到该位置。Buffer 常见类型: ByteBuffer 、 MappedByteBuffer 、 CharBuffer 、 DoubleBuffer 、 FloatBuffer 、 IntBuffer 、 LongBuffer 、 ShortBuffer 。3)基本操作Buffer基础操作: 链接缓冲区分片,缓冲区分配,直接缓存区,缓存区映射,缓存区只读:链接4)缓冲区存取数据流程存数据时position会++,当停止数据读取的时候调用flip(),此时limit=position,position=0读取数据时position++,一直读取到limitclear() 清空 buffer ,准备再次被写入 (position 变成 0 , limit 变成 capacity) 。5.通道Channel通道是一个对象,通过它可以读取和写入数据,当然了所有数据都通过Buffer对象来处理。我们永远不会将字节直接写入通道中,相反是将数据写入包含一个或者多个字节的缓冲区。同样不会直接从通道中读取字节,而是将数据从通道读入缓冲区,再从缓冲区获取这个字节。image4.png | center | 368x191在NIO中,提供了多种通道对象,而所有的通道对象都实现了Channel接口。它们之间的继承关系如下图所示:image5.png | center | 650x5171)使用NIO读取数据在前面我们说过,任何时候读取数据,都不是直接从通道读取,而是从通道读取到缓冲区。所以使用NIO读取数据可以分为下面三个步骤:从FileInputStream获取Channel 创建Buffer 将数据从Channel读取到Buffer中 例子:链接 2)使用NIO写入数据使用NIO写入数据与读取数据的过程类似,同样数据不是直接写入通道,而是写入缓冲区,可以分为下面三个步骤:从FileInputStream获取Channel 创建Buffer 将数据从Channel写入到Buffer中 例子:链接 6.反应堆1)阻塞IO模型在老的IO包中,serverSocket和socket都是阻塞式的,因此一旦有大规模的并发行为,而每一个访问都会开启一个新线程。这时会有大规模的线程上下文切换操作(因为都在等待,所以资源全都被已有的线程吃掉了),这时无论是等待的线程还是正在处理的线程,响应率都会下降,并且会影响新的线程。image6.png | center | 739x3362)NIOJava NIO是在jdk1.4开始使用的,它既可以说成“新IO”,也可以说成非阻塞式I/O。下面是java NIO的工作原理:1.由一个专门的线程来处理所有的IO事件,并负责分发。2.事件驱动机制:事件到的时候触发,而不是同步的去监视事件。3.线程通讯:线程之间通过wait,notify等方式通讯。保证每次上下文切换都是有意义的。减少无谓的线程切换。image7.png | center | 689x251注:每个线程的处理流程大概都是读取数据,解码,计算处理,编码,发送响应。7.选择器传统的 server / client 模式会基于 TPR ( Thread per Request ) .服务器会为每个客户端请求建立一个线程.由该线程单独负贵处理一个客户请求。这种模式带未的一个问题就是线程数是的剧增.大量的线程会增大服务器的开销,大多数的实现为了避免这个问题,都采用了线程池模型,并设置线程池线程的最大数量,这又带来了新的问题,如果线程池中有 200 个线程,而有 200 个用户都在进行大文件下载,会导致第 201 个用户的请求无法及时处理,即便第 201 个用户只想请求一个几 KB 大小的页面。传统的 Sorvor / Client 模式如下围所示:image8.png | center | 597x286NIO 中非阻塞IO采用了基于Reactor模式的工作方式,IO调用不会被阻塞,相反是注册感兴趣的特点IO事件,如可读数据到达,新的套接字等等,在发生持定率件时,系统再通知我们。 NlO中实现非阻塞IO的核心设计Selector,Selector就是注册各种IO事件的地方,而且当那些事件发生时,就是这个对象告诉我们所发生的事件。image9.png | center | 462x408当有读或者写等任何注册的事件发生时,可以从Selector中获得相应的SelectionKey,同时从SelectionKey中可以找到发生的事件和该事件所发生的具体的SelectableChannel,以获得客户端发送过来的数据。使用NIO中非阻塞IO编写服务器处理程序,有三个步骤1.向Selector对象注册感兴趣的事件2.从Selector中获取感兴趣的事件3.根据不同事件进行相应的处理8.NIO源码分析Selector是NIO的核心epool模型1)SelectorSelector的open()方法:链接2)ServerSocketChannelServerSocketChannel.open() 链接9.AIOAsynchronous IO异步非阻塞IOBIO ServerSocketNIO ServerSocketChannelAIO AsynchronousServerSocketChannel

wangccsy 2019-12-02 01:46:51 0 浏览量 回答数 0

问题

Netty实现原理浅析 1、总体结构 2、网络模型 3、 buffer 4、Ch?400报错

爱吃鱼的程序员 2020-06-04 11:53:36 3 浏览量 回答数 1

回答

原生XML扩展 我更喜欢使用其中一个原生XML扩展,因为它们与PHP捆绑在一起,通常比所有第三方库更快,并且在标记上给我所需的所有控制权。 DOM DOM扩展允许您使用PHP 5通过DOM API操作XML文档。它是W3C的文档对象模型核心级别3的实现,这是一个平台和语言中立的接口,允许程序和脚本动态访问和更新文件的内容,结构和风格。 DOM能够解析和修改现实世界(破碎)的HTML,并且可以执行XPath查询。它基于libxml。 使用DOM需要一些时间才能提高效率,但这个时间非常值得IMO。由于DOM是一个与语言无关的接口,因此您可以找到多种语言的实现,因此如果您需要更改编程语言,那么您很可能已经知道如何使用该语言的DOM API。 一个基本的用法示例可以在抓取A元素的href属性中找到,一般的概念概述可以在php的DOMDocument中找到 StackOverflow上已经广泛介绍了如何使用DOM扩展,因此如果您选择使用它,您可以确定您遇到的大多数问题都可以通过搜索/浏览Stack Overflow来解决。 XMLReader的 XMLReader扩展是一个XML pull解析器。读取器在文档流上作为光标前进,并在途中停在每个节点上。 与DOM一样,XMLReader基于libxml。我不知道如何触发HTML解析器模块,因此使用XMLReader解析损坏的HTML的可能性可能不如使用DOM,因为您可以明确告诉它使用libxml的HTML解析器模块。 使用php从h1标签获取所有值时,可以找到一个基本用法示例 XML解析器 此扩展允许您创建XML解析器,然后为不同的XML事件定义处理程序。每个XML解析器还有一些您可以调整的参数。 XML Parser库也基于libxml,并实现了SAX样式的XML推送解析器。它可能是比DOM或SimpleXML更好的内存管理选择,但是比XMLReader实现的pull解析器更难以使用。 SimpleXML的 SimpleXML扩展提供了一个非常简单且易于使用的工具集,用于将XML转换为可以使用普通属性选择器和数组迭代器处理的对象。 当您知道HTML是有效的XHTML时,SimpleXML是一个选项。如果你需要解析破碎的HTML,甚至不要考虑SimpleXml,因为它会窒息。 一个基本的用法示例可以在一个简单的CRUD节点程序和xml文件的节点值中找到,PHP手册中还有很多其他的例子。 第三方库(基于libxml) 如果您更喜欢使用第三方库,我建议使用实际上使用DOM / libxml而不是字符串解析的库。 FluentDom - 回购 FluentDOM为PHP中的DOMDocument提供了类似jQuery的流畅XML接口。选择器是用XPath或CSS编写的(使用CSS到XPath转换器)。当前版本扩展了DOM实现标准接口并添加了DOM Living Standard的功能。FluentDOM可以加载JSON,CSV,JsonML,RabbitFish等格式。可以通过Composer安装。 HtmlPageDom Wa72 \ HtmlPageDom`是一个用于轻松操作HTML文档的PHP库。它需要来自Symfony2组件的DomCrawler来遍历DOM树,并通过添加操作HTML文档的DOM树的方法来扩展它。 phpQuery(多年未更新) phpQuery是一个服务器端,可链接,CSS3选择器驱动的文档对象模型(DOM)API,基于用PHP5编写的jQuery JavaScript库,并提供额外的命令行界面(CLI)。 另见:https://github.com/electrolinux/phpquery Zend_Dom Zend_Dom提供了处理DOM文档和结构的工具。目前,我们提供Zend_Dom_Query,它提供了一个统一的界面,可以使用XPath和CSS选择器查询DOM文档。 的QueryPath QueryPath是一个用于操作XML和HTML的PHP​​库。它不仅适用于本地文件,还适用于Web服务和数据库资源。它实现了许多jQuery接口(包括CSS样式的选择器),但它在服务器端使用时经过了大量调整。可以通过Composer安装。 fDOMDocument fDOMDocument扩展了标准DOM,以便在所有错误情况下使用异常,而不是PHP警告或通知。为方便起见,他们还添加了各种自定义方法和快捷方式,并简化了DOM的使用。 军刀/ XML saber / xml是一个包装和扩展XMLReader和XMLWriter类的库,用于创建一个简单的“xml到对象/数组”映射系统和设计模式。编写和读取XML是单遍的,因此可以快速并且需要大型xml文件的低内存。 FluidXML FluidXML是一个用于使用简洁流畅的API来操作XML的PHP​​库。它利用XPath和流畅的编程模式,既有趣又有效。 第三方(不是基于libxml的) 构建DOM / libxml的好处是,您可以获得良好的开箱即用性能,因为您基于本机扩展。但是,并非所有第三方库都沿着这条路线行进。其中一些列在下面 PHP简单的HTML DOM解析器 用PHP5 +编写的HTML DOM解析器允许您以非常简单的方式操作HTML! 需要PHP 5+。 支持无效的HTML。 使用选择器在HTML页面上查找标签,就像jQuery一样。 从一行中提取HTML中的内容。 我一般不推荐这个解析器。代码库很糟糕,解析器本身很慢而且内存很耗。并非所有jQuery选择器(例如子选择器)都是可能的。任何基于libxml的库都应该比这更容易。 PHP Html解析器 PHPHtmlParser是一个简单,灵活的html解析器,允许您使用任何css选择器(如jQuery)选择标签。目标是帮助开发需要快速,简单的方法来废弃html的工具,无论它是否有效!这个项目最初是由sunra / php-simple-html-dom-parser支持的,但支持似乎已经停止,所以这个项目是我对他以前工作的改编。 同样,我不推荐这个解析器。CPU使用率很高,速度相当慢。还没有清除已创建DOM对象的内存的功能。这些问题尤其适用于嵌套循环。文档本身不准确且拼写错误,自4月14日以来没有回复修复。 加农 通用标记器和HTML / XML / RSS DOM解析器 能够操纵元素及其属性 支持无效的HTML和UTF8 可以对元素执行类似CSS3的高级查询(比如jQuery - 支持的命名空间) HTML美化器(如HTML Tidy) 缩小CSS和Javascript 排序属性,更改字符大小写,更正缩进等。 扩展 使用基于当前字符/标记的回调解析文档 操作以较小的功能分隔,以便轻松覆盖 快速而简单 从未使用过它。不知道它是否有用。 HTML 5 您可以使用上面的方法来解析HTML5,但由于HTML5允许的标记,可能会有怪癖。因此,对于HTML5,您要考虑使用专用解析器,例如 html5lib 基于WHATWG HTML5规范的HTML解析器的Python和PHP实现,可与主要桌面Web浏览器实现最大兼容性。 HTML5最终确定后,我们可能会看到更多专用解析器。还有一个W3的博客文章,名为How-To for html 5 parsing,值得一试。 网页服务 如果您不想编写PHP,您也可以使用Web服务。一般来说,我发现这些实用程序很少,但那只是我和我的用例。 ScraperWiki。 ScraperWiki的外部界面允许您以您希望在Web或您自己的应用程序中使用的形式提取数据。您还可以提取有关任何刮刀状态的信息。 常用表达 最后也是最不推荐的,您可以使用正则表达式从HTML中提取数据。通常,不鼓励在HTML上使用正则表达式。 您可以在网上找到与标记相匹配的大多数片段都很脆弱。在大多数情况下,它们只适用于非常特殊的HTML。微小的标记更改,例如在某处添加空格,或添加或更改标记中的属性,可以使RegEx在未正确编写时失败。在HTML上使用RegEx之前,您应该知道自己在做什么。 HTML解析器已经知道HTML的语法规则。必须为您编写的每个新RegEx讲授正则表达式。RegEx在某些情况下很好,但它实际上取决于您的用例。 您可以编写更可靠的解析器,但是使用正则表达式编写完整可靠的自定义解析器是浪费时间,因为上述库已经存在并且在此方面做得更好。

游客gsy3rkgcdl27k 2019-12-02 02:09:37 0 浏览量 回答数 0

问题

LogGroup对银行的应用是怎样的?

轩墨 2019-12-01 21:58:50 1492 浏览量 回答数 0

问题

“svchost.exe”进程导致CPU或内存资源使用率居高不下怎么办?

洛欢 2019-12-01 21:02:38 7719 浏览量 回答数 4

回答

楼主,非常抱歉对您造成困扰和不好感受,如果输入的密码和验证码正确的情况下一般不会无法登陆,建议您清理下浏览器缓存,另外最近有用户反馈关于登陆需要输入验证码的流程很繁琐,此流程的改进已在考虑中,关于版本问题,目前很多其他的网站都同样有向高版本兼容倾向,未来这可能也是种趋势哦,如果您使用的是非ie的浏览器,邮箱登陆仍有问题,麻烦提供浏览器版本名称,通过: http://help.aliyun.com/bug?spm=0.0.0.0.CLqnWt提交问题,会有邮箱团队的人为您处理,谢谢。 ------------------------- 回 2楼(douglas) 的帖子 您好,出现此情况可能与您设置的浏览器模式有关系,打开ie浏览器,按f12,点击浏览器模式 提交问题,会有邮箱团队的人为您处理,谢谢。 ------------------------- 回 5楼(dooli) 的帖子 您好,十分抱歉,目前已购买服务器的云账号无法更改,服务器也无法在不同帐号间转移,请知晓,谢谢。 ------------------------- 回 7楼(dooli) 的帖子 您好,您的验证码截图看起来有点陌生哦,不知道您是在什么连接下出现的此验证码,麻烦再尝试下,如果仍有问题您可以直接拨打我们的售后电话0571-85025885,或者提交工单与我们联系哦,谢谢! ------------------------- 回 9楼(dooli) 的帖子 您好,关于来自110.75.186.226这个ip的嗅探问题已电话与您沟通原因,另外验证码的问题已反馈给相关团队排查,可能需要一定的时间,麻烦耐心等待,谢谢。 ------------------------- 您好,开发经测试确实有出错的情况,目前已经暂时先启用验证问题,您可以再尝试下,详细的优化措施开发会后续再详细讨论,谢谢您及时将问题反馈了我们,督促我们将体验做的更好,十分感谢! ------------------------- 回 12楼(gggxz) 的帖子 您好,昨晚部分用户的web登陆确实有所影响,原因是邮箱后端服务出现异常,工程师已处理,目前已能正常登陆,麻烦您再尝试下,谢谢。

阿里云支持与服务 2019-12-02 02:14:06 0 浏览量 回答数 0

回答

更换服务器~100个是单服务器最大的负荷了你用的是镶嵌式的,要选择服务器机组的那种~刀片式服务器~然后oracl数据库支持分开安装。同步处理~ 你肯定买的是架式服务器~######装ORACLE服务器是刀片式的,6核至强 24G的内存 应该不是服务器瓶颈######oracl装在独立的一台服务器上的话,只支持小形企业和地、市级企业运行 你说的情况,可以理解你的数据量非常庞大,,有可能是省、国家级的数据量了~~ 让你单位给你单独开个服务器房间,更换服务器机柜然后购买刀片式服务器做服务器阵列机组~######数据量倒不会太大,一天1G不到,问题是很多存储过程的逻辑很复杂,一条线程调用存储过程,要等待很久才会返回,直接导致工作线程速度很慢,数据进入速度太快,工作异常状态频繁出现。######必须要实时的存入数据库吗?不能先缓存到服务器,然后让服务器慢慢去处理吗?或者直接将数据记入日志,然后sqlload?######回复 @xinzaibing : 我想到一个蛋疼的方式:数据写文件,文件内容定期入库,程序定期读取数据库计算的结果缓存到内存中。不知道你具体需求,瞎琢磨一个。######回复 @asdfsx : 公司领导一致认为内存不可靠,断电、程序异常什么的...存在内存的数据就没了...真是蛋疼啊######回复 @xinzaibing : 如果数据量不大的话,还有一个方案就是都保存在内存里,然后定时把内存里的结果同步到数据库里。数据库的逻辑挪到程序里..........这个方案比较累啊。另外就是缓存可以加个优先级高低的判断。######目前要求是必须要实时入库,采取写日志文件的方法也可以。 这些数据有一个特点,在某一个时刻会有一个突然出现的峰值,然后又慢慢变少,但是这个时间是不固定的,由于只实用了一条双缓冲队列,所有需要紧急处理的数据和非紧急处理的数据都在队列里,而如果遇到非紧急数据,处理了很长的时间,就直接导致后面的紧急数据失效了...或者导致嵌入式程序判断服务端未收到数据,进而采取重发,导致一条队列里有非常多重复的数据。######我可能会使用数据写入日志文件,然后定时将日志入库的办法操作######大概意思可能是多线程对数据库表的操作导致数据表锁定,性能损失在内耗上了。。那数据表采用行级锁呢?(这样会增大系统开销)我是菜鸟,求教  ######回复 @xinzaibing : 这个应该是属于最初的设计问题,hohoho######回复 @asdfsx : 目前我也在往这方面考虑,如果数据分类处理。那就得大改结构了...唉######回复 @xinzaibing : 建议根据上传的不同数据进行不同的处理,不要一股脑的都放在缓存中,如果是心跳的话,应该立即响应,如果是要处理的数据的话,才需要进行缓存等待处理######ORACLE默认就是行级锁的应该.. 主要是数据的写入速度远远小于数据上传的速度,导致了缓存溢出,紧急数据不能得到及时处理,大量数据出现超时失效,无法对嵌入式的采集器程序作出及时的心跳相应和其他回复(因为都在队列中,无法处理,无心跳的话嵌入式采集器会误认为服务器断线)。最终导致单台服务器接入数据的嵌入式设备的数量太少,不满足需求。######去年刚毕业,由于公司小,一个人搞后台,压力太大啊...大家指指招呗~ @中山野鬼######今天到图书馆看了一本书《让Orcale跑的更快点》,上面说可以从如下几个方面优化: 数据库方面:建适当的索引,固定长度;查询条件比较尽量简化;不同的表放在不同的磁盘里…… 服务层:增大缓存,(有没有数据库连接池不知道你能用上不) 软件层:对Java使用PaperStatement 囫囵吞枣就记得这么多了。。。哭~~######非常感谢...我去看看这本书 :)######我不清楚你的数据采集的内容是什么。不过看的出,对实时性要求高。换我,基本上就一个思路。 1、做个前段服务器,什么事情都不干,只进行数据的压缩。然后所有数据库和计算操作,放到后端。 至于并发,你这种 1W=100台服务器的方式治标不治本。######@中山野鬼 是说对数据进行预处理,提取有效内容?还是就是zip?######回复 @asdfsx : 不一样的。而是数据压缩。采样数据中间,信息密度不会太大的。######老鬼的思路有点像我说的那个数据写日志文件,或者内存缓存定时入库...........都被否定了啊######@xinzaibing 还有一个建议,上传的数据加一个验证,如果上传的数据已经插入缓存,就不要再次插入了。无脑插入插到崩也不是什么好主意啊######回复 @asdfsx : 要回复的,要处理成功后才回复,存库失败或者某些异常导致服务端崩溃重启,就不进行回复,客户端会持续地进行重发,重发到一定次数后,存本地,等恢复正常后发送存本地的数据

kun坤 2020-06-09 11:56:38 0 浏览量 回答数 0

回答

业务代码中有数据库事务锁,其实是卡在事务上了,或者是某个地方不恰当的使用了锁######回复 @malie0 : 打断点应该属于特殊情况,你直接Thread.sleep(5000L)并发测,再看看请求响应的顺序。######回复 @malie0 : debug看下,线程池是不是耗尽了######应该没用到事务锁,response后面的代码可以什么都不写,就一个systemout的打印输出,然后打个断点,后面的请求就会卡住######在reponse返回的代码后面接下来继续处理业务逻辑 什么意思?reponse写出流之后,继续使用线程?那这个线程不会返回给servlet容器的线程池,接下来的请求会被给线程池中其他的线程######回复 @gaomq : 但是只要执行了response的返回就能立刻返回给前台的,跟后台有没有执行完方法没关系,应该不需要用异步servlet######回复 @malie0 : reponse想先返回,需要开异步。一个request和response处理一次请求。######对啊,就是当前线程在返回response响应代码后继续在处理业务逻辑,按照道理后面的请求会用另一个线程去处理,但是卡住了,只有等到这个线程处理完了才会处理下一个请求######http1.1在一个连接上,前一个请求收到响应才会发起下一个请求,是串行的。比如:请求1-->响应1,然后请求2-->响应2。 浏览器对同一个域名能建立的连接是有数量限制的。 猜测你的两个请求走的是同一个连接。######回复 @gaomq : 查到了这个,你看看https://stackoverflow.com/questions/45583861/how-does-http2-solve-head-of-line-blocking-hol-issue#answer-45583977,有问题再交流。######回复 @gaomq :谢谢你的回复,我又想了想,“并且多个请求可以重叠进行”好像不行。假如请求1和2连续发出,服务器端请求1由线程1处理,请求2由线程2处理,线程2处理的快,先写出了响应(响应2的TCP包应该是在响应1的前面的,但我不确定),客户端应该是识别不出这个响应是请求1还是请求2的。你有没有文章佐证你的观点。我说的是串行的,是我从http1.1和http2区别上看到的。######http 1.1则可以在一次连接中处理多个请求,并且多个请求可以重叠进行,不需要等待一个请求结束后再发送下一个请求######这个有没有办法解决,让他们走不同的连接,前台ajax或者后台的servlet的配置?######应该是你前端的业务是串行执行的吧,后端单线程还是多线程都是可以自己配置的,不同的web层框架,配置的方式不同。######前端是在得到一个响应后才会触发另一个,但是对于后端来说应该两次响应要用不同的线程去处理,现在的情况感觉后端就是用了同一个线程在处理,所以出现了串行的问题######ajax不是可以设置是否异步处理,######ajax应该是异步的。就算是同步也不是导致我的问题的原因,因为ajax已经根据第一次返回的响应发起了第二次请求,后台是第二次请求发起后阻塞了。ajax的异步和同步只是前端要不要等待响应结果才做其他事情还是在请求没返回响应的时候还能执行其他代码######java servlet是多线程的,http1的ajax是阻塞的,也就是只有等到结果返回才能进行下次请求,http2的ajax才是真正的异步,http1、http2这个是刚好前几天在这里看到一篇博客。######但是我的问题是前端已经接收到第一次请求的响应才发起第二个请求,后端在第二个请求的时候阻塞了,我觉得跟前端应该已经没关系了######感觉像是AJAX没有开启异步处理吧。######请问这个问题解决了吗?请教下

kun坤 2020-05-30 14:36:49 0 浏览量 回答数 0

回答

delete []不知道分配的大小。但是,该知识驻留在运行时或操作系统的内存管理器中,这意味着编译器在编译期间不可用。而sizeof()不是一个真正的功能,它实际上是评价由编译器的常数,这是一件好事它不能用于动态分配数组,其大小在编译期间是不知道做。 另外,请考虑以下示例: int *arr = new int[256]; int *p = &arr[100]; printf("Size: %d\n", sizeof(p)); 编译器将如何知道其大小p?问题的根源在于C和C ++中的数组不是一流的对象。它们会衰减为指针,编译器或程序本身无法知道指针是否指向分配的内存块的开头new,或单个对象或块中间的某个位置。由分配的内存new。 原因之一是C和C ++将内存管理留给了程序员和操作系统,这也是为什么它们没有垃圾回收的原因。实施new和delete不是C ++标准的一部分,因为C ++,就是要在各种不同的平台,它可以以非常不同的方式管理他们的记忆中。如果您正在为运行在最新Intel CPU上的Windows框编写文字处理器,则可以让C ++跟踪所有分配的数组及其大小,但是当编写在Windows XP上运行的嵌入式系统时,这可能是完全不可行的。 DSP。

保持可爱mmm 2020-02-10 16:04:59 0 浏览量 回答数 0

问题

请问一下,有谁知道云哪里投诉阿里云。

熊贝贝 2019-12-01 21:23:16 8025 浏览量 回答数 3

问题

当我尝试在Android模拟器上运行Junit测试用例时,为什么会发生此错误?

LiuWH 2020-01-25 11:47:29 0 浏览量 回答数 1

问题

【精品问答】Java技术1000问(1)

问问小秘 2019-12-01 21:57:43 39926 浏览量 回答数 17

问题

ECS Windows 系统 SVCHOST.EXE进程占用资源(CPU,内存)较高的处理是什么

boxti 2019-12-01 22:06:56 2057 浏览量 回答数 0

回答

简介 如果您听说过 Node,或者阅读过一些文章,宣称 Node 是多么多么的棒,那么您可能会想:“Node 究竟是什么东西?”尽管不是针对所有人的,但 Node 可能是某些人的正确选择。 为试图解释什么是 Node.js,本文探究了它能解决的问题,它如何工作,如何运行一个简单应用程序,最后,Node 何时是和何时不是一个好的解决方案。本文不涉及如何编写一个复杂的 Node 应用程序,也不是一份全面的 Node 教程。阅读本文应该有助于您决定是否应该学习 Node,以便将其用于您的业务。 Node 旨在解决什么问题? Node 公开宣称的目标是 “旨在提供一种简单的构建可伸缩网络程序的方法”。当前的服务器程序有什么问题?我们来做个数学题。在 Java™ 和 PHP 这类语言中,每个连接都会生成一个新线程,每个新线程可能需要 2 MB 配套内存。在一个拥有 8 GB RAM 的系统上,理论上最大的并发连接数量是 4,000 个用户。随着您的客户端基础的增长,您希望您的 web 应用程序支持更多用户,这样,您必须添加更多服务器。当然,这会增加业务成本,尤其是服务器成本、运输成本和人工成本。除这些成本上升外,还有一个技术问题:用户可能针对每个请求使用不同的服务器,因此,任何共享资源都必须在所有服务器之间共享。例如,在 Java 中,静态变量和缓存需要在每个服务器上的 JVMs 之间共享。这就是整个 web 应用程序架构中的瓶颈:一个服务器能够处理的并发连接的最大数量。 Node 解决这个问题的方法是:更改连接连接到服务器的方式。每个连接都创建一个进程,该进程不需要配套内存块,而不是为每个连接生成一个新的 OS 线程(并向其分配一些配套内存)。Node 声称它绝不会死锁,因为它根本不允许使用锁,它不会直接阻塞 I/O 调用。Node 还宣称,运行它的服务器能支持数万个并发连接。事实上,Node 通过将整个系统中的瓶颈从最大连接数量更改到单个系统的流量来改变服务器面貌。 现在您有了一个能处理数万条并发连接的程序,那么您能通过 Node 实际构建什么呢?如果您有一个 web 应用程序需要处理这么多连接,那将是一件很 “恐怖” 的事!那是一种 “如果您有这个问题,那么它根本不是问题” 的问题。在回答上面的问题之前,我们先看看 Node 如何工作以及它被设计的如何运行。 Node 肯定不是什么 没错,Node 是一个服务器程序。但是,它肯定不 像 Apache 或 Tomcat。那些服务器是独立服务器产品,可以立即安装并部署应用程序。通过这些产品,您可以在一分钟内启动并运行一个服务器。Node 肯定不是这种产品。Apache 能添加一个 PHP 模块来允许开发人员创建动态 web 页,使用 Tomcat 的程序员能部署 JSPs 来创建动态 web 页。Node 肯定不是这种类型。 在 Node 的早期阶段(当前是 version 0.4.6),它还不是一个 “运行就绪” 的服务器程序,您还不能安装它,向其中放置文件,拥有一个功能齐全的 web 服务器。即使是要实现 web 服务器在安装完成后启动并运行这个基本功能,也还需要做大量工作。 Node 如何工作 Node 本身运行 V8 JavaScript。等等,服务器上的 JavaScript?没错,您没有看错。服务器端 JavaScript 是一个相对较新的概念,这个概念是大约两年前在 developerWorks 上讨论 Aptana Jaxer 产品时提到的(参见 参考资料)。尽管 Jaxer 一直没有真正流行,但这个理念本身并不是遥不可及的 — 为何不能在服务器上使用客户机上使用的编程语言? 什么使 V8?V8 JavaScript 引擎是 Google 用于他们的 Chrome 浏览器的底层 JavaScript 引擎。很少有人考虑 JavaScript 在客户机上实际做了些什么?实际上,JavaScript 引擎负责解释并执行代码。使用 V8,Google 创建了一个以 C++ 编写的超快解释器,该解释器拥有另一个独特特征;您可以下载该引擎并将其嵌入任何 应用程序。它不仅限于在一个浏览器中运行。因此,Node 实际上使用 Google 编写的 V8 JavaScript 引擎并将其重建为在服务器上使用。太完美了!既然已经有一个不错的解决方案可用,为何还要创建一种新语言呢? 事件驱动编程 许多程序员接受的教育使他们认为,面向对象编程是完美的编程设计,而对其他编程方法不屑一顾。Node 使用一个所谓的事件驱动编程模型。 清单 1. 客户端上使用 jQuery 的事件驱动编程 复制代码 代码如下: // jQuery code on the client-side showing how Event-Driven programming works // When a button is pressed, an Event occurs - deal with it // directly right here in an anonymous function, where all the // necessary variables are present and can be referenced directly $("#myButton").click(function(){ if ($("#myTextField").val() != $(this).val()) alert("Field must match button text"); }); 实际上,服务器端和客户端没有任何区别。没错,这没有按钮点击操作,也没有向文本字段键入的操作,但在一个更高的层面上,事件正在 发生。一个连接被建立 — 事件!数据通过连接接收 — 事件!数据通过连接停止 — 事件! 为什么这种设置类型对 Node 很理想?JavaScript 是一种很棒的事件驱动编程语言,因为它允许匿名函数和闭包,更重要的是,任何写过代码的人都熟悉它的语法。事件发生时调用的回调函数可以在捕获事件处编写。这样,代码容易编写和维护,没有复杂的面向对象框架,没有接口,没有在上面架构任何内容的潜能。只需监听事件,编写一个回调函数,然后,事件驱动编程将照管好一切! 示例 Node 应用程序 最后,我们来看一些代码!让我们将讨论过的所有内容综合起来,创建我们的第一个 Node 应用程序。由于我们已经知道,Node 对于处理高流量应用程序很理想,我们就来创建一个非常简单的 web 应用程序 — 一个为实现最大速度而构建的应用程序。下面是 “老板” 交代的关于我们的样例应用程序的具体要求:创建一个随机数字生成器 RESTful API。这个应用程序应该接受一个输入:一个名为 “number” 的参数。然后,应用程序返回一个介于 0 和该参数之间的随机数字,并将生成的数字返回调用者。由于 “老板” 希望它成为一个广泛流行的应用程序,因此它应该能处理 50,000 个并发用户。我们来看看代码: 清单 2. Node 随机数字生成器 复制代码 代码如下: // these modules need to be imported in order to use them. // Node has several modules. They are like any #include // or import statement in other languages var http = require("http"); var url = require("url"); // The most important line in any Node file. This function // does the actual process of creating the server. Technically, // Node tells the underlying operating system that whenever a // connection is made, this particular callback function should be // executed. Since we're creating a web service with REST API, // we want an HTTP server, which requires the http variable // we created in the lines above. // Finally, you can see that the callback method receives a 'request' // and 'response' object automatically. This should be familiar // to any PHP or Java programmer. http.createServer(function(request, response) { // The response needs to handle all the headers, and the return codes // These types of things are handled automatically in server programs // like Apache and Tomcat, but Node requires everything to be done yourself response.writeHead(200, {"Content-Type": "text/plain"}); // Here is some unique-looking code. This is how Node retrives // parameters passed in from client requests. The url module // handles all these functions. The parse function // deconstructs the URL, and places the query key-values in the // query object. We can find the value for the "number" key // by referencing it directly - the beauty of JavaScript. var params = url.parse(request.url, true).query; var input = params.number; // These are the generic JavaScript methods that will create // our random number that gets passed back to the caller var numInput = new Number(input); var numOutput = new Number(Math.random() * numInput).toFixed(0); // Write the random number to response response.write(numOutput); // Node requires us to explicitly end this connection. This is because // Node allows you to keep a connection open and pass data back and forth, // though that advanced topic isn't discussed in this article. response.end(); // When we create the server, we have to explicitly connect the HTTP server to // a port. Standard HTTP port is 80, so we'll connect it to that one. }).listen(80); // Output a String to the console once the server starts up, letting us know everything // starts up correctly console.log("Random Number Generator Running..."); 将上面的代码放到一个名为 “random.js” 的文件中。现在,要启动这个应用程序并运行它(进而创建 HTTP 服务器并监听端口 80 上的连接),只需在您的命令提示中输入以下命令:% node random.js。下面是服务器已经启动并运行时它看起来的样子: 复制代码 代码如下: root@ubuntu:/home/moila/ws/mike# node random.js Random Number Generator Running... 访问应用程序 应用程序已经启动并运行。Node 正在监听任何连接,我们来测试一下。由于我们创建了一个简单的 RESTful API,我们可以使用我们的 web 浏览器来访问这个应用程序。键入以下地址(确保您完成了上面的步骤):localhost/?number=27。 您的浏览器窗口将更改到一个介于 0 到 27 之间的随机数字。单击浏览器上的 “重新载入” 按钮,将得到另一个随机数字。就是这样,这就是您的第一个 Node 应用程序! Node 对什么有好处? 到此为止,应该能够回答 “Node 是什么” 这个问题了,但您可能还不清楚什么时候应该使用它。这是一个需要提出的重要问题,因为 Node 对有一些东西有好处,但相反,对另一些东西而言,目前 Node 可能不是一个好的解决方案。您需要小心决定何时使用 Node,因为在错误的情况下使用它可能会导致一个多余编码的 LOT。 它对什么有好处? 正如您此前所看到的,Node 非常适合以下情况:您预计可能有很高的流量,而在响应客户端之前服务器端逻辑和处理所需不一定是巨大的。Node 表现出众的典型示例包括: 1.RESTful API 提供 RESTful API 的 web 服务接收几个参数,解析它们,组合一个响应,并返回一个响应(通常是较少的文本)给用户。这是适合 Node 的理想情况,因为您可以构建它来处理数万条连接。它还不需要大量逻辑;它只是从一个数据库查找一些值并组合一个响应。由于响应是少量文本,入站请求时少量文本,因此流量不高,一台机器甚至也可以处理最繁忙的公司的 API 需求。 2.Twitter 队列 想像一下像 Twitter 这样的公司,它必须接收 tweets 并将其写入一个数据库。实际上,每秒几乎有数千条 tweets 达到,数据库不可能及时处理高峰时段需要的写入数量。Node 成为这个问题的解决方案的重要一环。如您所见,Node 能处理数万条入站 tweets。它能迅速轻松地将它们写入一个内存排队机制(例如 memcached),另一个单独进程可以从那里将它们写入数据库。Node 在这里的角色是迅速收集 tweet 并将这个信息传递给另一个负责写入的进程。想象一下另一种设计 — 一个常规 PHP 服务器自己试图处理对数据库的写入 — 每个 tweet 将在写入数据库时导致一个短暂的延迟,这是因为数据库调用正在阻塞通道。由于数据库延迟,一台这样设计的机器每秒可能只能处理 2000 条入站 tweets。每秒 100 万条 tweets 需要 500 个服务器。相反,Node 能处理每个连接而不会阻塞通道,从而能捕获尽可能多的 tweets。一个能处理 50,000 条 tweets 的 Node 机器只需要 20 个服务器。 3.映像文件服务器 一个拥有大型分布式网站的公司(比如 Facebook 或 Flickr)可能会决定将所有机器只用于服务映像。Node 将是这个问题的一个不错的解决方案,因为该公司能使用它编写一个简单的文件检索器,然后处理数万条连接。Node 将查找映像文件,返回文件或一个 404 错误,然后什么也不用做。这种设置将允许这类分布式网站减少它们服务映像、.js 和 .css 文件等静态文件所需的服务器数量。 它对什么有坏处? 当然,在某些情况下,Node 并非理想选择。下面是 Node 不擅长的领域: 1.动态创建的页 目前,Node 没有提供一种默认方法来创建动态页。例如,使用 JavaServer Pages (JSP) 技术时,可以创建一个在这样的 JSP 代码段中包含循环的 index.jsp 页。Node 不支持这类动态的、HTML 驱动的页面。同样,Node 不太适合作为 Apache 和 Tomcat 这样的网页服务器。因此,如果您想在 Node 中提供这样一个服务器端解决方案,必须自己编写整个解决方案。PHP 程序员不想在每次部署 web 应用程序时都编写一个针对 Apache 的 PHP 转换器,当目前为止,这正是 Node 要求您做的。 2. 关系数据库重型应用程序 Node 的目的是快速、异步和非阻塞。数据库并不一定分享这些目标。它们是同步和阻塞的,因为读写时对数据库的调用在结果生成之前将一直阻塞通道。因此,一个每个请求都需要大量数据库调用、大量读取、大量写入的 web 应用程序非常不适合 Node,这是因为关系数据库本身就能抵销 Node 的众多优势。(新的 NoSQL 数据库更适合 Node,不过那完全是另一个主题了。) 结束语 问题是 “什么是 Node.js?” 应该已经得到解答。阅读本文之后,您应该能通过几个清晰简洁的句子回答这个问题。如果这样,那么您已经走到了许多编码员和程序员的前面。我和许多人都谈论过 Node,但它们对 Node 究竟是什么一直很迷惑。可以理解,他们具有的是 Apache 的思维方式 — 服务器是一个应用程序,将 HTML 文件放入其中,一切就会正常运转。而 Node 是目的驱动的。它是一个软件程序,使用 JavaScript 来允许程序员轻松快速地创建快速、可伸缩的 web 服务器。Apache 是运行就绪的,而 Node 是编码就绪的。 Node 完成了它提供高度可伸缩服务器的目标。它并不分配一个 “每个连接一个线程” 模型,而是使用一个 “每个连接一个流程” 模型,只创建每个连接需要的内存。它使用 Google 的一个非常快速的 JavaScript 引擎:V8 引擎。它使用一个事件驱动设计来保持代码最小且易于阅读。所有这些因素促成了 Node 的理想目标 — 编写一个高度可伸缩的解决方案变得比较容易。 与理解 Node 是 什么同样重要的是,理解它不是 什么。Node 并不是 Apache 的一个替代品,后者旨在使 PHP web 应用程序更容易伸缩。事实确实如此。在 Node 的这个初始阶段,大量程序员使用它的可能性不大,但在它能发挥作用的场景中,它的表现非常好。 将来应该期望从 Node 得到什么呢?这也许是本文引出的最重要的问题。既然您知道了它现在的作用,您应该会想知道它下一步将做什么。在接下来的一年中,我期待着 Node 提供与现有的第三方支持库更好地集成。现在,许多第三方程序员已经研发了用于 Node 的插件,包括添加文件服务器支持和 MySQL 支持。希望 Node 开始将它们集成到其核心功能中。最后,我还希望 Node 支持某种动态页面模块,这样,您就可以在 HTML 文件中执行在 PHP 和 JSP(也许是一个 NSP,一个 Node 服务器页)中所做的操作。最后,希望有一天会出现一个 “部署就绪” 的 Node 服务器,可以下载和安装,只需将您的 HTML 文件放到其中,就像使用 Apache 或 Tomcat 那样。Node 现在还处于初始阶段,但它发展得很快,可能不久就会出现在您的视野中。 答案来源于网络

养狐狸的猫 2019-12-02 02:17:03 0 浏览量 回答数 0

回答

Java之JVM垃圾回收 内存结构以及垃圾回收算法前言:由于小组技术分享的需要,懂的不是很多所以我就找了这个我自己感兴趣的知识点给大家做个简单的介绍。由于是新人,算不了很懂,只是总结性的讲了些概念性的东西。给大家分享的同时,算是给自己做个笔记吧。作为Java语言的核心之一,JVM垃圾回收帮我们解决了让我们很头疼的垃圾回收问题。我们不需要像VC++一样,作为内存管理的统治者需要我们对我们分配的每一块内存进行回收,否则就会造成内存泄露问题。是不是只要有JVM存在我们就不会出现内存泄露问题,出现内存泄露问题我们又该怎么办,如果我们想提高我们程序的稳定性和其他性能我们能从什么地方下手!!!相信这些问题是我们程序过程中不可逾越的。了解JVM的内存分配及其相应的垃圾回收机制,不仅仅是可以了解底层的JVM运行机制,而且对于程序性能的优化和提升还是很有必要的。一、JVM内存分配区域结构图一从图一可以看出JVM中的内存分配包括PC Register(PC寄存器) JVM栈 堆(Heap) 方法区域(MethodArea)运行时常量池(RuntimeConstant Pool) 本地方法堆栈(NativeMethod Stacks),这几部分区域但是从程序员的角度来看我们只关注JVM Heap和JVM Stack,因为这两部分是直接关系程序运行期间的内存状态,所以我会主要介绍这两部分内存,其他的我只是给出了简单的一些概念性解释:PC Register(Program Counter 寄存器):主要作用是记录当前线程所执行的字节码的行号。方法区域(MethodArea):方法区域存放了所加载的类的信息(名称、修饰符等)、类中的静态变量、类中定义为final类型的常量、类中的Field信息、类中的方法信息,法区域也是全局共享的,它在虚拟机启动时在一定的条件下它也会被GC,当方法区域需要使用的内存超过其允许的大小时,会抛出OutOfMemory的错误信息。运行时常量池(RuntimeConstant Pool):存放的为类中的固定的常量信息、方法和Field的引用信息等,其空间从方法区域中分配。本地方法堆栈(NativeMethod Stacks):JVM采用本地方法堆栈来支持native方法的执行,此区域用于存储每个native方法调用的状态。JVM栈:主要存放一些基本类型的变量和对象的引用变量。JVM堆:用来存放由 new 创建的对象和数组Java 虚拟机的自动垃圾回收器来管理(注意数组也是对象,所以说数组也是存放在JVM堆中)。由于栈中存放的是主要存放一些基本类型的变量和对象的引用变量,所以当过了变量的作用区域或者是当程序运行结束后它所占用的内存会自动的释放掉,所以不用来关心,下面我们主要来说的是堆内存的分配以及回收的算法。二、JVM堆内存介绍工欲善其事,必先利其器。所以了解堆内存的内部结构是很必要的。在Jvm中堆空间划分为三个代:年轻代(Young Generation)、年老代(Old Generation)和永久代(Permanent Generation)。年轻带主要是动态的存储,年轻带主要储存新产生的对象,年老代储存年龄大些的对象,永久带主要是存储的是java的类信息,包括解析得到的方法、属性、字段等。永久带基本不参与垃圾回收。所以说我们说的垃圾回收主要是针对年轻代和年老代。图二年轻代又分成3个部分,一个eden区和两个相同的survior区。刚开始创建的对象都是放置在eden区的。分成这样3个部分,主要是为了生命周期短的对象尽量留在年轻带。当eden区申请不到空间的时候,进行minorGC,把存活的对象拷贝到survior。年老代主要存放生命周期比较长的对象,比如缓存对象。(经过IBM的一个研究机构研究数据表明,基本上80%-98%的对象都会在年轻代的Eden区死掉从而本回收掉,所以说真正进入到老年代的对象很少,这也是为什么MinorGC比MajorGC更加频繁的原因)具体JVM内存垃圾回收过程描述如下 :1、对象在Eden区完成内存分配2、当Eden区满了,再创建对象,会因为申请不到空间,触发minorGC,进行young(eden+1survivor)区的垃圾回收3、minorGC时,Eden不能被回收的对象被放入到空的survivor(Eden肯定会被清空),另一个survivor里不能被GC回收的对象也会被放入这个survivor,始终保证一个survivor是空的4、当做第3步的时候,如果发现survivor满了,则这些对象被copy到old区,或者survivor并没有满,但是有些对象已经足够Old,也被放入Old区 XX:MaxTenuringThreshold5、当Old区被放满的之后,进行fullGC补充: MinorGC:年轻代所进行的垃圾回收,非常频繁,一般回收速度也比较快。 MajorGC:老年代进行的垃圾回收,发生一次MajorGC至少伴随一次MinorGC,一般比MinorGC速度慢十倍以上。 FullGC:整个堆内存进行的垃圾回收,很多时候是MajorGC 以后就是堆内存结构已经大致的垃圾回收过程。三、对象分配原则1.对象优先分配在Eden区,如果Eden区没有足够的空间时,虚拟机执行一次Minor GC。2.大对象直接进入老年代(大对象是指需要大量连续内存空间的对象)。这样做的目的是避免在Eden区和两个Survivor区之间发生大量的内存拷贝(新生代采用复制算法收集内存)。3.长期存活的对象进入老年代。虚拟机为每个对象定义了一个年龄计数器,如果对象经过了1次Minor GC那么对象会进入Survivor区,之后每经过一次Minor GC那么对象的年龄加1,知道达到阀值对象进入老年区。4.动态判断对象的年龄。如果Survivor区中相同年龄的所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象可以直接进入老年代。5.空间分配担保。每次进行Minor GC时,JVM会计算Survivor区移至老年区的对象的平均大小,如果这个值大于老年区的剩余值大小则进行一次Full GC,如果小于检查HandlePromotionFailure设置,如果true则只进行Monitor GC,如果false则进行Full GC。四、垃圾收集器作为JVM中的核心之一垃圾收集器,主要完成的功能包括:(1)发现无用信息对象;(2)回收被无用对象占用的内存空间,使该空间可被程序再次使用。所以说我们在实现垃圾收集器的同时就要实现两个算法一个是发现无用的对象第二就是回收该对象的内存。收集器主要分为引用计数器和跟踪收集器两种,Sun JDK中采用跟踪收集器作为GC实现策略。发现无用对象只要的实现算法包括引用计数法和根搜索算法,引用计数法主要是JVM的早期实现方法,因为引用计数无法解决循环引用的问题,所以现在JVM实现的主要是根搜索算法,引用计数法:堆中的每个对象对应一个引用计数器。当每一次创建一个对象并赋给一个变量时,引用计数器置为1。当对象被赋给任意变量时,引用计数器每次加1当对象出了作用域后(该对象丢弃不再使用),引用计数器减1,一旦引用计数器为0,对象就不可用从而可以被回收。 根搜索算法:通过一系列的名为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连(用图论的话来说就是从GC Roots到这个对象不可达)时,则证明此对象是不可用的。目前的收集器主要有三种:串行收集器:使用单线程处理所有垃圾回收工作,因为无需多线程交互,所以效率比较高并行收集器:对年轻代进行并行垃圾回收,因此可以减少垃圾回收时间。一般在多线程多处理器机器上使用并发收集器:可以保证大部分工作都并发进行(应用不停止),垃圾回收只暂停很少的时间,此收集器适合对响应时间要求比较高的中、大规模应用五、垃圾收集器的回收算法Copying算法:算法:复制采用的方式为从根集合扫描出存活的对象,并将找到的存活对象复制到一块新的完全未使用的空间中。 过程: 此算法把内存空间划为两个相等的区域,每次只使用其中一个区域。垃圾回收时,遍历当前使用区域,把正在使用中的对象复制到另外一个区域中。次算法每次只处理正在使用中的对象,因此复制成本比较小,同时复制过去以后还能进行相应的内存整理,不过出现“碎片”问题。当然,此算法的缺点也是很明显的,就是需要两倍内存空间。Mark-Sweep算法: 算法:标记-清除采用的方式为从根集合开始扫描,对存活的对象进行标记,标记完毕后,再扫描整个空间中未标记的对象,并进行回收。 过程: 第一阶段从引用根节点开始标记所有被引用的对象,第二阶段遍历整个堆,把未标记的对象清除。它停止所有工作,收集器从根开始访问每一个活跃的节点,标记它所访问的每一个节点。走过所有引用后,收集就完成了,然后就对堆进行清除(即对堆中的每一个对象进行检查),所有没有标记的对象都作为垃圾回收并返回空闲列表。Mark-Compact算法: 算法:标记阶段与“Mark-Sweep”算法相同,但在清除阶段有所不同。在回收不存活对象所占用的内存空间后,会将其他所有存活对象都往左端空闲的空间进行移动,并更新引用其对象指针。过程:此算法结合了“标记-清除”和“复制”两个算法的优点。也是分两阶段,第一阶段从根节点开始标记所有被引用对象,第二阶段遍历整个堆,把清除未标记对象并且把存活对象“压缩”到堆的其中一块,按顺序排放。此算法避免了“标记-清除”的碎片问题,同时也避免了“复制”算法的空间问题。Sun JDK GC策略:新生代算法实现:Copying,Copying,Copying旧生代算发实现:Mark-Sweep-Compact,Mark –Compact,Mark –Sweep!!六、JvisuaVM 工具如果我们想优化自己的程序,那么我们就必须清楚的了解不同代码程序所消耗的性能多少,作为JDK的一部分,这个工具给我们提供了很大的帮助。这个工具可以在JDK的bin目录下找到,功能很强大,可以注意利用

auto_answer 2019-12-02 01:56:35 0 浏览量 回答数 0

回答

初识 MyBatis MyBatis 是第一个支持自定义 SQL、存储过程和高级映射的类持久框架。MyBatis 消除了大部分 JDBC 的样板代码、手动设置参数以及检索结果。MyBatis 能够支持简单的 XML 和注解配置规则。使 Map 接口和 POJO 类映射到数据库字段和记录。 MyBatis 的特点 那么 MyBatis 具有什么特点呢?或许我们可以从如下几个方面来描述 MyBatis 中的 SQL 语句和主要业务代码分离,我们一般会把 MyBatis 中的 SQL 语句统一放在 XML 配置文件中,便于统一维护。 解除 SQL 与程序代码的耦合,通过提供 DAO 层,将业务逻辑和数据访问逻辑分离,使系统的设计更清晰,更易维护,更易单元测试。SQL 和代码的分离,提高了可维护性。 MyBatis 比较简单和轻量 本身就很小且简单。没有任何第三方依赖,只要通过配置 jar 包,或者如果你使用 Maven 项目的话只需要配置 Maven 以来就可以。易于使用,通过文档和源代码,可以比较完全的掌握它的设计思路和实现。 屏蔽样板代码 MyBatis 回屏蔽原始的 JDBC 样板代码,让你把更多的精力专注于 SQL 的书写和属性-字段映射上。 编写原生 SQL,支持多表关联 MyBatis 最主要的特点就是你可以手动编写 SQL 语句,能够支持多表关联查询。 提供映射标签,支持对象与数据库的 ORM 字段关系映射 ORM 是什么?对象关系映射(Object Relational Mapping,简称ORM) ,是通过使用描述对象和数据库之间映射的元数据,将面向对象语言程序中的对象自动持久化到关系数据库中。本质上就是将数据从一种形式转换到另外一种形式。 提供 XML 标签,支持编写动态 SQL。 你可以使用 MyBatis XML 标签,起到 SQL 模版的效果,减少繁杂的 SQL 语句,便于维护。 MyBatis 整体架构 MyBatis 最上面是接口层,接口层就是开发人员在 Mapper 或者是 Dao 接口中的接口定义,是查询、新增、更新还是删除操作;中间层是数据处理层,主要是配置 Mapper -> XML 层级之间的参数映射,SQL 解析,SQL 执行,结果映射的过程。上述两种流程都由基础支持层来提供功能支撑,基础支持层包括连接管理,事务管理,配置加载,缓存处理等。 接口层 在不与Spring 集成的情况下,使用 MyBatis 执行数据库的操作主要如下: InputStream is = Resources.getResourceAsStream("myBatis-config.xml"); SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); SqlSessionFactory factory = builder.build(is); sqlSession = factory.openSession(); 其中的SqlSessionFactory,SqlSession是 MyBatis 接口的核心类,尤其是 SqlSession,这个接口是MyBatis 中最重要的接口,这个接口能够让你执行命令,获取映射,管理事务。 数据处理层 配置解析 在 Mybatis 初始化过程中,会加载 mybatis-config.xml 配置文件、映射配置文件以及 Mapper 接口中的注解信息,解析后的配置信息会形成相应的对象并保存到 Configration 对象中。之后,根据该对象创建SqlSessionFactory 对象。待 Mybatis 初始化完成后,可以通过 SqlSessionFactory 创建 SqlSession 对象并开始数据库操作。 SQL 解析与 scripting 模块 Mybatis 实现的动态 SQL 语句,几乎可以编写出所有满足需要的 SQL。 Mybatis 中 scripting 模块会根据用户传入的参数,解析映射文件中定义的动态 SQL 节点,形成数据库能执行的SQL 语句。 SQL 执行 SQL 语句的执行涉及多个组件,包括 MyBatis 的四大核心,它们是: Executor、StatementHandler、ParameterHandler、ResultSetHandler。SQL 的执行过程可以用下面这幅图来表示 MyBatis 层级结构各个组件的介绍(这里只是简单介绍,具体介绍在后面): SqlSession: ,它是 MyBatis 核心 API,主要用来执行命令,获取映射,管理事务。接收开发人员提供 Statement Id 和参数。并返回操作结果。Executor :执行器,是 MyBatis 调度的核心,负责 SQL 语句的生成以及查询缓存的维护。StatementHandler : 封装了JDBC Statement 操作,负责对 JDBC Statement 的操作,如设置参数、将Statement 结果集转换成 List 集合。ParameterHandler : 负责对用户传递的参数转换成 JDBC Statement 所需要的参数。ResultSetHandler : 负责将 JDBC 返回的 ResultSet 结果集对象转换成 List 类型的集合。TypeHandler : 用于 Java 类型和 JDBC 类型之间的转换。MappedStatement : 动态 SQL 的封装SqlSource : 表示从 XML 文件或注释读取的映射语句的内容,它创建将从用户接收的输入参数传递给数据库的 SQL。Configuration: MyBatis 所有的配置信息都维持在 Configuration 对象之中。 基础支持层 反射模块 Mybatis 中的反射模块,对 Java 反射进行了很好的封装,提供了简易的 API,方便上层调用,并且对反射操作进行了一系列的优化,比如,缓存了类的 元数据(MetaClass)和对象的元数据(MetaObject),提高了反射操作的性能。 类型转换模块 Mybatis 的别名机制,能够简化配置文件,该机制是类型转换模块的主要功能之一。类型转换模块的另一个功能是实现 JDBC 类型与 Java 类型的转换。在 SQL 语句绑定参数时,会将数据由 Java 类型转换成 JDBC 类型;在映射结果集时,会将数据由 JDBC 类型转换成 Java 类型。 日志模块 在 Java 中,有很多优秀的日志框架,如 Log4j、Log4j2、slf4j 等。Mybatis 除了提供了详细的日志输出信息,还能够集成多种日志框架,其日志模块的主要功能就是集成第三方日志框架。 资源加载模块 该模块主要封装了类加载器,确定了类加载器的使用顺序,并提供了加载类文件和其它资源文件的功能。 解析器模块 该模块有两个主要功能:一个是封装了 XPath,为 Mybatis 初始化时解析 mybatis-config.xml配置文件以及映射配置文件提供支持;另一个为处理动态 SQL 语句中的占位符提供支持。 数据源模块 Mybatis 自身提供了相应的数据源实现,也提供了与第三方数据源集成的接口。数据源是开发中的常用组件之一,很多开源的数据源都提供了丰富的功能,如连接池、检测连接状态等,选择性能优秀的数据源组件,对于提供ORM 框架以及整个应用的性能都是非常重要的。 事务管理模块 一般地,Mybatis 与 Spring 框架集成,由 Spring 框架管理事务。但 Mybatis 自身对数据库事务进行了抽象,提供了相应的事务接口和简单实现。 缓存模块 Mybatis 中有一级缓存和二级缓存,这两级缓存都依赖于缓存模块中的实现。但是需要注意,这两级缓存与Mybatis 以及整个应用是运行在同一个 JVM 中的,共享同一块内存,如果这两级缓存中的数据量较大,则可能影响系统中其它功能,所以需要缓存大量数据时,优先考虑使用 Redis、Memcache 等缓存产品。 Binding 模块 在调用 SqlSession 相应方法执行数据库操作时,需要制定映射文件中定义的 SQL 节点,如果 SQL 中出现了拼写错误,那就只能在运行时才能发现。为了能尽早发现这种错误,Mybatis 通过 Binding 模块将用户自定义的Mapper 接口与映射文件关联起来,系统可以通过调用自定义 Mapper 接口中的方法执行相应的 SQL 语句完成数据库操作,从而避免上述问题。注意,在开发中,我们只是创建了 Mapper 接口,而并没有编写实现类,这是因为 Mybatis 自动为 Mapper 接口创建了动态代理对象。 MyBatis 核心组件 在认识了 MyBatis 并了解其基础架构之后,下面我们来看一下 MyBatis 的核心组件,就是这些组件实现了从 SQL 语句到映射到 JDBC 再到数据库字段之间的转换,执行 SQL 语句并输出结果集。首先来认识 MyBatis 的第一个核心组件 SqlSessionFactory 对于任何框架而言,在使用该框架之前都要经历过一系列的初始化流程,MyBatis 也不例外。MyBatis 的初始化流程如下 String resource = "org/mybatis/example/mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); sqlSessionFactory.openSession(); 上述流程中比较重要的一个对象就是SqlSessionFactory,SqlSessionFactory 是 MyBatis 框架中的一个接口,它主要负责的是 MyBatis 框架初始化操作 为开发人员提供SqlSession 对象 SqlSessionFactory 有两个实现类,一个是 SqlSessionManager 类,一个是 DefaultSqlSessionFactory 类 DefaultSqlSessionFactory : SqlSessionFactory 的默认实现类,是真正生产会话的工厂类,这个类的实例的生命周期是全局的,它只会在首次调用时生成一个实例(单例模式),就一直存在直到服务器关闭。 SqlSessionManager : 已被废弃,原因大概是: SqlSessionManager 中需要维护一个自己的线程池,而使用MyBatis 更多的是要与 Spring 进行集成,并不会单独使用,所以维护自己的 ThreadLocal 并没有什么意义,所以 SqlSessionManager 已经不再使用。 ####SqlSessionFactory 的执行流程 下面来对 SqlSessionFactory 的执行流程来做一个分析 首先第一步是 SqlSessionFactory 的创建 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); 1 从这行代码入手,首先创建了一个 SqlSessionFactoryBuilder 工厂,这是一个建造者模式的设计思想,由 builder 建造者来创建 SqlSessionFactory 工厂 然后调用 SqlSessionFactoryBuilder 中的 build 方法传递一个InputStream 输入流,Inputstream 输入流中就是你传过来的配置文件 mybatis-config.xml,SqlSessionFactoryBuilder 根据传入的 InputStream 输入流和environment、properties属性创建一个XMLConfigBuilder对象。SqlSessionFactoryBuilder 对象调用XMLConfigBuilder 的parse()方法,流程如下。 XMLConfigBuilder 会解析/configuration标签,configuration 是 MyBatis 中最重要的一个标签,下面流程会介绍 Configuration 标签。 MyBatis 默认使用 XPath 来解析标签,关于 XPath 的使用,参见 https://www.w3school.com.cn/xpath/index.asp 在 parseConfiguration 方法中,会对各个在 /configuration 中的标签进行解析 重要配置 说一下这些标签都是什么意思吧 properties,外部属性,这些属性都是可外部配置且可动态替换的,既可以在典型的 Java 属性文件中配置,亦可通过 properties 元素的子元素来传递。 <properties> <property name="driver" value="com.mysql.jdbc.Driver" /> <property name="url" value="jdbc:mysql://localhost:3306/test" /> <property name="username" value="root" /> <property name="password" value="root" /> </properties> 一般用来给 environment 标签中的 dataSource 赋值 <environment id="development"> <transactionManager type="JDBC" /> <dataSource type="POOLED"> <property name="driver" value="${driver}" /> <property name="url" value="${url}" /> <property name="username" value="${username}" /> <property name="password" value="${password}" /> </dataSource> </environment> 还可以通过外部属性进行配置,但是我们这篇文章以原理为主,不会介绍太多应用层面的操作。 settings ,MyBatis 中极其重要的配置,它们会改变 MyBatis 的运行时行为。 settings 中配置有很多,具体可以参考 https://mybatis.org/mybatis-3/zh/configuration.html#settings 详细了解。这里介绍几个平常使用过程中比较重要的配置 一般使用如下配置 <settings> <setting name="cacheEnabled" value="true"/> <setting name="lazyLoadingEnabled" value="true"/> </settings> typeAliases,类型别名,类型别名是为 Java 类型设置的一个名字。 它只和 XML 配置有关。 <typeAliases> <typeAlias alias="Blog" type="domain.blog.Blog"/> </typeAliases> 当这样配置时,Blog 可以用在任何使用 domain.blog.Blog 的地方。 typeHandlers,类型处理器,无论是 MyBatis 在预处理语句(PreparedStatement)中设置一个参数时,还是从结果集中取出一个值时, 都会用类型处理器将获取的值以合适的方式转换成 Java 类型。 在 org.apache.ibatis.type 包下有很多已经实现好的 TypeHandler,可以参考如下 你可以重写类型处理器或创建你自己的类型处理器来处理不支持的或非标准的类型。 具体做法为:实现 org.apache.ibatis.type.TypeHandler 接口, 或继承一个很方便的类 org.apache.ibatis.type.BaseTypeHandler, 然后可以选择性地将它映射到一个 JDBC 类型。 objectFactory,对象工厂,MyBatis 每次创建结果对象的新实例时,它都会使用一个对象工厂(ObjectFactory)实例来完成。默认的对象工厂需要做的仅仅是实例化目标类,要么通过默认构造方法,要么在参数映射存在的时候通过参数构造方法来实例化。如果想覆盖对象工厂的默认行为,则可以通过创建自己的对象工厂来实现。 public class ExampleObjectFactory extends DefaultObjectFactory { public Object create(Class type) { return super.create(type); } public Object create(Class type, List constructorArgTypes, List constructorArgs) { return super.create(type, constructorArgTypes, constructorArgs); } public void setProperties(Properties properties) { super.setProperties(properties); } public boolean isCollection(Class type) { return Collection.class.isAssignableFrom(type); } } 然后需要在 XML 中配置此对象工厂 <objectFactory type="org.mybatis.example.ExampleObjectFactory"> <property name="someProperty" value="100"/> </objectFactory> plugins,插件开发,插件开发是 MyBatis 设计人员给开发人员留给自行开发的接口,MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用。MyBatis 允许使用插件来拦截的方法调用包括:Executor、ParameterHandler、ResultSetHandler、StatementHandler 接口,这几个接口也是 MyBatis 中非常重要的接口,我们下面会详细介绍这几个接口。 environments,MyBatis 环境配置,MyBatis 可以配置成适应多种环境,这种机制有助于将 SQL 映射应用于多种数据库之中。例如,开发、测试和生产环境需要有不同的配置;或者想在具有相同 Schema 的多个生产数据库中 使用相同的 SQL 映射。 这里注意一点,虽然 environments 可以指定多个环境,但是 SqlSessionFactory 只能有一个,为了指定创建哪种环境,只要将它作为可选的参数传递给 SqlSessionFactoryBuilder 即可。 SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment); SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment, properties); databaseIdProvider ,数据库厂商标示,MyBatis 可以根据不同的数据库厂商执行不同的语句,这种多厂商的支持是基于映射语句中的 databaseId 属性。 <databaseIdProvider type="DB_VENDOR"> <property name="SQL Server" value="sqlserver"/> <property name="DB2" value="db2"/> <property name="Oracle" value="oracle" /> </databaseIdProvider> mappers,映射器,这是告诉 MyBatis 去哪里找到这些 SQL 语句,mappers 映射配置有四种方式 上面的一个个属性都对应着一个解析方法,都是使用 XPath 把标签进行解析,解析完成后返回一个 DefaultSqlSessionFactory 对象,它是 SqlSessionFactory 的默认实现类。这就是 SqlSessionFactoryBuilder 的初始化流程,通过流程我们可以看到,初始化流程就是对一个个 /configuration 标签下子标签的解析过程。 SqlSession 在 MyBatis 初始化流程结束,也就是 SqlSessionFactoryBuilder -> SqlSessionFactory 的获取流程后,我们就可以通过 SqlSessionFactory 对象得到 SqlSession 然后执行 SQL 语句了。具体来看一下这个过程‘ 在 SqlSessionFactory.openSession 过程中我们可以看到,会调用到 DefaultSqlSessionFactory 中的 openSessionFromDataSource 方法,这个方法主要创建了两个与我们分析执行流程重要的对象,一个是 Executor 执行器对象,一个是 SqlSession 对象。执行器我们下面会说,现在来说一下 SqlSession 对象 SqlSession 对象是 MyBatis 中最重要的一个对象,这个接口能够让你执行命令,获取映射,管理事务。SqlSession 中定义了一系列模版方法,让你能够执行简单的 CRUD 操作,也可以通过 getMapper 获取 Mapper 层,执行自定义 SQL 语句,因为 SqlSession 在执行 SQL 语句之前是需要先开启一个会话,涉及到事务操作,所以还会有 commit、 rollback、close 等方法。这也是模版设计模式的一种应用。 MapperProxy MapperProxy 是 Mapper 映射 SQL 语句的关键对象,我们写的 Dao 层或者 Mapper 层都是通过 MapperProxy 来和对应的 SQL 语句进行绑定的。下面我们就来解释一下绑定过程 这就是 MyBatis 的核心绑定流程,我们可以看到 SqlSession 首先调用 getMapper 方法,我们刚才说到 SqlSession 是大哥级别的人物,只定义标准(有一句话是怎么说的来着,一流的企业做标准,二流的企业做品牌,三流的企业做产品)。 SqlSession 不愿意做的事情交给 Configuration 这个手下去做,但是 Configuration 也是有小弟的,它不愿意做的事情直接甩给小弟去做,这个小弟是谁呢?它就是 MapperRegistry,马上就到核心部分了。MapperRegistry 相当于项目经理,项目经理只从大面上把握项目进度,不需要知道手下的小弟是如何工作的,把任务完成了就好。最终真正干活的还是 MapperProxyFactory。看到这段代码 Proxy.newProxyInstance ,你是不是有一种恍然大悟的感觉,如果你没有的话,建议查阅一下动态代理的文章,这里推荐一篇 (https://www.jianshu.com/p/95970b089360) 也就是说,MyBatis 中 Mapper 和 SQL 语句的绑定正是通过动态代理来完成的。 通过动态代理,我们就可以方便的在 Dao 层或者 Mapper 层定义接口,实现自定义的增删改查操作了。那么具体的执行过程是怎么样呢?上面只是绑定过程,别着急,下面就来探讨一下 SQL 语句的执行过程。 MapperProxyFactory 会生成代理对象,这个对象就是 MapperProxy,最终会调用到 mapperMethod.execute 方法,execute 方法比较长,其实逻辑比较简单,就是判断是 插入、更新、删除 还是 查询 语句,其中如果是查询的话,还会判断返回值的类型,我们可以点进去看一下都是怎么设计的。 很多代码其实可以忽略,只看我标出来的重点就好了,我们可以看到,不管你前面经过多少道关卡处理,最终都逃不过 SqlSession 这个老大制定的标准。 我们以 selectList 为例,来看一下下面的执行过程。 这是 DefaultSqlSession 中 selectList 的代码,我们可以看到出现了 executor,这是什么呢?我们下面来解释。 Executor 还记得我们之前的流程中提到了 Executor(执行器) 这个概念吗?我们来回顾一下它第一次出现的位置。 由 Configuration 对象创建了一个 Executor 对象,这个 Executor 是干嘛的呢?下面我们就来认识一下 Executor 的继承结构 每一个 SqlSession 都会拥有一个 Executor 对象,这个对象负责增删改查的具体操作,我们可以简单的将它理解为 JDBC 中 Statement 的封装版。 也可以理解为 SQL 的执行引擎,要干活总得有一个发起人吧,可以把 Executor 理解为发起人的角色。 首先先从 Executor 的继承体系来认识一下 如上图所示,位于继承体系最顶层的是 Executor 执行器,它有两个实现类,分别是BaseExecutor和 CachingExecutor。 BaseExecutor 是一个抽象类,这种通过抽象的实现接口的方式是适配器设计模式之接口适配 的体现,是Executor 的默认实现,实现了大部分 Executor 接口定义的功能,降低了接口实现的难度。BaseExecutor 的子类有三个,分别是 SimpleExecutor、ReuseExecutor 和 BatchExecutor。 SimpleExecutor : 简单执行器,是 MyBatis 中默认使用的执行器,每执行一次 update 或 select,就开启一个Statement 对象,用完就直接关闭 Statement 对象(可以是 Statement 或者是 PreparedStatment 对象) ReuseExecutor : 可重用执行器,这里的重用指的是重复使用 Statement,它会在内部使用一个 Map 把创建的Statement 都缓存起来,每次执行 SQL 命令的时候,都会去判断是否存在基于该 SQL 的 Statement 对象,如果存在 Statement 对象并且对应的 connection 还没有关闭的情况下就继续使用之前的 Statement 对象,并将其缓存起来。因为每一个 SqlSession 都有一个新的 Executor 对象,所以我们缓存在 ReuseExecutor 上的 Statement作用域是同一个 SqlSession。 BatchExecutor : 批处理执行器,用于将多个 SQL 一次性输出到数据库 CachingExecutor: 缓存执行器,先从缓存中查询结果,如果存在就返回之前的结果;如果不存在,再委托给Executor delegate 去数据库中取,delegate 可以是上面任何一个执行器。 Executor 的创建和选择 我们上面提到 Executor 是由 Configuration 创建的,Configuration 会根据执行器的类型创建,如下 这一步就是执行器的创建过程,根据传入的 ExecutorType 类型来判断是哪种执行器,如果不指定 ExecutorType ,默认创建的是简单执行器。它的赋值可以通过两个地方进行赋值: 可以通过 标签来设置当前工程中所有的 SqlSession 对象使用默认的 Executor <settings> <!--取值范围 SIMPLE, REUSE, BATCH --> <setting name="defaultExecutorType" value="SIMPLE"/> </settings> 另外一种直接通过Java对方法赋值的方式 session = factory.openSession(ExecutorType.BATCH); Executor 的具体执行过程 Executor 中的大部分方法的调用链其实是差不多的,下面是深入源码分析执行过程,如果你没有时间或者暂时不想深入研究的话,给你下面的执行流程图作为参考。 我们紧跟着上面的 selectList 继续分析,它会调用到 executor.query 方法。 当有一个查询请求访问的时候,首先会经过 Executor 的实现类 CachingExecutor ,先从缓存中查询 SQL 是否是第一次执行,如果是第一次执行的话,那么就直接执行 SQL 语句,并创建缓存,如果第二次访问相同的 SQL 语句的话,那么就会直接从缓存中提取。 上面这段代码是从 selectList -> 从缓存中 query 的具体过程。可能你看到这里有些觉得类都是什么东西,我想鼓励你一下,把握重点,不用每段代码都看,从找到 SQL 的调用链路,其他代码想看的时候在看,看源码就是很容易发蒙,容易烦躁,但是切记一点,把握重点。 上面代码会判断缓存中是否有这条 SQL 语句的执行结果,如果没有的话,就再重新创建 Executor 执行器执行 SQL 语句,注意, list = doQuery 是真正执行 SQL 语句的过程,这个过程中会创建我们上面提到的三种执行器,这里我们使用的是简单执行器。 到这里,执行器所做的工作就完事了,Executor 会把后续的工作交给 StatementHandler 继续执行。下面我们来认识一下 StatementHandler 上面代码会判断缓存中是否有这条 SQL 语句的执行结果,如果没有的话,就再重新创建 Executor 执行器执行 SQL 语句,注意, list = doQuery 是真正执行 SQL 语句的过程,这个过程中会创建我们上面提到的三种执行器,这里我们使用的是简单执行器。 到这里,执行器所做的工作就完事了,Executor 会把后续的工作交给 StatementHandler 继续执行。下面我们来认识一下 StatementHandler StatementHandler 的继承结构 有没有感觉和 Executor 的继承体系很相似呢?最顶级接口是四大组件对象,分别有两个实现类 BaseStatementHandler 和 RoutingStatementHandler,BaseStatementHandler 有三个实现类, 他们分别是 SimpleStatementHandler、PreparedStatementHandler 和 CallableStatementHandler。 RoutingStatementHandler : RoutingStatementHandler 并没有对 Statement 对象进行使用,只是根据StatementType 来创建一个代理,代理的就是对应Handler的三种实现类。在MyBatis工作时,使用的StatementHandler 接口对象实际上就是 RoutingStatementHandler 对象。 BaseStatementHandler : 是 StatementHandler 接口的另一个实现类,它本身是一个抽象类,用于简化StatementHandler 接口实现的难度,属于适配器设计模式体现,它主要有三个实现类 SimpleStatementHandler: 管理 Statement 对象并向数据库中推送不需要预编译的SQL语句。PreparedStatementHandler: 管理 Statement 对象并向数据中推送需要预编译的SQL语句。CallableStatementHandler:管理 Statement 对象并调用数据库中的存储过程。 StatementHandler 的创建和源码分析 我们继续来分析上面 query 的调用链路,StatementHandler 的创建过程如下 MyBatis 会根据 SQL 语句的类型进行对应 StatementHandler 的创建。我们以预处理 StatementHandler 为例来讲解一下 执行器不仅掌管着 StatementHandler 的创建,还掌管着创建 Statement 对象,设置参数等,在创建完 PreparedStatement 之后,我们需要对参数进行处理了。 如 如果用一副图来表示一下这个执行流程的话我想是这样 这里我们先暂停一下,来认识一下第三个核心组件 ParameterHandler ParameterHandler - ParameterHandler 介绍 ParameterHandler 相比于其他的组件就简单很多了,ParameterHandler 译为参数处理器,负责为 PreparedStatement 的 sql 语句参数动态赋值,这个接口很简单只有两个方法 ParameterHandler 只有一个实现类 DefaultParameterHandler , 它实现了这两个方法。 getParameterObject: 用于读取参数setParameters: 用于对 PreparedStatement 的参数赋值ParameterHandler 的解析过程 上面我们讨论过了 ParameterHandler 的创建过程,下面我们继续上面 parameterSize 流程 这就是具体参数的解析过程了,下面我们来描述一下 下面用一个流程图表示一下 ParameterHandler 的解析过程,以简单执行器为例 我们在完成 ParameterHandler 对 SQL 参数的预处理后,回到 SimpleExecutor 中的 doQuery 方法 上面又引出来了一个重要的组件那就是 ResultSetHandler,下面我们来认识一下这个组件 ResultSetHandler - ResultSetHandler 简介 ResultSetHandler 也是一个非常简单的接口 ResultSetHandler 是一个接口,它只有一个默认的实现类,像是 ParameterHandler 一样,它的默认实现类是DefaultResultSetHandler ResultSetHandler 解析过程 MyBatis 只有一个默认的实现类就是 DefaultResultSetHandler,DefaultResultSetHandler 主要负责处理两件事 处理 Statement 执行后产生的结果集,生成结果列表 处理存储过程执行后的输出参数 按照 Mapper 文件中配置的 ResultType 或 ResultMap 来封装成对应的对象,最后将封装的对象返回即可。 其中涉及的主要对象有: ResultSetWrapper : 结果集的包装器,主要针对结果集进行的一层包装,它的主要属性有 ResultSet : Java JDBC ResultSet 接口表示数据库查询的结果。 有关查询的文本显示了如何将查询结果作为java.sql.ResultSet 返回。 然后迭代此ResultSet以检查结果。 TypeHandlerRegistry: 类型注册器,TypeHandlerRegistry 在初始化的时候会把所有的 Java类型和类型转换器进行注册。 ColumnNames: 字段的名称,也就是查询操作需要返回的字段名称 ClassNames: 字段的类型名称,也就是 ColumnNames 每个字段名称的类型 JdbcTypes: JDBC 的类型,也就是 java.sql.Types 类型 ResultMap: 负责处理更复杂的映射关系 在 DefaultResultSetHandler 中处理完结果映射,并把上述结构返回给调用的客户端,从而执行完成一条完整的SQL语句。 内容转载自:CSDN博主:cxuann 原文链接:https://blog.csdn.net/qq_36894974/article/details/104132876?depth_1-utm_source=distribute.pc_feed.none-task&request_id=&utm_source=distribute.pc_feed.none-task

问问小秘 2020-03-05 15:44:27 0 浏览量 回答数 0

回答

说实话这乱七八糟一堆文字我看了两边,然后发现真救不了你. ######回复 @刘子玄:操作系统不管理寄存器,现在都是抢占式多线程操作系统,都是在线程释放资源的时候切换到其他进程的(你调用某些api的时候会发生等待和切换操作,然后保存线程执行环境数据)看一下操作系统原理的书籍就知道了.直接切换那个只有纯正的分时操作系统才会去做.现在估计只剩下大型UNIX了######==######靠时钟中断,硬件一定会定时发起时钟中断,中断服务一定会执行,这样就可以进行调度或做其他事了,中断机制由硬件保证。找书看吧,这些问题不是几句说得清。######谢了######在中断产生时,寄存器压栈,在中断结束后,堆栈的数据弹回到寄存器。###### 寄存器操作是汇编级别的最小操作单元,即使是操作系统也不能够管理寄存器. 是计算机有一些指令,能够自己把所有寄存器保存到一个地方.######计算机基础如此博大精深,几十年高科技结晶,不是三天三夜就能说清的,更何况几句话###### 简单2个字压栈.OS的原理很简单,你可以找一些嵌入式的OS开源代码进行阅读,相信读完2个系统的代码后,就对OS核心部分很清楚了. 挑你的一个问题进行回答:" 操作系统是如何让一个程序在规定时间内执行再准确的暂停了?这是如何控制的?"      感觉你还不清楚调度算法的实现.简单的说:硬件中断将其打断,如果需要1ms的进程调度精度,那么就设置时钟中断为1ms. 你可以看下中断部分的代码.       CPU的PC指针即使软件不去设置它也不是固定不变只能向下跑的.当中断发生的时候,PC指针会自动修改到相应中断向量的物理地址上,并且中断时的重要寄存器的值被硬件自动保存. 于是我们就设置一个时钟中断向量(将这个地址上写入我们的代码函数的地址),每18msPC指针会被自动改到这个地方,在这个地方我们根据调度算法,看是继续执行被打断的线程还是切换到更合适的线程上.  感性上,线程/cpu的运行实际上是非常的不连贯, 中途不断的被各种中断疯狂的打断.尤其高响应的硬实时OS,打断应该更加频繁. 我们想干任何事情都可以在中断处理中去做.        此外除了硬件中断,因为硬件功能都是api提供,so程序代码实际上经常会很频繁调用一些系统API,既然调用了系统api,os也完全可以在系统api执行软中断,执行调度算法,把pc指针移到别处去,不再正常的函数返回了(保存好数据,下次调度它时,模拟这个函数返回,应用程序完全不知道发生了什么). ######一个嵌入式OS的代码不过几千行而已. 看完几个 你就精通OS的实现了.不过"知识改变命运", 懂得越多混得越惨, 个人建议你干点其他能赚钱的事情.底层实现的东西,除了吹牛,提升点技术素质,对赚钱来说毫无用处,面试时都没用!!(实际上现在面试都是看算法)  小正太, 根据赚钱来指导自己学习/背诵什么东西.(很心痛的经验)######回复 @MinGKai:haha.反正比赚1个亿简单多了.######“精通”OS有那么简单么…………######这个你放心,我只会把编程当成毕生的爱好,而不会用作工作。

优选2 2020-06-09 16:14:52 0 浏览量 回答数 0

回答

说实话这乱七八糟一堆文字我看了两边,然后发现真救不了你. ######回复 @刘子玄 : 操作系统不管理寄存器,现在都是抢占式多线程操作系统,都是在线程释放资源的时候切换到其他进程的(你调用某些api的时候会发生等待和切换操作,然后保存线程执行环境数据)看一下操作系统原理的书籍就知道了.直接切换那个只有纯正的分时操作系统才会去做.现在估计只剩下大型UNIX了######= =######靠时钟中断,硬件一定会定时发起时钟中断,中断服务一定会执行,这样就可以进行调度或做其他事了,中断机制由硬件保证。找书看吧,这些问题不是几句说得清。######谢了######在中断产生时,寄存器压栈,在中断结束后,堆栈的数据弹回到寄存器。###### 寄存器操作是汇编级别的最小操作单元,即使是操作系统也不能够管理寄存器. 是计算机有一些指令,能够自己把所有寄存器保存到一个地方. ######计算机基础如此博大精深,几十年高科技结晶,不是三天三夜就能说清的,更何况几句话###### 简单2个字 压栈. OS的原理很简单, 你可以找一些嵌入式的OS开源代码进行阅读, 相信读完2个系统的代码后, 就对OS核心部分很清楚了. 挑你的一个问题进行回答: "操作系统是如何让一个程序在规定时间内执行再准确的暂停了?这是如何控制的?"       感觉你还不清楚调度算法的实现.简单的说: 硬件中断将其打断,如果需要1ms的进程调度精度,那么就设置时钟中断为1ms.  你可以看下中断部分的代码.        CPU的PC指针即使软件不去设置它也不是固定不变只能向下跑的. 当中断发生的时候,PC指针会自动修改到相应中断向量的物理地址上,并且中断时的重要寄存器的值被硬件自动保存.  于是我们就设置一个时钟中断向量(将这个地址上写入我们的代码函数的地址), 每18ms PC指针会被自动改到这个地方,在这个地方 我们根据调度算法, 看是继续执行被打断的线程 还是切换到更合适的线程上.   感性上, 线程/cpu 的运行 实际上是非常的不连贯,  中途不断的被各种中断疯狂的打断. 尤其高响应的硬实时OS,打断应该更加频繁.  我们想干任何事情都可以在中断处理中去做.         此外除了硬件中断, 因为硬件功能都是api提供,so程序代码实际上经常会很频繁调用一些系统API, 既然调用了系统api, os也完全可以在系统api执行软中断, 执行调度算法, 把pc指针移到别处去, 不再正常的函数返回了(保存好数据, 下次调度它时,模拟这个函数返回, 应用程序完全不知道发生了什么). ######一个嵌入式OS的代码不过几千行而已.  看完几个  你就精通OS的实现了. 不过"知识改变命运",  懂得越多混得越惨,  个人建议你干点其他能赚钱的事情. 底层实现的东西, 除了吹牛, 提升点技术素质, 对赚钱来说毫无用处, 面试时都没用!! (实际上现在面试都是看算法)   小正太,  根据赚钱来指导自己学习/背诵 什么东西. (很心痛的经验)######回复 @MinGKai : haha. 反正比赚1个亿简单多了.######“精通”OS有那么简单么…………######这个你放心,我只会把编程当成毕生的爱好,而不会用作工作。

爱吃鱼的程序员 2020-05-30 22:45:50 0 浏览量 回答数 0

回答

先补充一下概念:Java 内存模型中的可见性、原子性和有序性。可见性:  可见性是一种复杂的属性,因为可见性中的错误总是会违背我们的直觉。通常,我们无法确保执行读操作的线程能适时地看到其他线程写入的值,有时甚至是根本不可能的事情。为了确保多个线程之间对内存写入操作的可见性,必须使用同步机制。  可见性,是指线程之间的可见性,一个线程修改的状态对另一个线程是可见的。也就是一个线程修改的结果。另一个线程马上就能看到。比如:用volatile修饰的变量,就会具有可见性。volatile修饰的变量不允许线程内部缓存和重排序,即直接修改内存。所以对其他线程是可见的。但是这里需要注意一个问题,volatile只能让被他修饰内容具有可见性,但不能保证它具有原子性。比如 volatile int a = 0;之后有一个操作 a++;这个变量a具有可见性,但是a++ 依然是一个非原子操作,也就是这个操作同样存在线程安全问题。  在 Java 中 volatile、synchronized 和 final 实现可见性。原子性:  原子是世界上的最小单位,具有不可分割性。比如 a=0;(a非long和double类型) 这个操作是不可分割的,那么我们说这个操作时原子操作。再比如:a++; 这个操作实际是a = a + 1;是可分割的,所以他不是一个原子操作。非原子操作都会存在线程安全问题,需要我们使用同步技术(sychronized)来让它变成一个原子操作。一个操作是原子操作,那么我们称它具有原子性。java的concurrent包下提供了一些原子类,我们可以通过阅读API来了解这些原子类的用法。比如:AtomicInteger、AtomicLong、AtomicReference等。  在 Java 中 synchronized 和在 lock、unlock 中操作保证原子性。有序性:  Java 语言提供了 volatile 和 synchronized 两个关键字来保证线程之间操作的有序性,volatile 是因为其本身包含“禁止指令重排序”的语义,synchronized 是由“一个变量在同一个时刻只允许一条线程对其进行 lock 操作”这条规则获得的,此规则决定了持有同一个对象锁的两个同步块只能串行执行。下面内容摘录自《Java Concurrency in Practice》:  下面一段代码在多线程环境下,将存在问题。复制代码+ View code1 /** 2 * @author zhengbinMac 3 */ 4 public class NoVisibility { 5 private static boolean ready; 6 private static int number; 7 private static class ReaderThread extends Thread { 8 @Override 9 public void run() {10 while(!ready) {11 Thread.yield();12 }13 System.out.println(number);14 }15 }16 public static void main(String[] args) {17 new ReaderThread().start();18 number = 42;19 ready = true;20 }21 }复制代码  NoVisibility可能会持续循环下去,因为读线程可能永远都看不到ready的值。甚至NoVisibility可能会输出0,因为读线程可能看到了写入ready的值,但却没有看到之后写入number的值,这种现象被称为“重排序”。只要在某个线程中无法检测到重排序情况(即使在其他线程中可以明显地看到该线程中的重排序),那么就无法确保线程中的操作将按照程序中指定的顺序来执行。当主线程首先写入number,然后在没有同步的情况下写入ready,那么读线程看到的顺序可能与写入的顺序完全相反。  在没有同步的情况下,编译器、处理器以及运行时等都可能对操作的执行顺序进行一些意想不到的调整。在缺乏足够同步的多线程程序中,要想对内存操作的执行春旭进行判断,无法得到正确的结论。  这个看上去像是一个失败的设计,但却能使JVM充分地利用现代多核处理器的强大性能。例如,在缺少同步的情况下,Java内存模型允许编译器对操作顺序进行重排序,并将数值缓存在寄存器中。此外,它还允许CPU对操作顺序进行重排序,并将数值缓存在处理器特定的缓存中。二、Volatile原理  Java语言提供了一种稍弱的同步机制,即volatile变量,用来确保将变量的更新操作通知到其他线程。当把变量声明为volatile类型后,编译器与运行时都会注意到这个变量是共享的,因此不会将该变量上的操作与其他内存操作一起重排序。volatile变量不会被缓存在寄存器或者对其他处理器不可见的地方,因此在读取volatile类型的变量时总会返回最新写入的值。  在访问volatile变量时不会执行加锁操作,因此也就不会使执行线程阻塞,因此volatile变量是一种比sychronized关键字更轻量级的同步机制。  当对非 volatile 变量进行读写的时候,每个线程先从内存拷贝变量到CPU缓存中。如果计算机有多个CPU,每个线程可能在不同的CPU上被处理,这意味着每个线程可以拷贝到不同的 CPU cache 中。  而声明变量是 volatile 的,JVM 保证了每次读变量都从内存中读,跳过 CPU cache 这一步。当一个变量定义为 volatile 之后,将具备两种特性:  1.保证此变量对所有的线程的可见性,这里的“可见性”,如本文开头所述,当一个线程修改了这个变量的值,volatile 保证了新值能立即同步到主内存,以及每次使用前立即从主内存刷新。但普通变量做不到这点,普通变量的值在线程间传递均需要通过主内存(详见:Java内存模型)来完成。  2.禁止指令重排序优化。有volatile修饰的变量,赋值后多执行了一个“load addl $0x0, (%esp)”操作,这个操作相当于一个内存屏障(指令重排序时不能把后面的指令重排序到内存屏障之前的位置),只有一个CPU访问内存时,并不需要内存屏障;(什么是指令重排序:是指CPU采用了允许将多条指令不按程序规定的顺序分开发送给各相应电路单元处理)。volatile 性能:  volatile 的读性能消耗与普通变量几乎相同,但是写操作稍慢,因为它需要在本地代码中插入许多内存屏障指令来保证处理器不发生乱序执行。

wangccsy 2019-12-02 01:48:10 0 浏览量 回答数 0

回答

楼主这是节点遍历时,通过函数指针动态加载节点处理函数的设计方法。这个几年前写过,后来不这么写了。主要有以下几个问题。 1、每个节点被访问时,操作可能不一样,通用的函数指针的入口参数,要么可变参,要么多套,入口指针,都是很繁琐的事情,把代码逻辑结构搞的会更复杂。 2、操作函数和操作对象没有绑定,这个在规模开发时,很容易引起混乱。这样设计的代码,我自己到后面都觉得混乱,更别说基于我的架子让别人开发,楼主你的例子不够复杂可能感觉不到。 3、上面两个问题,也导致,代码复用率不高。 现在我的设计思想,如果是基础的数据结构,如同你这个例子中就是个线形表,我都全部独立成模版,在头文件中。 特定数据的处理不会和处理方法绑定,而是调用不同通用模块来处理,这样是尽可能的让数据和处理松耦合。而关联数据再怎么关联,处理时,也是一类整体处理的,同时一批数据再怎么复合,总可以拆成不同大部分串联处理(例如,读取、处理、写出,通过增加cache的方式可以分批分步骤完成,而不是读、处理、写 、一个完整操作周期,仅针对一个单元)。所以这类数据的整体处理落在通用模块里,通过数据和处理的紧耦合的提升效率。 ###### 另外,补充说一下,楼主的函数式风格,和我的函数式风格理解相差颇大。我的理解如下,所谓函数式风格,是将一批数据的若干处理,分解为正交串接的多个子步骤,每个步骤都是对整体数据的某个操作的实现。楼主的方案实质是对一个处理,可以挂接不同的操作方法。 我的理解函数式的风格在于每个独立模块处理极少的有逻辑关联的操作,可以看作针对一个数据池的原子操作。依次将数据池的数据灌入不同的独立模块,实现数据处理。当然差异的模块调用顺序和不同处理模块的组合,可以有不同的效果。 但无论如何,都是函数与数据松耦合的设计。这个和面向对象是反过来的。 ######相互嵌套耦合,牵一发动全身######楼主的代码有很浓重的其他语言的味道######楼主文章不错,我看现在的C模块基本就是你所说的面向对象风格,其实就是用数据结构组织起来。###### 引用来自“中山野鬼”的答案 楼主这是节点遍历时,通过函数指针动态加载节点处理函数的设计方法。这个几年前写过,后来不这么写了。主要有以下几个问题。 1、每个节点被访问时,操作可能不一样,通用的函数指针的入口参数,要么可变参,要么多套,入口指针,都是很繁琐的事情,把代码逻辑结构搞的会更复杂。 2、操作函数和操作对象没有绑定,这个在规模开发时,很容易引起混乱。这样设计的代码,我自己到后面都觉得混乱,更别说基于我的架子让别人开发,楼主你的例子不够复杂可能感觉不到。 3、上面两个问题,也导致,代码复用率不高。 现在我的设计思想,如果是基础的数据结构,如同你这个例子中就是个线形表,我都全部独立成模版,在头文件中。 特定数据的处理不会和处理方法绑定,而是调用不同通用模块来处理,这样是尽可能的让数据和处理松耦合。而关联数据再怎么关联,处理时,也是一类整体处理的,同时一批数据再怎么复合,总可以拆成不同大部分串联处理(例如,读取、处理、写出,通过增加cache的方式可以分批分步骤完成,而不是读、处理、写 、一个完整操作周期,仅针对一个单元)。所以这类数据的整体处理落在通用模块里,通过数据和处理的紧耦合的提升效率。 你说的问题#1和文章中函数式风格一节抱怨employee_read无法和Callback兼容的问题是类似的,说到底就是因为C语言静态类型等语法特性导致了对函数式风格支持不好;同时也反向说明了为什么大多数支持函数式风格的语言会选择“动态类型”,并且支持灵活的可变个数参数等特性,都是为了辅助函数式风格的编码。 #2这一点我不太同意。C语言里虽然没有类的概念把数据和函数在语法层次上绑定在一起,但通过规范地命令提供隐喻,比如代码中,所有操作Employee对象的函数都以employee_前缀开头。而且,这些接口之间也有层级关系,符合下表描述的抽象屏障。如果你把Employee相关的声明、操作独立出来放在一个文件里,然后头文件里只放置公开的接口信息,这样就变得简洁多了。 最高层:使用API的程序 main 基于Employee的接口实现的高级操作 employee_print, employee_adjust_salary 基于最底层的C,对象Employee的最基础的操作,包括读入、释放、遍历等 employee_read, employee_free, foreach, with_open_file C语言本身提供的最底层的工具 struct Empoloyee, for, free, calloc... 例如C语言自带的操作文件的接口同样符合这样的抽象屏障:我们只需要使用fopen、fclose、fread、fwrite等一系列操作FILE对象的接口,无需关心FILE结构体里有些什么内容,表示什么意思,以及各个接口是怎么实现的。 #3的确是一个问题,而且我在文章里也可以没有提及,因为这不是这篇文章要表达的重点。它最本质的问题在于将集合的数据结构和单个对象的信息保存在同一个地方。其他语言,例如Java的java.util.*容器、C++的STL容器,都符合你的设计,将容器这个单一职责抽象出来。当然,我自己实际的工作也是这样做的。 ###### 引用来自“中山野鬼”的答案 另外,补充说一下,楼主的函数式风格,和我的函数式风格理解相差颇大。我的理解如下,所谓函数式风格,是将一批数据的若干处理,分解为正交串接的多个子步骤,每个步骤都是对整体数据的某个操作的实现。楼主的方案实质是对一个处理,可以挂接不同的操作方法。 我的理解函数式的风格在于每个独立模块处理极少的有逻辑关联的操作,可以看作针对一个数据池的原子操作。依次将数据池的数据灌入不同的独立模块,实现数据处理。当然差异的模块调用顺序和不同处理模块的组合,可以有不同的效果。 但无论如何,都是函数与数据松耦合的设计。这个和面向对象是反过来的。 我认为你说的是“责任单一原则”,让每个函数、每个模块责任都尽可能地单一,然后通过类似搭积木一样的灵活组合,完成不同的任务。就像UNIX下的命令,每个单独命令都只完成一件事情,通过管道等把这些功能单一的命令组织在一起,协作完成一个复杂的任务! 我个人认为这是一种设计思想,和源自Lambda演算的函数式风格并没有太大关系。 ###### 引用来自“杨同学”的答案 楼主的代码有很浓重的其他语言的味道 因为其他语言也能写“面向对象风格”和“函数式风格”的代码,并且看起来比C更“专业”。 ###### 引用来自“优游幻世”的答案 楼主文章不错,我看现在的C模块基本就是你所说的面向对象风格,其实就是用数据结构组织起来。 嗯,将数据和操作数据的方法集中在一起会让代码更容易维护。 就像我在六楼回复里提到的,很多C模块往往还会更进一步,把容器和对象也分离开来。这样容器能容纳各种不同的对象,对象则只保留数据本身,不关心和其他对象是以什么形式组织在一起的。 ###### 引用来自“redraiment”的答案 引用来自“中山野鬼”的答案 楼主这是节点遍历时,通过函数指针动态加载节点处理函数的设计方法。这个几年前写过,后来不这么写了。主要有以下几个问题。 1、每个节点被访问时,操作可能不一样,通用的函数指针的入口参数,要么可变参,要么多套,入口指针,都是很繁琐的事情,把代码逻辑结构搞的会更复杂。 2、操作函数和操作对象没有绑定,这个在规模开发时,很容易引起混乱。这样设计的代码,我自己到后面都觉得混乱,更别说基于我的架子让别人开发,楼主你的例子不够复杂可能感觉不到。 3、上面两个问题,也导致,代码复用率不高。 现在我的设计思想,如果是基础的数据结构,如同你这个例子中就是个线形表,我都全部独立成模版,在头文件中。 特定数据的处理不会和处理方法绑定,而是调用不同通用模块来处理,这样是尽可能的让数据和处理松耦合。而关联数据再怎么关联,处理时,也是一类整体处理的,同时一批数据再怎么复合,总可以拆成不同大部分串联处理(例如,读取、处理、写出,通过增加cache的方式可以分批分步骤完成,而不是读、处理、写 、一个完整操作周期,仅针对一个单元)。所以这类数据的整体处理落在通用模块里,通过数据和处理的紧耦合的提升效率。 你说的问题#1和文章中函数式风格一节抱怨employee_read无法和Callback兼容的问题是类似的,说到底就是因为C语言静态类型等语法特性导致了对函数式风格支持不好;同时也反向说明了为什么大多数支持函数式风格的语言会选择“动态类型”,并且支持灵活的可变个数参数等特性,都是为了辅助函数式风格的编码。 #2这一点我不太同意。C语言里虽然没有类的概念把数据和函数在语法层次上绑定在一起,但通过规范地命令提供隐喻,比如代码中,所有操作Employee对象的函数都以employee_前缀开头。而且,这些接口之间也有层级关系,符合下表描述的抽象屏障。如果你把Employee相关的声明、操作独立出来放在一个文件里,然后头文件里只放置公开的接口信息,这样就变得简洁多了。 最高层:使用API的程序 main 基于Employee的接口实现的高级操作 employee_print, employee_adjust_salary 基于最底层的C,对象Employee的最基础的操作,包括读入、释放、遍历等 employee_read, employee_free, foreach, with_open_file C语言本身提供的最底层的工具 struct Empoloyee, for, free, calloc... 例如C语言自带的操作文件的接口同样符合这样的抽象屏障:我们只需要使用fopen、fclose、fread、fwrite等一系列操作FILE对象的接口,无需关心FILE结构体里有些什么内容,表示什么意思,以及各个接口是怎么实现的。 #3的确是一个问题,而且我在文章里也可以没有提及,因为这不是这篇文章要表达的重点。它最本质的问题在于将集合的数据结构和单个对象的信息保存在同一个地方。其他语言,例如Java的java.util.*容器、C++的STL容器,都符合你的设计,将容器这个单一职责抽象出来。当然,我自己实际的工作也是这样做的。 第二个问题其实是不同设计思想的核心问题。你举的例子只能说是些简单的系统中的模块。如果是个大系统中的底层模块特别是引擎方面(会产生数据加工的),这种方法最终组合出来的系统,会比面向对象出来的类套类更复杂。说实话,还不如用面相对象实现。 面向对象,是将数据和操作,进行耦合,并且封装在类里面。这种做法是有它的好处的。这样不会导致数据和操作之间出现问题。而c如果这么写,说实话还不如用c++的类进行实现,因为类描述这些逻辑更为清晰,而且语法和编译器可以帮你做大量的事情。 而相反面向数据,是一批数据(不是一个具体数据单元),存在一批不同操作。如何分析数据之间的无关性和前后操作的无关性是重点,这两个分析清楚,那么并发计算,和分步骤计算就得以实现。并发计算不谈,分步骤计算的思想就是原子操作,或者微指令集管道设计思想。这样设计,可以令复杂的数据处理,根据流程细分到步骤,每个步骤细分到子步骤单元,而每个子步骤单元只负责处理,不负责数据的格式问题。 上面这段的设计思想和面向对象是反过来的,数据和操作松耦合。数据的特殊性导致的操作,是通过各种操作模块组合调用实现(这些操作模块可以看作上面独立的子步骤单元和外部特定数据结构无关的)。 这样做的好处是,模块的设计,可以独立进行,让外部数据格式依赖自身,而不是操作对应数据格式(面向对象是后者,成员变量类型决定了成员函数的实际操作),模块复用率高,同时是整批数据处理,只要数据流程(调用不同模块的系统设计良好),运行效率会很高。而且便于并发操作。 并发操作并不单单是一批数据,分层几组让同一个操作的多个进程处理。流水线技术的使用,一样可以实现。 这里顺带喷下hadoop。貌似hadoop的map reduce并没有在流水线方面有什么突破的思路,这块需要考虑到不同计算单元之间数据流动的费用, hadoop整天扯分布计算,根本不考虑数据整体计算周期内的相关性的问题,基本上都是推给用户自己处理,而用户应该无法控制具体计算硬件设备,最后能有好效果就扯淡了。

kun坤 2020-06-10 09:29:21 0 浏览量 回答数 0
阿里云大学 云服务器ECS com域名 网站域名whois查询 开发者平台 小程序定制 小程序开发 国内短信套餐包 开发者技术与产品 云数据库 图像识别 开发者问答 阿里云建站 阿里云备案 云市场 万网 阿里云帮助文档 免费套餐 开发者工具 企业信息查询 小程序开发制作 视频内容分析 企业网站制作 视频集锦 代理记账服务 企业建站模板