HTTP学习笔记6 首部字段类型 通用首部字段

HTTP学习笔记6 首部字段类型 通用首部字段

HTTP首部字段:给浏览器和服务器提供报文主体大小、语言、认证内容等诸多额外的信息

首部字段格式:字段名:字段值1 [,字段值2] [,字段值3] [….]

首部字段类型:

按实际用途划分:通用首部字段、请求首部字段、响应首部字段、实体首部字段
按是否缓存代理的行为划分:端到端首部(End to end Header)、逐跳首部(Hop by hop Header)
端到端首部:此类别的首部将转发给请求或响应的*终目标,经过代理时,也必须保存在由缓存生成的响应中,必须被转发。
逐跳首部:此类别的首部只对单次转发有效,经过缓存或代理时,将会失效。
在HTTP/1.1中的逐跳首部有8个,其余都为端到端首部,这8个为:Connection、Keep-Alive、Proxy-Authorization、Trailer、TE、Transfer-Encoding、Upgrade
(如果要使用逐跳首部,必须提供Connection字段,例如Connection:Keep-Alive)

通用首部字段:
① Cahe-Control:操作缓存

缓存请求指令:

no-cache:强制向服务器验证缓存有效期,无论缓存是否过期
no-store:不使用缓存
max-age=[秒]:接受缓存已保存的*大时间值,若缓存保存已超过该时间,则不会接受缓存(在Http/1.1中,同时存在max-age和Expires首部字段时,会忽略Expires,而在Http/1.0时,会忽略max-age)
max-stale(=[秒]):可接受缓存的*大保存时间,不管是否过期,时间可省略
min-fresh=[秒]:缓存的已保存时间加上该数值若未超过缓存的有效期,则可接受该缓存
no-transform:代理不可更改媒体类型
only-if-cached:只接受缓存资源,不会验证有效期,若无响应则返回504 网关超时状态码
缓存响应请求:

public:表明响应可以被任何对象(包括:发送请求的客户端,代理服务器,等等)缓存,即使是通常不可缓存的内容。
private:表明响应只能被单个用户缓存,不能作为共享缓存(即代理服务器不能缓存它)。私有缓存可以缓存响应内容,比如:对应用户的本地浏览器
no-cache:缓存前需确定有效性
no-store:不使用缓存
no-transform:代理不可更改媒体类型
must-revalidate:若缓存过期后,必须再向源服务器验证有效期,会忽略max-stale指令
proxy-revalidate:与must-revalidate类似,但只限定于代理服务器
max-age=[秒]:将资源作为缓存保存的*长时间,在此期间不再确认有效期
s-maxage=[秒]:与max-age类似,但只适用于公共的缓存服务器(会忽略max-age以及Expires)
示例:Cache-Control:private,max-age=0,no-cache

② Connection:控制逐跳首部、管理持久连接
关闭持久连接:Connection:close
在旧版本http协议上维持持久连接:

Connection:keep-alive
Keep-Alive:timeout=10,max=500

③ Date:创建Http报文的日期和时间

④ Pragma:为兼容低版本Http协议的缓存字段,常与Cache-Control一起使用

Pragma:no-cache
Cache-Control:no-cache

⑤ Trailer:应用于Http/1.1分块传输编码时,说明报文主体后记录了哪些首部字段

Transfer-Encoding:chunked
Trailer:Expires

⑥ Transfer-Encoding:传输时采用的编码方式,Http/1.1仅对分块编码有效

⑦ Upgrade:使客户端或邻接服务器升级协议版本

Upgrade:TLS/1.0
Connection:upgrade

⑧ Via:追踪客户端与服务器之间的请求和响应报文的传输路径,常与Trace方法连用,详见本博客代理那一节笔记

⑨ Warning:告知用户一些关于缓存的警告信息
格式 warning:[警告码] [警告主机:端口号] [“警告内容”] (日期)
例如:

110 response is stale 代理返回的资源已过期
111 revalidation failed 验证资源失败
113 heuristic expiration 响应有效期大于24小时,试探性过期
214 transformation applied 媒体类型被代理改变

解决debug到jdk源码时不能查看变量值的问题

 

亲测有效!!

如何跟踪jdk源码

看到这个标题大概大家都会在心里想谁还跟踪个源码呀,在eclipse中打个断点,以debug的方式运行,然后F5进入方法,F6跳过方法,F7跳出方法。但是不知道大家有没有注意到,如果你跟踪到的是jdk源码的话,比如HashMapput方法,即使你F5进入到这个方法的内部了,你也看不到你put的 key 和 value 的实际值。但是我们既然要跟踪源码,那么肯定要看到我们设置的 key 和 value 是如何历经九九八十一难才进入到 HashMap 这个桶之中的。为什么说是桶,大家有兴趣的可以自己去深入了解一下HashMap,这里我就不做过多的介绍了。

这是我们跟踪源码看到的情况:

%title插图%num

这是我们想要看到的情况:

%title插图%num

接下来就是今天的正题了

 


1、 编译源码

1.1 、简单介绍

首先我们要知道一个jar也就是rt.jar, 它是JAVA基础类库,也就是你在java doc里面看到的所有的类的class文件,但是 orcale 在编译jdk源码的时候为了减小jar包的大小,使用的是 javac -g:none 也就是不带任何的调试信息。这就是我们跟踪jdk源码但是看不到变量值的罪魁祸首,但是上帝在给你关了一扇门的同时也会给你开一扇窗的。那么我现在就来带你一下这扇窗是怎么打开的。

%title插图%num

上面这张图是jdk安装目录,可以看到这个有个src.zip,它就是今天的主角:jdk源码,你可以把它解压出来看一下里面的内容,其实就是一个个的java类。

1.2、 开始编译源码

  1. 我们需要将它解压出来,至于放在哪里看你心情,只要你能找得到就可以了,因为我们它是我们今天的重头戏。解压出来就是下面这些东西

%title插图%num

  1. 打开eclipse新建一个java工程,命名也看你心情。

%title插图%num

  1. 将*步解压出来的那6个文件夹全部复制到你工程下的src中(其实不需要全部,有些是可以删除的,但是为了省事就全部都复制进去好了。)

