Fork me on GitHub

lambda表达式中变量的注意事项

lambda表达式中变量的注意事项

前言

The restriction to effectively final variables prohibits access to dynamically-changing local variables, whose capture would likely introduce concurrency problems.

在Java的线程模型中,栈帧中的局部变量是线程私有的,永远不需要进行同步。假如说允许通过匿名内部类把栈帧中的变量地址泄漏出去(逃逸),就会引发非常可怕的后果:一份“本来被Java线程模型规定永远是线程私有的数据”可能被并发访问!哪怕它不被并发访问,栈中变量的内存地址泄漏到栈帧之外这件事本身已经足够危险了,这是Java这种内存安全的语言绝对无法容忍的

这才是本质原因。

举例描述

  • 下面是比较长的答案。对于如下代码:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    例子1:
    public void doSomething() {

    int value = 0;

    IntStream.range(0, 10).forEach(i -> value++ );

    }

    你会得到一个编译错误:Variable used in lambda expression should be final or effectively final。如果从头分析一下跟这个问题相关的知识,事情要从Java 8之前就存在的匿名内部类说起:


    例子2:
    public void doSomething() {

    int value = 0;

    Executors.newSingleThreadExecutor().submit(new Runnable() {


    @Override
    public void run() {undefined
    value++;
    }
    });
    }

    同样,你会得到一个编译错误:Variable is accessed within inner class. Needs to be declared final.
  1. 第一个问题,为什么存在这样的限制?
    要回答这个问题,我们需要首先明白,匿名内部类外面的value和里面的value是同一个内存地址中的数据么?
  • 很明显不是,因为我们都知道,局部变量存在于栈帧的局部变量表中,一旦方法结束,栈帧被销毁,这个变量(这份数据)就不再存在
  • 但是匿名内部类中的value可能在栈帧销毁后继续存在(比如在这个例子中,匿名内部类被提交到了线程池中)。
  • 所以,只有一个可能,在匿名内部类被创建的时候,被捕获的局部变量发生了复制。
    • 如果我们允许在匿名内部类中执行value++操作,带来的后果就是,匿名内部类中的value的拷贝被更新了,但是原先的value不会受到任何影响(因为它可能已经不存在了)——你看上去好像两个value是同一个地址,同一份数据,但是实际上发生了拷贝,和方法调用的值传递如出一辙。这是很可怕的一件事情,它会让你误以为,在匿名内部类中执行value++会改变原先的局部变量value
  1. 这还不是最可怕的。最可怕的是,如果允许匿名内部类修改外面的局部变量,会颠覆掉整个Java线程模型!!!!!!

The restriction to effectively final variables prohibits access to dynamically-changing local variables, whose capture would likely introduce concurrency problems.

  • 在Java的线程模型中,栈帧中的局部变量是线程私有的,永远不需要进行同步。但是,假如说我们通过匿名内部类把栈帧中的变量地址泄漏出去,就会引发非常可怕的后果:一份“本来被Java线程模型规定永远是线程私有的数据”可能被并发访问!!!

3. 解决方法

  1. 因此,在Java 8之前,编译器会强迫你加上一个final关键字:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public void doSomething() {

final int value = 0; // 不声明final不给过编译,你给老子死了这条修改的心吧

Executors.newSingleThreadExecutor().submit(new Runnable() {

@Override

public void run() {

System.out.println(value);

}

});
}
  1. 第二个问题:那为什么Java 8之后我可以不写final了呢?
  • Java 8引入了lambda表达式,我们从此可以非常方便地编写大量的小代码块,但是在捕获外围的局部变量这件事上,lambda表达式和匿名内部类没有任何区别——被捕获的局部变量必须是final的。这就带来了一个问题,继续坚持把局部变量声明成final的话,烦也烦死了。 因此,JLS做出了一个妥协:

  • 假如一个局部变量在整个生命周期中都没有被改变(指向),那么它就是effectively final的——换句话说,不是final,胜似final。这样的局部变量也允许被lambda表达式或者匿名内部类所捕获,不过只能看不能摸——可以读取,但是不能修改。

  1. 下一个问题是,老子就是想在lambda表达式里面改外面的值!你咬我啊!
  • 为什么转换成一个AtomicInteger就可以了呢?这跟线程安全没有半毛钱关系,纯粹是利用了这样一个技巧:AtomicInteger可以当作int的容器。因为它是在堆上被分配的,我们完全没有改变这个局部变量的指向(effectively final成立),就达到了修改其中数据的目的。

开发笔记-07-跨域问题

开发笔记-07-跨域问题

