HTTPS细节

  1. Server Hello:告知客户端后续使用的SSL版本,加密套件
    加密套件:使用非对称协议P1(例如RSA)加密进行对称协议P2(如AES)加密的秘钥,并使用对称协议P2绝对信息进行加密

  2. Certificate协议:传输服务端的证书内容

  3. Server Key Exchange:提供证书公钥(如果Certificate协议没有)

  4. Certificate Request: 可选,服务端需要验证客户端证书时才发送

  5. Certificate Verify: 告诉服务端证书校验没问题

  6. Change Clipher Spec:告诉服务端,确认收到刚才选的加密套件,接下来就用这个加密套件了。服务器收到之后,用约定的非对称协议P1和证书私钥解密pre master。三个随机数通过一个密钥导出器最终导出一个对称密钥K1

  7. Encrypted Handshake Mesaage:用刚才选的加密套件的P2协议和对称秘钥K1加密一段数据,让客户端验证加密通道的正确性


【译】为何Lambda中的局部变量必须是final

译自 Why Do Local Variables Used in Lambdas Have to Be Final or Effectively Final?
以学习为目的翻译,适当省略部分内容和增加个人补充

简介

Java8 提供了lambda表达式,同事也给出了实际final变量的概念,意思是lamdba表达式使用的局部变量必须是显式生命为final,或事实上是final,即声明后不再修改。你有没用想过其中的原因呢?

oracle的官方文档JLS15.27.2. Lambda Body一节有提到,“禁止使用可动态修改的局部变量,因为有可能导致并发问题”,这是什么意思呢?

接下来,我们会深入了解这个限制使用可变局部变量的规定,并给出相关的例子证明它是如何影响单线程和并发程序的。我们也会展示一个与这个限制相关常用的反范式例子。

2. 捕获Lambdas

Lambda表达式可以使用内层和外层作用域中敌营的变量,我们称之为捕获Lambdas,包括静态变量、实例变量和局部变量,但局部变量必须是final事实上是final

在早期Java版本中,我们需要在匿名内部类使用的外部变量前加上final关键字。
现在Java语法糖会自动帮我们识别这种情况并在编译之前帮我们加上遗漏的final关键字,因此,代码中即使没有显示声明变量是final也没关系,但这并不会改变变量是final的事实(如果你尝试修改变量,则会出现编译错误)。

3.捕获lambdas中的局部变量

1
2
3
Supplier<Integer> incrementer(int start) {
return () -> start++;
}

上述代码中局部变量start在lambda表达式中被修改,无法编译。
这段代码无法编译的原因是实际上lambda捕获的是start的值,意思就是start的一个拷贝副本。这就要求start变量必须是final的,这样才能避免lamdba中的start++操作改变了incrementer方法的参数值。

但为什么要拷贝呢?请注意一点,这个方法return的是一个lambda,因此,这个incrementer方法执行完了,lambda不会都执行,此时incrementer方法的局部变量start(在栈中)已经被垃圾回收,所以Java会为start参数做一个拷贝副本,实际访问的也是这个副本,而不是原始变量。只有这样,return的lambda才能在incrementer方法之外‘存活’。

补充:
final可修饰引用数据类型基本数据类型

4.并发问题

举个反例,如果lambda捕获的局部变量可以被修改(不是final,不拷贝)。

1
2
3
4
5
6
7
8
9
10
public void localVariableMultithreading() {
boolean run = true;
executor.execute(() -> {
while (run) {
// do operation
}
});

run = false;
}

那么这段代码将会有潜在的‘可见性’问题。
可以分一下几种可能考虑:

  1. 因为每个线程都有各自的栈,这该如何保证while循环每次都能正确看到其他栈中run变量发生的改变?
    答案是,使用用synchronizedvolatile关键字
  2. 多线程的情况下,使用lambda的线程,可能会在分配该局部变量的的线程将这个变量回收之后,去访问该变量

正是因为有了这个强制final的措施,我们才不需要亲自考虑这几种情况。

补充:
Java的不可变类(Immutable Objects)有一个特点就是线程安全。在多线程情况下,一个可变对象的值很可能被其他进程改变,这样会造成不可预期的结果,而使用不可变对象就可以避免这种情况同时省去了同步加锁等过程,因此不可变类是线程安全的

5.静态变量和实例变量

第一个例子,把start变量改为实例变量,即可成功编译。

1
2
3
4
5
private int start = 0;

Supplier<Integer> incrementer() {
return () -> start++;
}

为什么实例变量start可以被修改呢?
这和成员变量存储的位置有关。局部变量存储在栈中,成员变量存在于堆中。因为我们一直在操作堆内存,所以编译器可以保证变量start始终是正确的。

第二个例子,也可修改为

1
2
3
4
5
6
7
8
9
10
11
private volatile boolean run = true;

public void instanceVariableMultithreading() {
executor.execute(() -> {
while (run) {
// do operation
}
});

run = false;
}

这里的run参数加上了volatile,在别的线程执行的过程中,对于lambda也是可见的。
简单地说,当lambda捕获一个实例变量,我们可以认为它捕获的是final变量this

补充:
成员变量(实例变量):存在于对象所在的堆内存,随着对象的建立而建立,随着对象的消失而消失
静态变量:静态变量随着类的加载而存在,随着类的消失而消失,存储在方法区(共享数据区)的静态区


关于工作的一些感想

关于工作的一些感想

正式工作近两年,有幸亲自onw过几个项目,此文将一些想法和做法文字化,作为总结供日后参考和改进
提纲列出来, 慢慢更

阅读更多
NewStart

之前的Django博客维护实在费力, 开个新坑开始写东西

1
new Start();