Java中Lambda函数局部变量不可变

一、现象

Java中,不通过Lambda函数入参传入的参数,我们成为函数的自由变量,在Lambda函数中使用自由变量的动作叫捕获。Lambda函数捕获的自由变量,必须是逻辑不变的(不可变或事实上无逻辑修改),通常用final修饰。

二、猜测

  1. Lambda函数只能捕获指派给他们的自由变量一次,这个变量的值,入栈时就已决定了;(JVM内存模型,局部变量存储在线程栈中,实例变量存储在堆中)。

  2. 在Lambda函数中修改主体、自由变量都会引入并发安全问题,这与函数式编程的并发控制目标相悖。

最底层的本质是:Java在语义上是【引用传递】,但实现上是【值传递】,传入lambda函数的局部变量,看起来是传的变量的引用符号,实际传的是变量对象地址,若引用符号的指针发生移动,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();
    }
}

三、结论

  1. Lambda函数对静态变量、实例变量的捕获,可以看作分别对Class对象、实例this的捕获,它俩都在堆上分配,堆上数据是线程共享的,所以不会发生堆上变量在Lambda函数访问其之前被回收的问题;

  2. 而局部变量则是在栈上分配的,可能会发生局部变量的分配逻辑和Lambda函数不在同一个线程的情况,若分配逻辑已经执行完,其栈上的变量将被回收,此时Lambda函数再去访问势必会出错。Java中Lambda函数对局部变量的捕获过程,其实是拷贝了一次变量值的副本,为了保证并发时的逻辑正确性,就规定Lambda函数捕获的局部变量必须是不可变的,这样各个副本才是逻辑一致的。

  3. 同时,出于对函数式编程在并发上的“不可变数据结构”,这个限制不鼓励在Lambda函数内部改变外部变量,这与传统的命令式编程有所区别。

posted @ 2022-01-25 12:31  JaxYoun  阅读(561)  评论(0编辑  收藏  举报