一、现象
Java中,不通过Lambda函数入参传入的参数,我们成为函数的自由变量,在Lambda函数中使用自由变量的动作叫捕获。Lambda函数捕获的自由变量,必须是逻辑不变的(不可变或事实上无逻辑修改),通常用final修饰。
二、猜测
-
Lambda函数只能捕获指派给他们的自由变量一次,这个变量的值,入栈时就已决定了;(JVM内存模型,局部变量存储在线程栈中,实例变量存储在堆中)。
-
在Lambda函数中修改主体、自由变量都会引入并发安全问题,这与函数式编程的并发控制目标相悖。
class Go {
private String name;
private static int id;
public static void main(String[] args) {
int count = 0;
Go go = new Go();
new Thread(() -> {
System.out.println(id++); // 1.修改-静态变量-成功捕获
System.out.println(go.name = "yangjx"); //2.修改-实例变量-成功捕获
System.out.println(count); //3.不修改修改-外层方法的局部变量-成功捕获
count++; //4.修改-外层方法的局部变量-编译报错
}).start();
}
}
三、结论
-
Lambda函数对静态变量、实例变量的捕获,可以看作分别对Class对象、实例this的捕获,它俩都在堆上分配,堆上数据是线程共享的,所以不会发生堆上变量在Lambda函数访问其之前被回收的问题;
-
而局部变量则是在栈上分配的,可能会发生局部变量的分配逻辑和Lambda函数不在同一个线程的情况,若分配逻辑已经执行完,其栈上的变量将被回收,此时Lambda函数再去访问势必会出错。Java中Lambda函数对局部变量的捕获过程,其实是拷贝了一次变量值的副本,为了保证并发时的逻辑正确性,就规定Lambda函数捕获的局部变量必须是不可变的,这样各个副本才是逻辑一致的。
-
同时,处于函数式编程在并发上的目标,这个限制不鼓励在Lambda函数内部改变外部变量,这与传统的命令式编程有所区别。