首页 文章 精选 留言 我的

精选列表

搜索[笔记工具],共10000篇文章
优秀的个人博客,低调大师

总结笔记(一) - Java 泛型总结

泛型总结 泛型是什么? 一句话说就是类型参数化。什么意思呢?参数化的意思就是我们在定义的时候不知道具体的值,我们在到我们实际运行的时候才知道具体的值。类型参数化就是具体类型在定义的时候不知道,在实际运行的时候是确定的某一个类型。 Java 是如何实现泛型的? 泛型是很多高级语言都有的特性。根据定义,泛型在运行时表示同一个类型,我们比较容易想到 List<A> 和 List<B> 用 2 个不同的 Class 表示,这个是可行的,但是 Java 由于需要兼容支持旧的代码,而且在推出泛型前就提供了容器类,这种方式(List<A> 和 List<B> 用 2 个不同的 Class)无法兼容以前的老代码,所以这个实现方法不适用。所以 Java 大佬们想了另外一种方式来实现泛型,这种方式就是类型擦除。 什么是泛型的类型擦除呢? 类型擦除就是在实际生成字节码的时候,编译器源码里面定义的 List<A> 变成了 List<Object>,源码里面定义的 A Class 被擦除了,变成了 Object,同时在使用的时候,会强制类型转换,把取出来的 object 转成 A 的实例去使用。这就是类型擦除。 初步看,泛型擦除好像是没什么大的问题,但是仔细想想,在强制类型转换的时候,由于会丢掉类型的一些信息,会导致一些不符合预期的事情。比如有个基类 A,和它的两个子类 B 和 C ,然后我们有下面的一段代码。 List<A> listA = new ArrayList<A>(); listA.add(new B()); // 错误的, 第二行代码是不符合预期的,因为 listA 里面期望放的是 A 而不是 B。 但是这个好像不太符合预期,我们有时候希望子类是可以放进容器里面的。但是如果支持这个操作的话,会发生什么呢?取出来来的是 B 还是 C ?如果不能明确,那么就没有实现“泛型”。 为了解决这个问题, Java 大佬们想了个方法,提出了一些通配符来解决这些问题。 泛型的通配符 ?、extends 和 super 在理解通配符之前,我们需要知道的是,通配符的发明是为了解决什么问题?至少要解决的一个问题是:容器里面放进去的是什么,取出来的就是什么。 这个问题,其实分两步,放进去,是说放进去同一种类型的东西。取出来,是说取出同一种类型的东西。或者说,用到通配符的地方应该是在不同的地方,一个地方把数据写到容器,另外一个地方把数据从容器拿出来,如果实在同一个代码块里写入和读取数据到同一个容器,应该是知道具体类型的,是不需要用到通配符的。 ? 通配符 ? 通配符称为无限通配符,表示不确定或者不关心类型。 extends 通配符 一般称为上界通配符,表示的意思是:取值范围为 (某个类的子类, 某个类]。再想想我们之前说的,通配符要解决的问题?放进去的是什么,取出来的就应该是什么。放数据和取数据应用在不同的场景。如果我们在同一个场景,就不需要用到通配符了,因为类型是已知的。 通过上面的表述,容易推断出来 <? extends E> 的集合只能往外拿数据,因为取出来的一定是 E ,但是放进去的不知道是什么,可能是 E ,也可能是 E 的子类,如果允许往集合里面放东西,就不能保证放进去的是什么,拿出来的就是什么了。因为只能保证拿出来的是 E 。 这个特性也叫做协变。 super 通配符 一般称为下界通配符,表示的意思是:取值范围为 [某个类,这个类的父类)。结合上面小节的解释,可以推断出 <? super S> 的集合只能往里面放数据,而不能从里面拿东西,为什么呢?因为 <? extends E> 解决的就是拿出来的问题啊,所以这个解决的就是放进去的问题啊,囧。里面放的是下限或者下限的子类。 这个特性也叫做逆变。 小结 通配符与一个规则, PE-CS。 PE 简单的说,当只想从集合中获取元素,请把这个集合看成生产者,请使用<? extends T>,这就是 Producer extends 原则,PECS原则中的PE部分。集合生产元素后,就可以拿过来用了。 CS 简单的说,当你仅仅想增加元素到集合,把这个集合看成消费者,请使用<? super T>。这就是 Consumer super 原则,PECS原则中的CS部分。集合消费元素,这样就可以往里面放了 同时作为生产者和消费者的情况不存在,因为你可以指定具体的泛型。 参考资料: 深入理解 Java 泛型