%title插图%num

  1. 其中有报错,但是你不用管,你只需要等到eclipse将这个工程编译完成后将其导出为jar文件。选中src-->右键-->选择Export

%title插图%num

  1. 取好你的名字,选择放到哪里,然后Finish就可以了。至此我们就完成*步了。

%title插图%num

2、 关联源码

  1. 在你的eclipse的工具栏选择window-->preferences,找到Installed JREs, 选择你使用的jdk点击Edit进行编辑。

%title插图%num

  1. 在编辑窗口点击 Add External JARs 选择我们刚刚编译好导出的那个jar包。

%title插图%num

  1. 红色的jar就是我们自己导出的jar,我们要将我们导出的jar往上移,移到蓝色的 rt.jar 上面。然后Finish
    %title插图%num
  2. 将我们编译的jar和src.zip进行关联。选择JRE System Library 找到里面我们编译的jar, 右键–>Propertites。

%title插图%num

  1. 在 Propertites 选择 External File... 找到我们*开始的src.zip并选择它。然后Aplly ok。一切结束。

%title插图%num

3、 大功告成

现在你就可以愉快的跟踪源码,看看你的变量是怎么在java的世界中遨游了。

为什么强烈禁止开发人员使用isSuccess作为变量名

转载自:https://www.toutiao.com/a6688488577111687694/?

在日常开发中,我们会经常要在类中定义布尔类型的变量,比如在给外部系统提供一个RPC接口的时候,我们一般会定义一个字段表示本次请求是否成功的。

关于这个”本次请求是否成功”的字段的定义,其实是有很多种讲究和坑的,稍有不慎就会掉入坑里,作者在很久之前就遇到过类似的问题,本文就来围绕这个简单分析一下。到底该如何定一个布尔类型的成员变量。

一般情况下,我们可以有以下四种方式来定义一个布尔类型的成员变量:

boolean success
boolean isSuccess
Boolean success
Boolean isSuccess

以上四种定义形式,你日常开发中*常用的是哪种呢?到底哪一种才是正确的使用姿势呢?

通过观察我们可以发现,前两种和后两种的主要区别是变量的类型不同,前者使用的是boolean,后者使用的是Boolean。

另外,*种和第三种在定义变量的时候,变量命名是success,而另外两种使用isSuccess来命名的。

首先,我们来分析一下,到底应该是用success来命名,还是使用isSuccess更好一点。

success 还是 isSuccess

到底应该是用success还是isSuccess来给变量命名呢?从语义上面来讲,两种命名方式都可以讲的通,并且也都没有歧义。那么还有什么原则可以参考来让我们做选择呢。

在阿里巴巴Java开发手册中关于这一点,有过一个『强制性』规定:

为什么强烈禁止开发人员使用isSuccess作为变量名

 

那么,为什么会有这样的规定呢?我们看一下POJO中布尔类型变量不同的命名有什么区别吧。

class Model1 {
 private Boolean isSuccess;
 public void setSuccess(Boolean success) {
 isSuccess = success;
 }
 public Boolean getSuccess() {
 return isSuccess;
 }
 }
class Model2 {
 private Boolean success;
 public Boolean getSuccess() {
 return success;
 }
 public void setSuccess(Boolean success) {
 this.success = success;
 }
}
class Model3 {
 private boolean isSuccess;
 public boolean isSuccess() {
 return isSuccess;
 }
 public void setSuccess(boolean success) {
 isSuccess = success;
 }
}
class Model4 {
 private boolean success;
 public boolean isSuccess() {
 return success;
 }
 public void setSuccess(boolean success) {
 this.success = success;
 }
}

以上代码的setter/getter是使用Intellij IDEA自动生成的,仔细观察以上代码,你会发现以下规律:

  • 基本类型自动生成的getter和setter方法,名称都是isXXX()和setXXX()形式的。
  • 包装类型自动生成的getter和setter方法,名称都是getXXX()和setXXX()形式的。

既然,我们已经达成一致共识使用基本类型boolean来定义成员变量了,那么我们再来具体看下Model3和Model4中的setter/getter有何区别。

我们可以发现,虽然Model3和Model4中的成员变量的名称不同,一个是success,另外一个是isSuccess,但是他们自动生成的getter和setter方法名称都是isSuccess和setSuccess。

Java Bean中关于setter/getter的规范

关于Java Bean中的getter/setter方法的定义其实是有明确的规定的,根据JavaBeans(TM) Specification规定,如果是普通的参数propertyName,要以以下方式定义其setter/getter:

public <PropertyType> get<PropertyName>();
public void set<PropertyName>(<PropertyType> a);

但是,布尔类型的变量propertyName则是单独定义的:

public boolean is<PropertyName>();
public void set<PropertyName>(boolean m);

为什么强烈禁止开发人员使用isSuccess作为变量名

 

通过对照这份JavaBeans规范,我们发现,在Model4中,变量名为isSuccess,如果严格按照规范定义的话,他的getter方法应该叫isIsSuccess。但是很多IDE都会默认生成为isSuccess。

那这样做会带来什么问题呢。

在一般情况下,其实是没有影响的。但是有一种特殊情况就会有问题,那就是发生序列化的时候。

序列化带来的影响

关于序列化和反序列化请参考Java对象的序列化与反序列化。我们这里拿比较常用的JSON序列化来举例,看看看常用的fastJson、jackson和Gson之间有何区别:

public class BooleanMainTest {
 public static void main(String[] args) throws IOException {
 //定一个Model3类型
 Model3 model3 = new Model3();
 model3.setSuccess(true);
 //使用fastjson(1.2.16)序列化model3成字符串并输出
 System.out.println("Serializable Result With fastjson :" + JSON.toJSONString(model3));
 //使用Gson(2.8.5)序列化model3成字符串并输出
 Gson gson =new Gson();
 System.out.println("Serializable Result With Gson :" +gson.toJson(model3));
 //使用jackson(2.9.7)序列化model3成字符串并输出
 ObjectMapper om = new ObjectMapper();
 System.out.println("Serializable Result With jackson :" +om.writeValueAsString(model3));
 }
}
class Model3 implements Serializable {
 private static final long serialVersionUID = 1836697963736227954L;
 private boolean isSuccess;
 public boolean isSuccess() {
 return isSuccess;
 }
 public void setSuccess(boolean success) {
 isSuccess = success;
 }
 public String getHollis(){
 return "hollischuang";
 }
}

