lambda表达式引用的本地变量必须是最终变量或实际上的最终变量

博客讲述了如何在不使用Synchronized的情况下,利用AtomicInteger和线程池解决10个线程计算1到100的累加问题。在Java中,由于lambda表达式对变量的限制,需要将循环变量`i`复制为final变量,以避免编译错误。最终代码成功实现了并发计算并得到正确结果5050。

问题描述:最近遇到一个问题,让你用10个线程计算1-100累加的结果,并且不能使用Synchronize。

思路:用AtomicInteger + 线程池来解决。
tip:不能使用volatile关键字(只能保证可见性,不能保证同步性),用的话需要加锁不符题意

代码如下

public class Sum1To100With10Thread {
    //创建十个线程,计算1加到100的结果,然后合并返回public class Add {//使用原子类
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        AtomicInteger sum = new AtomicInteger();
        CountDownLatch countDownLatch = new CountDownLatch(10);//设置计数器初始值为10
        ExecutorService ex = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 10; i++) {
            ex.execute(() -> {
                for (int j = 1; j <= 10; j++) {
                    sum.addAndGet(i * 10 + j);
                }
                countDownLatch.countDown();//执行完sum+之后,countDownLatch数量-1
            });
        }
        countDownLatch.await();//等待计数器归零,然后再向下执行
        System.out.println(sum);
        ex.shutdown();
    }
}

遇到问题:Error:(23, 35) java: 从lambda 表达式引用的本地变量必须是最终变量或实际上的最终变量
在这里插入图片描述
知识点
Lambda表达式规则

  • 只能引用标记了 final 的外层局部变量,这就是说不能在 lambda 内部修改定义在域外的局部变量,否则会编译错误。
  • 局部变量可以不用声明为 final,但是必须不可被后面的代码修改(即隐性的具有 final 的语义)
  • 不允许声明一个与局部变量同名的参数或者局部变量。

解决方法:copy ‘i’ to effectively final temp variable

修改后的代码如下

public class Sum1To100With10Thread {
    //创建十个线程,计算1加到100的结果,然后合并返回public class Add {//使用原子类
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        AtomicInteger sum = new AtomicInteger();//只能用原子类,不能用volatile。如果用volatile得加锁
        CountDownLatch countDownLatch = new CountDownLatch(10);
        ExecutorService ex = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 10; i++) {
            int finalI = i;                                // <--------
            ex.execute(() -> {
                for (int j = 1; j <= 10; j++) {
                    sum.addAndGet(finalI * 10 + j);         //<--------
                }
                countDownLatch.countDown();//执行完sum+之后,countDownLatch数量-1
            });
        }
        countDownLatch.await();//等待计数器归零,然后再向下执行
        System.out.println(sum);
        ex.shutdown();
    }
}

运行结果:5050

最后,欢迎对本题有其他解法的大佬,在评论中分享下思路 ~

<think>好的,我现在需要解释为什么从lambda表达式引用本地变量必须是最终变量实际上最终变量。让我先回忆一下相关的Java知识。 首先,lambda表达式是在Java 8引入的,它允许我们以更简洁的方式表示匿名内部类。但lambda在使用外部变量时有一个限制,就是这些变量必须是final实际上是final的。这背后的原因可能涉及到变量捕获和作用域的问题。 我记得在Java中,匿名内部类和lambda表达式在访问外部变量时,变量必须是final的,这是因为它们是在不同的线程上下文中执行的,可能存在生命周期不一致的问题。如果允许修改外部变量,可能会导致并发问题者数据不一致。 比如,如果在lambda引用了一个外部变量,而这个变量lambda外部被修改了,那么lambda内部看到的值可能是不确定的。为了避免这种情况,Java要求这些变量必须是不可变的,即final实际上final,也就是声明后没有被修改过。 另外,变量捕获的机制可能也是原因之一。lambda表达式捕获的是变量的值,而不是变量本身。如果变量是可变的,那么每次访问时可能需要同步,这增加了复杂性。而使用final实际final的变量,可以确保在捕获时值不会改变,从而保证线程安全。 不过,这里可能还涉及到Java内存模型中的可见性问题。如果变量不是final的,其他线程可能看不到最新的值,而final变量在初始化后对其他线程是可见的,这样lambda使用这些变量时就能保证一致性。 还有一点是,局部变量的生命周期通常与方法执行相关,而lambda可能在方法执行完毕后仍然存在,比如被传递给其他线程存储起来。这时候,如果lambda引用的局部变量已经被销毁,就会有问题。因此,Java通过复制这些变量的值到lambda中来解决,而为了确保复制的值不会变化,必须要求它们是final的。 总结一下,原因可能包括:线程安全、避免数据不一致、变量生命周期管理以及内存可见性。Java通过强制变量为final实际final来保证lambda表达式在使用外部变量时的正确性和稳定性。</think>在Java中,当lambda表达式引用外部方法中的局部变量时,该变量必须是`final``实际上最终变量(effectively final)`。这是由以下原因和机制决定的: --- ### 1. **变量捕获的机制** Lambda表达式会**捕获变量的值**而非变量本身。当lambda被创建时,Java会将局部变量的当前值复制一份到lambda的上下文中。如果允许修改外部变量lambda内部捕获的值可能与外部的实际值不一致,导致不可预测的行为。 --- ### 2. **线程安全问题** Lambda可能在另一个线程中执行,而外部方法可能在原线程中修改局部变量。如果变量不是final,这种**并发修改**会导致线程安全问题(如数据竞争内存可见性问题)。强制变量为final可以确保所有线程看到的值一致。 --- ### 3. **局部变量的生命周期** 局部变量存在于栈内存中,当方法执行结束时,栈帧被销毁,局部变量也随之消失。但lambda可能被传递到其他作用域(如另一个线程对象)并长期存活。通过**复制final变量的值**,Java确保lambda可以安全地访问这些值,即使原变量已不存在。 --- ### 4. **设计一致性** 匿名内部类访问外部局部变量时也要求变量为final。Lambda延续了这一设计,以保持语言特性的一致性。 --- ### 示例说明 ```java public class LambdaExample { public static void main(String[] args) { int count = 0; // 实际上最终变量(从未被修改) Runnable r = () -> System.out.println(count); // 合法 // 如果取消注释以下代码,会编译报错: // count++; } } ``` - ✅ **合法**:`count`未被修改,是实际上最终变量。 - ❌ **非法**:如果尝试修改`count`,则不再是最终变量lambda无法引用它。 --- ### 如何绕过限制? 如果需要修改外部变量,可以使用以下方法: 1. **使用数组对象包装**(但需注意线程安全): ```java int[] counter = {0}; Runnable r = () -> counter[0]++; ``` 2. **使用原子类**(如`AtomicInteger`): ```java AtomicInteger atomicCount = new AtomicInteger(0); Runnable r = () -> atomicCount.incrementAndGet(); ``` --- ### 总结 | 要求 | 原因 | |--------------------|----------------------------------------------------------------------| | 变量必须为final实际final | 确保线程安全、避免生命周期冲突、保持变量值的确定性、与匿名内部类行为一致。 | 通过这种限制,Java在简化lambda表达式的同时,保证了代码的健壮性和可维护性。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值