优秀的个人博客,低调大师

JVM笔记八-堆参数调优

JVM垃圾收集器(Java Garbage Collection)。本教程均在JDK1.8+HotSpot为例来讲解的. 先来看看Java7的: 编辑 再来看看Jva8的 编辑 从上图中我们可以看出,java8之后换成了元空间。那么怎么证明,堆区是有新生代、永久代、元空间三部分组成的呢?OOM这个错误我们都熟悉,那么怎么手动制造出一个OOM呢?如果16G的物理内存,JVM堆内存能够分到多少G的内存空间呢?我们带着这些疑问来一起学习吧 在Java8中,永久带已经被移除了,被一个称为元空间的区域所取代。元空间的本质和永久带类似。 元空间与永久带之间最大的区别在于: 永久带使用的是JVM的堆内存空间,但是java8以后的元空间并不是虚拟机中的空间,而是使用了本机的物理内存空间的。 因此,默认情况下,元空间大小仅受到本地内存大小的限制。类的元数据放入native memory,字符串常量池和静态类变量存放在java堆区中。这样可以加载多少类的元数据,就不在由MaxPermSize控制了,而是由系统的实际可用空间来控制。 Java默认堆区空间大小是物理内存的六十四分之一(1/64).默认最大堆空间是物理内存的1/4 想要对JVM调优的话,就先要知道自己的家底。默认情况下,当前服务的JVM最大和最小内存是多少呢?怎么查看呢? 我们可以使用Runtime这个类来查看。具体代码如下: 编辑 运行结果: 编辑 来看看凯哥本子上物理内存大小: 编辑 可以看到是24GB。 从打印的结果,我们看知道,凯哥本子上的JVM最大内存是5.4个G。也就是大约等于物理内存的1/4 JVM最小内存就是:368。大约是物理内存的1/64. 是不是证明了JVM默认堆内存最大值占用物理内存的1/4,最小值占用物理内存的1/64。没有忽悠,没有骗人吧。 编辑 看到了吗?totoalMemory方法和maxMemory方法都是native的。在前面,我们讲解JVM体系图的时候,讲解了native关键字修饰的方法,这里就不赘述了。 代码证明堆内存空间就是新生代、老年代、元空间三个区域: 在idea中通过VM options参数来操作 编辑 找到需要修改的类,然后在VM options,添加参数。如下图: 编辑 输入如下参数:-Xms1024m -Xmx1024m -XX:+PrintGCDetails 编辑 堆内存调优参数说明: 命令 描述 -Xms 设置初始分配大小,默认物理内存的1/64 -Xmx 最大分配内存,默认为物理内存的1/4 -XX:+PrintGCDetails 输出详细的GC处理日志 修改好了之后,重新运行程序,我们看看控制台打印的信息: 编辑 修改后,我们发现堆内存的最大和最小的值是相等的。需要说明一点,在生产环境中,我们最好也把最大和最小值设置一样。这样可以减少空间差距切换从而影响了程序的稳定健壮性。 在上图2部分区域,就是打印出了jvm的详细信息。我们可以明显的看到如下几个数据: PSYoungGen、ParOldGen、Metaspace这三个区域,正好就是我们之前文章说的,新生代、老年代、元空间这三个区域。这是逻辑上区分的。 在物理上区分是2个,分别是新生代和老年代,怎么证明呢? 编辑 还记得我们参数设置的是1024m吧。把新生代和老年代的total相加,是不是就是打印出最大和最小堆内存的值? 再来看看新生代和老年代空间占用比例:305664/699392是不是于等于1/2。 怎么证明新生代是有伊甸园区、from区、to区三部分组成呢?三部分占用比例怎么证明是8/1/1呢?请看下图: 编辑 是不是有三个区域。占用空间分别是:26214/43520/43520.是不是就是8/1/1? 现在再回过头,来看看堆内存,是不是更清晰了。 编辑 通过修改堆参数,模拟出OOM问题 思路: 写个while(true)死循环,通过设置JVM的参数,设置小一点。比如8M,然后执行就会出现OOM。或者new一个字节数组,大于配置的参数就可以。比如设置的堆内存大小是8M,那么byte[] bytes =new byte[10*1024*1024]; //10M的对象。一定会OOM 编辑 -Xms8m -Xmx8m -XX:+PrintGCDetails 编辑 运行后,查看控制台打印信息. 编辑 是不是看到了熟悉的 [Full GC (Allocation Failure) Exception in thread "main" java.lang.OutOfMemoryError: Java heap space。 下一篇文章预告:GC收集日志信息分析。欢迎大家和凯哥(凯哥java:kaigejava)一起继续学习。