以上代码的Model3中,只有一个成员变量即isSuccess,三个方法,分别是IDE帮我们自动生成的isSuccess和setSuccess,另外一个是作者自己增加的一个符合getter命名规范的方法。

以上代码输出结果:

Serializable Result With fastjson :{"hollis":"hollischuang","success":true}
Serializable Result With Gson :{"isSuccess":true}
Serializable Result With jackson :{"success":true,"hollis":"hollischuang"}

在fastjson和jackson的结果中,原来类中的isSuccess字段被序列化成success,并且其中还包含hollis值。而Gson中只有isSuccess字段。

我们可以得出结论:fastjson和jackson在把对象序列化成json字符串的时候,是通过反射遍历出该类中的所有getter方法,得到getHollis和isSuccess,然后根据JavaBeans规则,他会认为这是两个属性hollis和success的值。直接序列化成json:{“hollis”:”hollischuang”,”success”:true}

但是Gson并不是这么做的,他是通过反射遍历该类中的所有属性,并把其值序列化成json:{“isSuccess”:true}

可以看到,由于不同的序列化工具,在进行序列化的时候使用到的策略是不一样的,所以,对于同一个类的同一个对象的序列化结果可能是不同的。

前面提到的关于对getHollis的序列化只是为了说明fastjson、jackson和Gson之间的序列化策略的不同,我们暂且把他放到一边,我们把他从Model3中删除后,重新执行下以上代码,得到结果:

Serializable Result With fastjson :{"success":true}
Serializable Result With Gson :{"isSuccess":true}
Serializable Result With jackson :{"success":true}

现在,不同的序列化框架得到的json内容并不相同,如果对于同一个对象,我使用fastjson进行序列化,再使用Gson反序列化会发生什么?

public class BooleanMainTest {
 public static void main(String[] args) throws IOException {
 Model3 model3 = new Model3();
 model3.setSuccess(true);
 Gson gson =new Gson();
 System.out.println(gson.fromJson(JSON.toJSONString(model3),Model3.class));
 }
}
class Model3 implements Serializable {
 private static final long serialVersionUID = 1836697963736227954L;
 private boolean isSuccess;
 public boolean isSuccess() {
 return isSuccess;
 }
 public void setSuccess(boolean success) {
 isSuccess = success;
 }
 @Override
 public String toString() {
 return new StringJoiner(", ", Model3.class.getSimpleName() + "[", "]")
 .add("isSuccess=" + isSuccess)
 .toString();
 }
}

以上代码,输出结果:

Model3[isSuccess=false]

这和我们预期的结果完全相反,原因是因为JSON框架通过扫描所有的getter后发现有一个isSuccess方法,然后根据JavaBeans的规范,解析出变量名为success,把model对象序列化城字符串后内容为{“success”:true}。

根据{“success”:true}这个json串,Gson框架在通过解析后,通过反射寻找Model类中的success属性,但是Model类中只有isSuccess属性,所以,*终反序列化后的Model类的对象中,isSuccess则会使用默认值false。

但是,一旦以上代码发生在生产环境,这*对是一个致命的问题。

所以,作为开发者,我们应该想办法尽量避免这种问题的发生,对于POJO的设计者来说,只需要做简单的一件事就可以解决这个问题了,那就是把isSuccess改为success。这样,该类里面的成员变量时success,getter方法是isSuccess,这是完全符合JavaBeans规范的。无论哪种序列化框架,执行结果都一样。就从源头避免了这个问题。