前言

  • 原因:出于浏览器的同源策略限制。同源策略(Sameoriginpolicy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。
    • 可以说Web是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。同源策略会阻止一个域的javascript脚本和另外一个域的内容进行交互。
    • 所谓同源(即指在同一个域)就是两个页面具有相同的协议(protocol),主机(host)和端口号(port)
阅读更多...

ES-02-核心概念

ES-02-核心概念

前言

mark

  • ElasticSearch是面向文档,关系行数据库和ElasticSearch客观对比!一切都是JSON!
Relational DB ElasticSearch
数据库(database) 索引(indices)
表(tables) types <慢慢会被弃用!>
行(rows) documents
字段(columns) fields
阅读更多...

ES-01-简介

ES-01-简介

前言

mark

1
2
3
4
5
ElasticSearch: https://mirrors.huaweicloud.com/elasticsearch/?C=N&O=D
logstash: https://mirrors.huaweicloud.com/logstash/?C=N&O=D
kibana: https://mirrors.huaweicloud.com/kibana/?C=N&O=D
elasticsearch-analysis-ik: https://github.com/medcl/elasticsearch-analysis-ik/releases
cerebro: https://github.com/lmenezes/cerebro/releases
  • Elaticsearch,简称为es,es是一个开源的高扩展分布式全文检索引擎,它可以近乎实时的存储检索数据;本身扩展性很好,可以扩展到上百台服务器,处理PB级别(大数据时代)的数据。

  • es也使用java开发并使用Lucene作为其核心来实现所有索引和搜索的功能,但是它的目的是通过简单的RESTful API来隐藏Lucene的复杂性,从而让全文搜索变得简单。

  • 据国际权威的数据库产品评测机构DB Engines的统计,在2016年1月,ElasticSearch已超过Solr等,成为排名第一的搜索引擎类应用

阅读更多...

kafka-02-流处理

kafka-02-前世今生

前言

官方文档https://kafka.apache.org/

注意:⚠️:此篇与技术大部分无关,可跳过

1. 只是消息引擎吗?

  • 要搞清楚这个问题,我们不可避免地要了解一下 Apache Kafka 的发展历程。有的时候我们会觉得说了解一个系统或框架的前世今生似乎没什么必要,直接开始学具体的技术不是更快更好吗?
  • 其实,不论是学习哪种技术,直接扎到具体的细节中,亦或是从一个很小的点开始学习,你很快就会感到厌烦。为什么呢?因为你虽然快速地搞定了某个技术细节,但无法建立全局的认知观,这会导致你只是在单个的点上有所进展,却没法将其串联成一条线进而扩展成一个面,从而实现系统地学习。
  • 我这么说是有依据的,因为这就是我当初学习 Kafka 的方式。你可能不会相信,我阅读 Kafka 源码就是从 utils 包开始的。
    • 显然,我们不用看源码也知道这玩意是干什么用的,对吧?就是个工具类包嘛,而且这种阅读源码的方式是极其低效的。
    • 就像我说的,我是在一个点一个点地学习,但全部学完之后压根没有任何感觉,依然不了解 Kafka,因为不知道这些包中的代码组合在一起能达成什么效果。所以我说它是很低效的学习方法。
阅读更多...

kafka-01-简介

kafka-01-简介

前言: why learning?

官方文档https://kafka.apache.org/

注意:⚠️:此篇废话较多

  • 你可能会有这样的疑问,我为什么要学习 Kafka 呢?要回答这个问题,我们不妨从更大的视角来审视它,先聊聊我对这几年互联网技术发展的理解吧。
  • 互联网蓬勃发展的这些年涌现出了很多令人眼花缭乱的新技术。当下互联网行业最火的技术当属 ABC 了,即所谓的 AI 人工智能、BigData 大数据和 Cloud 云计算云平台。我个人对区块链技术发展前景存疑,毕竟目前没有看到特别好的落地应用场景,也许在未来几年它会更令人刮目相看吧。
  • 对于数据密集型应用来说,如何应对数据量激增、数据复杂度增加以及数据变化速率变快,是彰显大数据工程师、架构师功力的最有效表征。
  • Kafka 在帮助你应对这些问题方面能起到非常好的效果。就拿数据量激增来说,Kafka 能够有效隔离上下游业务,将上游突增的流量缓存起来,以平滑的方式传导到下游子系统中,避免了流量的不规则冲击。由此可见,如果你是一名大数据从业人员,熟练掌握 Kafka 是非常必要的一项技能。
    • 刚刚所举的例子仅仅是 Kafka 助力业务的一个场景罢了。事实上,Kafka 有着非常广阔的应用场景。不谦虚地说,目前 Apache Kafka 被认为是整个消息引擎领域的执牛耳者,仅凭这一点就值得我们好好学习一下它。
    • 另外,从学习技术的角度而言,Kafka 也是很有亮点的。仅需要学习一套框架就能在实际业务系统中实现消息引擎应用、应用程序集成、分布式存储构建,甚至是流处理应用的开发与部署,听起来还是很超值的吧。

阅读更多...

MyBatisPlus-05-代码自动生成器

MyBatisPlus-05-代码自动生成器

前言

  • dao、pojo、service、controller都是我们自己去编写完成!
  • AutoGenerator 是 MyBatis-Plus 的代码生成器,通过 AutoGenerator 可以快速生成 Entity、Mapper、Mapper XML、Service、Controller 等各个模块的代码,极大的提升了开发效率。

效果对比

  1. 只配置代码的空项目
  1. MyBatis-Plus 自动生成代码之后
阅读更多...

MyBatisPlus-02-主键生成策略及CRUD

MyBatisPlus-02-主键生成策略及CRUD

前言

  • 具体雪花算法可以参考:

  • 雪花算法:

    • snowflake是Twitter开源的分布式ID生成算法,结果是一个long型的ID。其核心思想是:使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID),12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID),最后还有一个符号位,永远是0。可以保证几乎全球唯一!
阅读更多...
  • © 2019-2022 Zhuuu
  • PV: UV:

请我喝杯咖啡吧~

支付宝
微信