优秀的个人博客,低调大师

再谈 Send 与 Sync | Rust学习笔记

Send 与 Sync 可能是Rust多线程以及异步代码种最常见到的约束。在前面一篇讨论多线程的文章中介绍过这两个约束的由来。但是,真正书写比较复杂的代码时,还是会经常遇到编译器的各种不配合。这里借用我的同事遇到的一个问题再次举例谈一谈 Send 与 Sync 的故事。 基本场景 C/C++中不存在Send/Sync的概念,数据对象可以任意在多线程中访问,只不过需要程序员保证线程安全,也就是所谓“加锁”。而在Rust中,由于所有权的设计,不能直接将一个对象分成两份或多份,每个线程都放一份。一般地,如果一份数据仅仅子线程使用,我们会将数据的值转移至线程中,这也是Send的基础含义。因此,Rust代码经常会看到将数据clone(),然后move到线程中: let b = aa.clone(); thread::spawn(move || { b... }) 假如,数据需要在多线程共享,情况会复杂一些。我们一般不会在线程中直接使用外部环境变量引用。原因很简单,生命周期的问题。线程的闭包要求‘static,这会与被借用的外部环境变量的生命周期冲突,错误代码如下: let bb = AA::new(8); thread::spawn( || { let cc = &bb; //closure may outlive the current function, but it borrows `bb`, which is owned by the current function }); 包裹一个Arc可以解决这个问题,Arc恰好就是用来管理生命周期的,改进后的代码如下: let b = Arc::new(aa); let b1 = b.clone(); thread::spawn(move || { b1... }) Arc提供了共享不可变引用的功能,也就是说,数据是只读的。如果我们需要访问多线程访问共享数据的可变引用,即读写数据,那么还需要在原始数据上先包裹Mutex<T>,类似于RefCell<T>,提供内部可变性,因此我们可以获取内部数据的&mut,修改数据。当然,这需要通过Mutex::lock() 来操作。 let b = Arc::new(Mutex::new(aa)); let b1 = b.clone(); thread::spawn(move || { let b = b1.lock(); ... }) 为什么不能直接使用RefCell完成这个功能?这是因为RefCell不支持 Sync,没办法装入Arc。注意Arc的约束: unsafe impl<T: ?Sized + Sync + Send> Send for Arc<T> {} 若 Arc<T>是Send,条件是 T:Send+Sync。RefCell不满足 Sync,因此 Arc<RefCell<>> 不满足Send,无法转移至线程中。错误代码如下: let b = Arc::new(RefCell::new(aa)); let b1 = b.clone(); thread::spawn(move || { ^^^^^^^^^^^^^ `std::cell::RefCell<AA<T>>` cannot be shared between threads safely let x = b1.borrow_mut(); }) 异步代码:跨越 await 问题 如上所述,一般地,我们会将数据的值转移入线程,这样只需要做正确的 Send和Sync 标记即可,很直观,容易理解。典型的代码如下: fn test1<T: Send + Sync + 'static>(t: T) { let b = Arc::new(t); let bb = b.clone(); thread::spawn( move|| { let cc = &bb; }); } 根据上面的分析,不难推导出条件 T: Send + Sync + 'static 的来龙去脉:Closure: Send + 'static ⇒ Arc<T>: Send + ’static ⇒ T: Send + Sync + 'static。 然而,在异步协程代码中有一种常见情况,推导过程则显得比较隐蔽,值得说道说道。考察以下代码: struct AA<T>(T); impl<T> AA<T> { async fn run_self(self) {} async fn run(&self) {} async fn run_mut(&mut self) {} } fn test2<T: Send + 'static>(mut aa: AA<T>) { let ha = async_std::task::spawn(async move { aa.run_self().await; }); } test2 中,限定 T: Send + ‘static,合情合理。async fn 生成的 GenFuture 要求 Send + ‘static,因此被捕获置入 GenFuture 匿名结构中的 AA 也必须满足 Send + ‘static,进而要求AA 泛型参数也满足Send + ‘static。 然而,类似的方式调用 AA::run() 方法,编译失败,编译器提示 GenFuture 不满足 Send。代码如下: fn test2<T: Send + 'static>(mut aa: AA<T>) { let ha = async_std::task::spawn(async move { ^^^^^^^^^^^^^^^^^^^^^^ future returned by `test2` is not `Send` aa.run().await; }); } 原因在于,AA::run()方法的签名是 &self,所以run()是通过 aa 的不可变借用 &AA 来调用。而run()又是一个异步方法,执行了await,也就是所谓的&aa 跨越了 await,故而要求GenFuture匿名结构除了生成aa之外,还需要生成 &aa,示意代码如下: struct { aa: AA aa_ref: &AA } 正如之前探讨过,生成的 GenFuture需要满足 Send,因此 AA 以及 &AA 都需要满足 Send。而&AA满足 Send,则意味着 AA 满足 Sync。这也就是各种 Rust教程中都会提到的那句话的真正含义: 对于任意类型 T,如果 &T是 Send ,T 就是 Sync 的 之前出错的代码修改为如下形式,增加 Sync标记,编译通过。 fn test2<T: Send + Sync + 'static>(mut aa: AA<T>) { let ha = async_std::task::spawn(async move { aa.run().await; }); } 另外,值得指出的是上述代码中调用 AA::run_mut(&mut self) 不需要 Sync 标记: fn test2<T: Send + 'static>(mut aa: AA<T>) { let ha = async_std::task::spawn(async move { aa.run_mut().await; }); } 这是因为 &mut self 并不要求 T: Sync。参见以下标准库中关于Sync定义代码就明白了: mod impls { #[stable(feature = "rust1", since = "1.0.0")] unsafe impl<T: Sync + ?Sized> Send for &T {} #[stable(feature = "rust1", since = "1.0.0")] unsafe impl<T: Send + ?Sized> Send for &mut T {} } 可以看到,&T: Send 要求 T: Sync,而 &mut T 则 T: Send 即可。 总结 总而言之,Send约束在根源上是由 thread::spawn() 或是 task::spawn() 引入的,因为两个方法的闭包参数必须满足 Send。此外,在需要共享数据时使用Arc<T>会要求 T: Send + Sync。而共享可写数据,需要Arc<Mutex<T>>,此时 T: Send 即可,不再要求Sync。 异步代码中关于 Send/Sync 与同步多线程代码没有不同。只是因为GenFuture 的特别之处使得跨越 await 的变量必须是 T: Send,此时需要注意通过 T 调用异步方法的签名,如果为 &self,则必须满足 T:Send + Sync。 最后,一点经验分享:关于 Send/Sync 的道理并不复杂,更多时候是因为代码中层次比较深,调用关系复杂,导致编译器的错误提示很难看懂,某些特定场合编译器可能还会给出完全错误的修正建议,这时候需要仔细斟酌,追根溯源,找到问题的本质,不能完全依靠编译器提示。 作者:谢敬伟,江湖人称“刀哥”,20年IT老兵,数据通信网络专家,电信网络架构师,目前任Netwarps开发总监。刀哥在操作系统、网络编程、高并发、高吞吐、高可用性等领域有多年的实践经验,并对网络及编程等方面的新技术有浓厚的兴趣。 深圳星链网科科技有限公司(Netwarps),专注于互联网安全存储领域技术的研发与应用,是先进的安全存储基础设施提供商,主要产品有去中心化文件系统(DFS)、企业联盟链平台(EAC)、区块链操作系统(BOS)。 微信公众号:Netwarps

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册