引用以下R大关于阿里巴巴Java开发手册这条规定的评价(https://www.zhihu.com/question/55642203):

为什么强烈禁止开发人员使用isSuccess作为变量名

 

所以,在定义POJO中的布尔类型的变量时,不要使用isSuccess这种形式,而要直接使用success!

Boolean还是boolean?

前面我们介绍完了在success和isSuccess之间如何选择,那么排除错误答案后,备选项还剩下:

boolean success
Boolean success

那么,到底应该是用Boolean还是boolean来给定一个布尔类型的变量呢?

我们知道,boolean是基本数据类型,而Boolean是包装类型。关于基本数据类型和包装类之间的关系和区别请参考一文读懂什么是Java中的自动拆装箱

那么,在定义一个成员变量的时候到底是使用包装类型更好还是使用基本数据类型呢?

我们来看一段简单的代码

 /**
 * @author Hollis
 */
public class BooleanMainTest {
 public static void main(String[] args) {
 Model model1 = new Model();
 System.out.println("default model : " + model1);
 }
}
class Model {
 /**
 * 定一个Boolean类型的success成员变量
 */
 private Boolean success;
 /**
 * 定一个boolean类型的failure成员变量
 */
 private boolean failure;
 /**
 * 覆盖toString方法,使用Java 8 的StringJoiner
 */
 @Override
 public String toString() {
 return new StringJoiner(", ", Model.class.getSimpleName() + "[", "]")
 .add("success=" + success)
 .add("failure=" + failure)
 .toString();
 }
}

以上代码输出结果为:

default model : Model[success=null, failure=false]

可以看到,当我们没有设置Model对象的字段的值的时候,Boolean类型的变量会设置默认值为null,而boolean类型的变量会设置默认值为false。

即对象的默认值是null,boolean基本数据类型的默认值是false。

在阿里巴巴Java开发手册中,对于POJO中如何选择变量的类型也有着一些规定:

为什么强烈禁止开发人员使用isSuccess作为变量名

 

这里建议我们使用包装类型,原因是什么呢?

举一个扣费的例子,我们做一个扣费系统,扣费时需要从外部的定价系统中读取一个费率的值,我们预期该接口的返回值中会包含一个浮点型的费率字段。当我们取到这个值得时候就使用公式:金额*费率=费用 进行计算,计算结果进行划扣。

如果由于计费系统异常,他可能会返回个默认值,如果这个字段是Double类型的话,该默认值为null,如果该字段是double类型的话,该默认值为0.0。

如果扣费系统对于该费率返回值没做特殊处理的话,拿到null值进行计算会直接报错,阻断程序。拿到0.0可能就直接进行计算,得出接口为0后进行扣费了。这种异常情况就无法被感知。

这种使用包装类型定义变量的方式,通过异常来阻断程序,进而可以被识别到这种线上问题。如果使用基本数据类型的话,系统可能不会报错,进而认为无异常。

以上,就是建议在POJO和RPC的返回值中使用包装类型的原因。

但是关于这一点,作者之前也有过不同的看法:对于布尔类型的变量,我认为可以和其他类型区分开来,作者并不认为使用null进而导致NPE是一种*好的实践。因为布尔类型只有true/false两种值,我们完全可以和外部调用方约定好当返回值为false时的明确语义。

后来,作者单独和《阿里巴巴Java开发手册》、《码出高效》的作者——孤尽 单独1V1(qing) Battle(jiao)了一下。*终达成共识,还是尽量使用包装类型。

但是,作者还是想强调一个我的观点,尽量避免在你的代码中出现不确定的null值。

null何罪之有?

关于null值的使用,我在使用Optional避免NullPointerException、9 Things about Null in Java等文中就介绍过。

null是很模棱两可的,很多时候会导致令人疑惑的的错误,很难去判断返回一个null代表着什么意思。

图灵*得主Tony Hoare 曾经公开表达过null是一个糟糕的设计。

为什么强烈禁止开发人员使用isSuccess作为变量名

 

我把 null 引用称为自己的十亿美元错误。它的发明是在1965 年,那时我用一个面向对象语言( ALGOL W )设计了*个全面的引用类型系统。我的目的是确保所有引用的使用都是*对安全的,编译器会自动进行检查。但是我未能抵御住诱惑,加入了Null引用,仅仅是因为实现起来非常容易。它导致了数不清的错误、漏洞和系统崩溃,可能在之后 40 年中造成了十亿美元的损失。

当我们在设计一个接口的时候,对于接口的返回值的定义,尽量避免使用Boolean类型来定义。大多数情况下,别人使用我们的接口返回值时可能用if(response.isSuccess){}else{}的方式,如果我们由于忽略没有设置success字段的值,就可能导致NPE(java.lang.NullPointerException),这明显是我们不希望看到的。

所以,当我们要定义一个布尔类型的成员变量时,尽量选择boolean,而不是Boolean。当然,编程中并没有*对。

总结

本文围绕布尔类型的变量定义的类型和命名展开了介绍,*终我们可以得出结论,在定义一个布尔类型的变量,尤其是一个给外部提供的接口返回值时,要使用success来命名,阿里巴巴Java开发手册建议使用封装类来定义POJO和RPC返回值中的变量。但是这不意味着可以随意的使用null,我们还是要尽量避免出现对null的处理的。

MongoDB配置用户名与密码

本篇的前提是已经安装好MongoDB并且成功连接上mongodb.

*次玩MongoDB我安装的是3.6.3  ,安装完成之后默认不需要用户名与密码,我希望像之前用的关系型数据库mysql一样 设置一个用户名与密码,在网上查阅,许多方法不对,才知道是版本问题,因此如果您读到此篇,设置之前需保证自己的版本正确。

首先 ,在默认无需用户名密码的情况下连接上mongodb

C:\Users\Administrator>mongo
MongoDB shell version v3.6.3
connecting to: mongodb://127.0.0.1:27017
MongoDB server version: 3.6.3

我这样写的前提是将mongodb添加到系统环境变量,将安装mongodb的路径..\mongodb\bin\ 添加到计算机高级配置 环境变量里面的系统变量,多个变量之间用分号隔开。

注意:在写的时候翻了个错误 ,创建用户前要知道自己当前属于哪个数据库,默认连接到test数据库,结果我在test数据库中创建了对admin有读写权限的jenny,

所以要先use dbname 进入该数据库,在进行角色创建工作。

复制代码

> db.createUser({
            user:'jenny',
            pwd:'jenny',
            roles:[
                     {role:'readWrite',db:'admin'}
                  ]
    })

复制代码

成功执行

%title插图%num

其次,打开MongoDB安装目录下之前建立的,mongo.config 配置文件,添加 auth=true 保存关闭即可

%title插图%num

关闭验证的话 填写noauth=ture

重启MongoDB 会看到如下界面,不会自动连接;

%title插图%num

这时使用 show dbs 会报错,需使用如下命令返回值为1说明正确连接

> db.auth(‘jenny’,’jenny’)
1
>

在内置数据库admin中设置的用户及密码可访问系统中所有其他表

附录:几个mogodb命令

1.show dbs     —-列出所有数据库

2.db.getName()        —-列出当前数据库名

3.use dbname     —-切换到某个数据库

4.db.createUser()  —-创建用户角色

5.db.auth()               —- 验证用户到数据库

内存屏障

为什么会有内存屏障

  • 每个CPU都会有自己的缓存(有的甚至L1,L2,L3),缓存的目的就是为了提高性能,避免每次都要向内存取。但是这样的弊端也很明显:不能实时的和内存发生信息交换,分在不同CPU执行的不同线程对同一个变量的缓存值不同。
  • 用volatile关键字修饰变量可以解决上述问题,那么volatile是如何做到这一点的呢?那就是内存屏障,内存屏障是硬件层的概念,不同的硬件平台实现内存屏障的手段并不是一样,java通过屏蔽这些差异,统一由jvm来生成内存屏障的指令。

内存屏障是什么

  • 硬件层的内存屏障分为两种:Load Barrier 和 Store Barrier即读屏障和写屏障。
  • 内存屏障有两个作用:
  1. 阻止屏障两侧的指令重排序;
  2. 强制把写缓冲区/高速缓存中的脏数据等写回主内存,让缓存中相应的数据失效。
  • 对于Load Barrier来说,在指令前插入Load Barrier,可以让高速缓存中的数据失效,强制从新从主内存加载数据;
  • 对于Store Barrier来说,在指令后插入Store Barrier,能让写入缓存中的*新数据更新写入主内存,让其他线程可见。

java内存屏障

  • java的内存屏障通常所谓的四种即LoadLoad,StoreStore,LoadStore,StoreLoad实际上也是上述两种的组合,完成一系列的屏障和数据同步功能。
  • LoadLoad屏障:对于这样的语句Load1; LoadLoad; Load2,在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕。
  • StoreStore屏障:对于这样的语句Store1; StoreStore; Store2,在Store2及后续写入操作执行前,保证Store1的写入操作对其它处理器可见。
  • LoadStore屏障:对于这样的语句Load1; LoadStore; Store2,在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕。
  • StoreLoad屏障:对于这样的语句Store1; StoreLoad; Load2,在Load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见。它的开销是四种屏障中*大的。在大多数处理器的实现中,这个屏障是个万能屏障,兼具其它三种内存屏障的功能

volatile语义中的内存屏障

  • volatile的内存屏障策略非常严格保守,非常悲观且毫无安全感的心态:

在每个volatile写操作前插入StoreStore屏障,在写操作后插入StoreLoad屏障;
在每个volatile读操作前插入LoadLoad屏障,在读操作后插入LoadStore屏障;

  • 由于内存屏障的作用,避免了volatile变量和其它指令重排序、线程之间实现了通信,使得volatile表现出了锁的特性。

final语义中的内存屏障

  • 对于final域,编译器和CPU会遵循两个排序规则:
  1. 新建对象过程中,构造体中对final域的初始化写入和这个对象赋值给其他引用变量,这两个操作不能重排序;(废话嘛)
  2. 初次读包含final域的对象引用和读取这个final域,这两个操作不能重排序;(晦涩,意思就是先赋值引用,再调用final值)
  • 总之上面规则的意思可以这样理解,必需保证一个对象的所有final域被写入完毕后才能引用和读取。这也是内存屏障的起的作用:
  • 写final域:在编译器写final域完毕,构造体结束之前,会插入一个StoreStore屏障,保证前面的对final写入对其他线程/CPU可见,并阻止重排序。
  • 读final域:在上述规则2中,两步操作不能重排序的机理就是在读final域前插入了LoadLoad屏障。
  • X86处理器中,由于CPU不会对写-写操作进行重排序,所以StoreStore屏障会被省略;而X86也不会对逻辑上有先后依赖关系的操作进行重排序,所以LoadLoad也会变省略。

HttpServletResponse中sendError与setStatus的区别

转载自:https://my.oschina.net/angerbaby/blog/468687

*近在开发项目时,由于前端代码调用后端接口,需要使用响应状体码告知前端登录异常(401)和权限验证不通过(403)。前端拿到对应的状态码会做出相应的处理。

上述的登录验证和权限验证,后端采用Spring拦截器技术实现。为了返回指定的状态码,使用了HttpServletResponse中的setStatus方法。一切都正常运行,没问题!但突然我有了个想法,如果登录验证不通过,需要跳转到专门负责显示401友好提示信息的页面,如何做?有人会说使用sendRedirect方法,可以。还有吗?

我的做法是直接根据响应状态码,依赖web.xml中的error-page配置实现报错页面的跳转。相信下面代码的配置,搞JavaWeb开发的人应该都会熟悉:

  1. <error-page>
  2. <error-code>401</error-code>
  3. <location>/WEB-INF/view/401.jsp</location>
  4. </error-page>

但在真正运行时发现,setStatus确实可以设置response的状态码,却无法像设想的那样,显示出401.jsp页面中的内容。经过一番搜索查证,发现使用sendError代替setStatus,可以达到期望的效果。可是,为什么?sendError和setStatus有何区别?下面是直译官方API文档的内容:

sendError(int sc):使用指定的状态码并清空缓冲,发送一个错误响应至客户端。如果响应已经被提交,这个方法会抛出IllegalStateException。使用这个方法后,响应则应该被认为已被提交,且不应该再被进行写操作了。

sendError(int sc, String msg):使用指定的状态码发送一个错误响应至客户端。服务器默认会创建一个HTML格式的服务错误页面作为响应结果,其中包含参数msg指定的文本信息,这个HTML页面的内容类型为“text/html”,保留cookies和其他未修改的响应头信息。如果一个对应于传入的错误码的错误页面已经在web.xml中声明,那么这个声明的错误页面将会优先于建议的msg参数服务于客户端。(ps:相比较上面的方法,我更倾向于前者。使用上面的方法,可以通过定制不同状态的响应结果显示于客户端,我们应该不想让客户端看到服务器创建出的简单粗暴的页面吧?)

setStatus(int sc):设置响应的状态码。这个方法被用于当响应结果正常时(例如,状态码为SC_OK或SC_MOVED_TEMPORARTLY)设置响应状态码。如果发生错误,而且来访者希望调用在web应用中定义的错误页面作为显示,那么应该使用sendError方法代替之。使用setStatus方法之后,容器会清空缓冲并设置Location响应头,保留cookies和其他响应头信息。

从直译的结果不难发现哈,sendError适用于报错且存在对应的报错页面配置作为输出显示的情况,而setStatus适用于正常响应的情况,仅仅可以改变响应状态码而已。

SQL语句为什么执行的很慢?

转载自:https://mp.weixin.qq.com/s/r-9BLOK5zFEYqwXc1bdGKA

说实话,这个问题可以涉及到 MySQL 的很多核心知识,可以扯出一大堆,就像要考你计算机网络的知识时,问你“输入URL回车之后,究竟发生了什么”一样,看看你能说出多少了。

 

之前腾讯面试的实话,也问到这个问题了,不过答的很不好,之前没去想过相关原因,导致一时之间扯不出来。所以今天,我带大家来详细扯一下有哪些原因,相信你看完之后一定会有所收获,不然你打我。

 

分类讨论:

 

一条 SQL 语句执行的很慢,那是每次执行都很慢呢?还是大多数情况下是正常的,偶尔出现很慢呢?所以我觉得,我们还得分以下两种情况来讨论。

 

  • 大多数情况是正常的,只是偶尔会出现很慢的情况。
  • 在数据量不变的情况下,这条SQL语句一直以来都执行的很慢。

 

针对这两种情况,我们来分析下可能是哪些原因导致的。

 

一、针对偶尔很慢的情况

 

一条 SQL 大多数情况正常,偶尔才能出现很慢的情况,针对这种情况,我觉得这条SQL语句的书写本身是没什么问题的,而是其他原因导致的,那会是什么原因呢?

 

1、数据库在刷新脏页

 

当我们要往数据库插入一条数据、或者要更新一条数据的时候,我们知道数据库会在内存中把对应字段的数据更新了,但是更新之后,这些更新的字段并不会马上同步持久化到磁盘中去,而是把这些更新的记录写入到 redo log 日记中去,等到空闲的时候,在通过 redo log 里的日记把*新的数据同步到磁盘中去。

 

不过,redo log 里的容量是有限的,如果数据库一直很忙,更新又很频繁,这个时候 redo log 很快就会被写满了,这个时候就没办法等到空闲的时候再把数据同步到磁盘的,只能暂停其他操作,全身心来把数据同步到磁盘中去的,而这个时候,就会导致我们平时正常的SQL语句突然执行的很慢,所以说,数据库在在同步数据到磁盘的时候,就有可能导致我们的SQL语句执行的很慢了。

 

2、拿不到锁

 

这个就比较容易想到了,我们要执行的这条语句,刚好这条语句涉及到的表,别人在用,并且加锁了,我们拿不到锁,只能慢慢等待别人释放锁了。或者,表没有加锁,但要使用到的某个一行被加锁了,这个时候,我也没办法啊。

 

如果要判断是否真的在等待锁,我们可以用 show processlist这个命令来查看当前的状态哦,这里我要提醒一下,有些命令*好记录一下,反正,我被问了好几个命令,都不知道怎么写,呵呵。

 

下来我们来访分析下第二种情况,我觉得第二种情况的分析才是*重要的。

 

二、针对一直都这么慢的情况

 

如果在数据量一样大的情况下,这条 SQL 语句每次都执行的这么慢,那就就要好好考虑下你的 SQL 书写了,下面我们来分析下哪些原因会导致我们的 SQL 语句执行的很不理想。

 

我们先来假设我们有一个表,表里有下面两个字段,分别是主键 id,和两个普通字段 c 和 d。

 

mysql> CREATE TABLE `t` (

`id` int(11) NOT NULL,

`c` int(11) DEFAULT NULL,

`d` int(11) DEFAULT NULL,

PRIMARY KEY (`id`)

) ENGINE=InnoDB;

 

1、没用到索引

 

没有用上索引,我觉得这个原因是很多人都能想到的,例如你要查询这条语句:

 

select * from t where 100 <c and c < 100000;

 

1)字段没有索引

 

刚好你的 c 字段上没有索引,那么抱歉,只能走全表扫描了,你就体验不会索引带来的乐趣了,所以,这回导致这条查询语句很慢。

 

2)字段有索引,但却没有用索引

 

好吧,这个时候你给 c 这个字段加上了索引,然后又查询了一条语句:

 

select * from t where c – 1 = 1000;

 

我想问大家一个问题,这样子在查询的时候会用索引查询吗?

 

答是不会,如果我们在字段的左边做了运算,那么很抱歉,在查询的时候,就不会用上索引了,所以呢,大家要注意这种字段上有索引,但由于自己的疏忽,导致系统没有使用索引的情况了。

 

正确的查询应该如下:

 

select * from t where c = 1000 + 1;

 

有人可能会说,右边有运算就能用上索引?难道数据库就不会自动帮我们优化一下,自动把 c – 1=1000 自动转换为 c = 1000+1。

 

不好意思,确实不会帮你,所以,你要注意了。

 

3)函数操作导致没有用上索引

 

如果我们在查询的时候,对字段进行了函数操作,也是会导致没有用上索引的,例如:

 

select * from t where pow(c,2) = 1000;

 

 

这里我只是做一个例子,假设函数 pow 是求 c 的 n 次方,实际上可能并没有 pow(c,2)这个函数。其实这个和上面在左边做运算也是很类似的。

 

所以呢,一条语句执行都很慢的时候,可能是该语句没有用上索引了,不过具体是啥原因导致没有用上索引的呢,你就要会分析了,我上面列举的三个原因,应该是出现的比较多的吧。

 

2、数据库自己选错索引

 

我们在进行查询操作的时候,例如:

 

select * from t where 100 < c and c < 100000;

 

我们知道,主键索引和非主键索引是有区别的,主键索引存放的值是整行字段的数据,而非主键索引上存放的值不是整行字段的数据,而且存放主键字段的值。不大懂的可以看我这篇文章:《面试小知识:MySQL索引相关》,里面有说到主键索引和非主键索引的区别。

 

也就是说,我们如果走 c 这个字段的索引的话,*后会查询到对应主键的值,然后,再根据主键的值走主键索引,查询到整行数据返回。

 

好吧扯了这么多,其实我就是想告诉你,就算你在 c 字段上有索引,系统也并不一定会走 c 这个字段上的索引,而是有可能会直接扫描扫描全表,找出所有符合 100 < c and c < 100000 的数据。

 

为什么会这样呢?

 

其实是这样的,系统在执行这条语句的时候,会进行预测:究竟是走 c 索引扫描的行数少,还是直接扫描全表扫描的行数少呢?显然,扫描行数越少当然越好了,因为扫描行数越少,意味着I/O操作的次数越少。

 

如果是扫描全表的话,那么扫描的次数就是这个表的总行数了,假设为 n;而如果走索引 c 的话,我们通过索引 c 找到主键之后,还得再通过主键索引来找我们整行的数据,也就是说,需要走两次索引。而且,我们也不知道符合 100 c < and c < 10000 这个条件的数据有多少行,万一这个表是全部数据都符合呢?这个时候意味着,走 c 索引不仅扫描的行数是 n,同时还得每行数据走两次索引。

 

所以呢,系统是有可能走全表扫描而不走索引的。

 

那系统是怎么判断呢?

 

判断来源于系统的预测,也就是说,如果要走 c 字段索引的话,系统会预测走 c 字段索引大概需要扫描多少行。如果预测到要扫描的行数很多,它可能就不走索引而直接扫描全表了。

 

那么问题来了,系统是怎么预测判断的呢?这里我给你讲下系统是怎么判断的吧,虽然这个时候我已经写到脖子有点酸了。

 

系统是通过索引的区分度来判断的,一个索引上不同的值越多,意味着出现相同数值的索引越少,意味着索引的区分度越高。我们也把区分度称之为基数,即区分度越高,基数越大。所以呢,基数越大,意味着符合 100 < c and c < 10000 这个条件的行数越少。

 

所以呢,一个索引的基数越大,意味着走索引查询越有优势。

 

那么问题来了,怎么知道这个索引的基数呢?

 

系统当然是不会遍历全部来获得一个索引的基数的,代价太大了,索引系统是通过遍历部分数据,也就是通过采样的方式,来预测索引的基数的。

 

扯了这么多,重点的来了:

 

既然是采样,那就有可能出现失误的情况,也就是说,c 这个索引的基数实际上是很大的,但是采样的时候,却很不幸,把这个索引的基数预测成很小。例如你采样的那一部分数据刚好基数很小,然后就误以为索引的基数很小。然后就呵呵,系统就不走 c 索引了,直接走全部扫描了。

 

所以呢,说了这么多,得出结论:由于统计的失误,导致系统没有走索引,而是走了全表扫描,而这,也是导致我们 SQL 语句执行的很慢的原因。

 

这里我声明一下,系统判断是否走索引,扫描行数的预测其实只是原因之一,这条查询语句是否需要使用使用临时表、是否需要排序等也是会影响系统的选择的。

 

不过呢,我们有时候也可以通过强制走索引的方式来查询,例如:

 

select * from t force index(a) where c < 100 and c < 100000;

 

我们也可以通过:

 

show index from t;

 

来查询索引的基数和实际是否符合,如果和实际很不符合的话,我们可以重新来统计索引的基数,可以用这条命令:

 

analyze table t;

 

来重新统计分析。

 

既然会预测错索引的基数,这也意味着,当我们的查询语句有多个索引的时候,系统有可能也会选错索引哦,这也可能是 SQL 执行的很慢的一个原因。

 

好吧,就先扯这么多了,你到时候能扯出这么多,我觉得已经很棒了,下面做一个总结。

 

三、总结

 

以上是我的总结与理解,*后一个部分,我怕很多人不大懂数据库居然会选错索引,所以我详细解释了一下,下面我对以上做一个总结。

 

一个 SQL 执行的很慢,我们要分两种情况讨论:

 

大多数情况下很正常,偶尔很慢,则有如下原因:

 

  • 数据库在刷新脏页,例如 redo log 写满了需要同步到磁盘。
  • 执行的时候,遇到锁,如表锁、行锁。

 

这条 SQL 语句一直执行的很慢,则有如下原因:

 

  • 没有用上索引:例如该字段没有索引;由于对字段进行运算、函数操作导致无法用索引。
  • 数据库选错了索引。

云主机搭建多线Minecraft服务器

前言
此教程适合初学者,想看多线的部署请看*后。

相信许多朋友和我一样,很喜欢玩MC,但是用别人的服务器总不是个长久之计。为了有一片自己和朋友们的秘密基地,不如自己开设一个服务器。

正文
准备工作:
云主机一台:作为主要的服务器,性能不必太好,有树莓派般的性能即可带起三四人的服务器了。(国内有许多特价的学生机)

安装Linux:我用的Ubuntu 16发行版,国内可能用CentOS一系的居多,本质上大差不离吧。用Windows server也行,但不在本文讨论范围内。

安装JVM:openJDK 和 Oracle 的官方JDK都可以,请下载适合你的操作系统的JDK;个人感觉openJDK性能差于官方JDK。

open JDK可以用 apt-get install 直接安装。

Oracle JDK请去官网下载,藉由FTP等方法部署到服务器上,此方法需要自己配置环境变量。参见Linux公社-Ubuntu安装JDK

获取用于开设服务器jar包:可在官网下载(也许要科学上网) 下载 MINECRAFT :JAVA 版的服务器

(可选)第二台主机:主要用来利用“端口转发”布置多线访问,解决可能存在的网络质量问题。

部署服务器:
#确认Java安装无误
java -version

#显示版本信息则安装无误
openjdk version “1.8.0_181”
OpenJDK Runtime Environment (build 1.8.0_181-8u181-b13-0ubuntu0.16.04.1
b13)
OpenJDK 64-Bit Server VM (build 25.181-b13, mixed mode)
#切换目录至存放jar的目录
cd ~/mc/mcserver

#运行服务端
#-Xms为初始分配内存 -Xmx*大内存
#-jar 后跟jar包的*对路径。注意!–你的文件名与路径可能与我不同–!
# nogui不需要图形界面,节约性能
java -Xms512M -Xmx1024M -jar “$PWD”/server1.13.2.jar nogui
#建议把上述命令保存为shell脚本方便启动服务器

#初次运行时会强制退出并生成一些文件
#其中eula.txt为用户协议,打开并修改*后的eula=true既视为同意用户协议
#盗版用户需关闭在线模式:修改server.properties文件的online-mode为false

#更详细的Minecraft服务器配置方法请参阅其它教程
更改好配置后,再次启动服务器

现在可以进行测试了,对于默认端口(25565)可以直接填入主机的外网IP进行访问。

tip:利用ssh开启服务器的话,会在关闭会话的同时结束所有此会话下属的程序,也就是会关闭服务器,这明显不是我们所期望的。对此可以使用screen软件来创建虚拟会话保持服务器的运行,参考:linux screen *简单的五个用法

多线连接的方法
本方法核心思路是利用iptables进行端口映射与转发,既利用一台额外的可选服务器作为跳板去访问主服务器,以此解决有人连接不到主服务器的问题。(请保证主服务器与跳板机之间的网络质量,以免适得其反;同时跳板机*好与主服务器不在同一网段,否则意义不大)

#在跳板机上配置iptables

#需打开fword转发功能
nano /etc/sysctl.conf
#将下项注释去掉
#net.ipv4.ipv4_forward=1
#使改动生效
sudo sysctl -p

#假如没有配置用途的话,*好先清空iptables
#端口转发(假设跳板机IP 172.0.0.1开放9528端口 主服务器IP 106.8.8.8)
#在nat表中新增一个路由:把访问172.0.0.1:9528的TCP包利用目的地址转换转发到106.8.8.8:25565
sudo iptables -t nat -A PREROUTING -d 172.0.0.1 -p tcp –dport 9528 -DNAT –to-destination 106.8.8.8:25565

#在nat表中新增一个路由:把106.8.8.8:25565发送的TCP包的源地址改为172.0.0.1
sudo iptables -t nat -A POSTROUTING -d 106.8.8.8 -p tcp –dport 25565 -j SNAT –to 172.0.0.1
运行上述语句后,转发便开始了,可以利用跳板机的公网IP进行测试了。
%title插图%num

spring中bean之间的引用以及内部bean

转载自:https://www.cnblogs.com/sxdcgaq8080/p/5680612.html

在spring中会有如下的几种情况:

1.在当前容器中,(即在spring.xml这一个配置文件中),一个bean引用了另一个bean。

使用

1》  <ref  bean=”另一个bean的id” />

1.1 构造器

%title插图%num

1.2 setter

%title插图%num

 

  2》ref作属性

—–2.1  -构造器注入:<constructor-arg   index=”0″  ref=”另一个bean的id”  />

%title插图%num

 

—–2.2-setter注入:<property   name=” ”  ref=”另一个bean的id”  />

 

%title插图%num

 

2.<ref  local=” “>

引用当前容器中的另一个<bean>,只能通过引用这样定义的<bean>即:<bean  id=”bean1″  class=””/> 定义id的可以被识别.

其余的<bean  name=”bean2″  class=””> 或者<bean alias=”bean3″  class=””>都识别不到。

%title插图%num

 

3.<ref parent=””  />

引用父容器中的bean,若父容器中定义的<bean  id=”bean1″ class=”” />,当前容器中也有一个<bean  id=”bean1″ class=””/> 则<ref parent =””>会直接去父容器中去找,如果没有那就是没有,不会在当前容器中寻找。

%title插图%num

 

4.内部bean

1.在<property>或<constructor-arg>内部通过<bean>定义的,

2.该bean不管是否指定id或者name,该bean都有一个唯一的匿名标识符,且不能被指定别名

3.该bean队其他外部的bean不可见。

%title插图%num

我的世界:CentOS搭建mc服务器

安装配置
1. 安装配置java环境,用Java1.8
2. 获取mc服务器jar包:
wget https://s3.amazonaws.com/Minecraft.Download/versions/[version]/minecraft_server.[version].jar

# 如采用1.11.2版本服务器端
wget htts://s3.amazonaws.com/Minecraft.Download/versions/1.11.2/minecraft_server.1.11.2.jar

3. 启动mc服务器端:
java -Xms**m -Xmx**m -jar [path]/minecraft_server.[version].jar nogui

# 1.11.2版本
java -Xms512m -Xmx768m -jar /root/mc/minecraft_server.1.11.2.jar nogui
# -Xms:初始启动分配的内存(-Xms512m)
# -Xmx:*大分配的内存(-Xmx768m)
# nogui:用于以基于文本的界面来显示,可减少内存使用。如果使用图形化界面,那么移除nogui选项。

4. 同意*终用户许可协议 EULA
首次启动不会成功启动,会生成一个eula.txt 文件。用vim打开,将行 eula = false 更改为 eula = true,并保存文件,表示同意许可协议。

5. 服务器要开25565端口
服务器端默认使用25565端口,可配置,配置详情参考下一条。

如腾讯云服务器设置步骤:

控制台-云服务器-安全组-安全组规则-添加规则:
来源:0.0.0.0/0
协议端口:TCP:25565

6. 修改server.propertices
如果客户端连接报错,修改服务器端server.propertices文件:
把 online_mode=true 改成online_mode=false,重启服务再试。
这个选项表示是否连接正版服务器验证用户。

其中server.propertices是mc服务端配置文件,可设置游戏难度、世界类型、游戏模式、允许玩家数量、世界大小、黑白名单等等。
配置文件内容:

#Minecraft server properties
#Fri Jan 05 22:45:30 CST 2018
generator-settings=
op-permission-level=4
allow-nether=true
level-name=world #存档名称,也就是读取的存档文件夹的名称,默认为world
enable-query=false
allow-flight=false
announce-player-achievements=true
server-port=25565 #端口,客户端连接的话要指定这个端口,服务器防火墙要开放这个端口。可以不指定,默认为:25565
level-type=DEFAULT
enable-rcon=false
force-gamemode=false
level-seed= #地图种子
server-ip=
max-build-height=256
spawn-npcs=true
white-list=false
spawn-animals=true
snooper-enabled=true
hardcore=false
online-mode=false #是否连接正版服务器校验
resource-pack=
pvp=true
difficulty=1
enable-command-block=false
player-idle-timeout=0
gamemode=0
max-players=20 #*大玩家数
spawn-monsters=true
view-distance=10
generate-structures=true
motd=A Minecraft Server

另外服务器启动后可以在后台执行命令。如设置超级管理员op命令:

op player1 # 把player1设为op,然后player1就能输入作弊码了。

安装mod
若要在服务器上安装mod,需下载相应版本的forge jar包(假设1.7.10版本,则为forge-1.7.10-10.13.4.1558-1.7.10-universal.jar),将forge-1.7.10-10.13.4.1558-1.7.10-universal.jar同客户端.minecraft文件夹下libraries目录一起拷贝到服务器端(同服务器jar位于同一目录)。然后执行:

java -Xms512m -Xmx768m -jar /root/mc/forge-1.7.10-10.13.4.1558-1.7.10-universal.jar nogui

执行成功后就会生成mods目录,然后将相关mod的jar包放到mods目录里,重启服务器就可以了(启动服务器也使用上面forge jar包的命令)。

注意客户端需要安装了同样的mod,mod才能有效。

另外:mc1.12用forge-1.12-14.21.1.2443-installer.jar安装mod,用forge-1.7.10-10.13.4.1558-1.7.10-universal.jar启动。

虚拟终端screen
另外为了让此服务器程序一直运行,可以安装虚拟终端screen:

yum install -y screen

开终端mc,然后在里面执行命令:

screen -S mc # 开一个名为mc的session
… # 然后是要在虚拟终端mc下执行的命令
# 将终端后台:按`ctrl+a`,然后按`d`。

回到终端mc:

screen -r mc

列出已经打开的session:

screen -ls #或
screen -list