Java核心 -- Vector + ArrayList + LinkedList
概述
Vector、ArrayList、LinkedList都实现了java.util.List接口,即有序集合
Vector是Java早期提供的线程安全的动态数组,同步有一定的额外开销
Vector内部使用对象数组来保存数据,可以自动扩容(创建新的数组,拷贝原有数组的数据)
ArrayList是应用更加广泛的动态数组,本身并不是线程安全的,性能比Vector要好很多
Vector的自动扩容是增加1倍,而ArrayList的自动扩容是增加50%
LinkedList是双向链表,不是线程安全的,也不需要动态调整容量
适用场景
Vector和ArrayList都是动态数组,其内部元素是以数组的形式存储的,非常适合随机访问的场合
除了在尾部插入和删除元素,性能都比较差,因为要移动后续所有的元素
LinkedList进行元素的插入和删除操作会高效很多,但随机访问性能要比动态数组差
集合
容器包括集合和Map,_Map并不是真正的集合_
List:有序结合
Set:不允许重复元素(equals判断)
Queue/Deque:标准队列,支持FIFO或者LIFO
每种集合的通用逻辑,都被抽 ...
Java并发 -- Lock
管程
并发领域的两大核心问题:互斥 + 同步
互斥:同一时刻只允许一个线程访问共享资源
同步:线程之间的通信和协作
JUC通过Lock和Condition两个接口实现管程,其中Lock用于解决互斥问题,而Condition用于解决同步问题
再造管程的理由
Java语言对管程的原生实现:synchronized
在Java 1.5中,synchronized的性能不如JUC中的Lock,在Java 1.6中,synchronized做了很多的性能优化
再造管程的核心理由:synchronized无法破坏不可抢占条件(死锁的条件之一)
synchronized在申请资源的时候,如果申请不到,线程直接进入阻塞状态,也不会释放线程已经占有的资源
更合理的情况:占用部分资源的线程如果进一步申请其它资源的时,如果申请不到,可以主动释放它所占有的资源
解决方案
能够响应中断
synchronized:持有锁A的线程在尝试获取锁B失败,进入阻塞状态,如果发生死锁,将没有机会唤醒阻塞线程
如果处于阻塞状态的线程能够响应中断信号,那阻塞线程就有机会释放曾经持有的锁A
支持超时
如果线程在一段时间内没有获得锁,不是进 ...
Java核心 -- int + Integer
包装类
Integer是int对应的包装类,里面有一个int类型的字段存储数据,并提供了基本的操作
在Java 5,引入了自动装箱和自动拆箱(boxing/unboxing),Java可以根据上下文,自动进行转换
在Java 5,还引入了值缓存(静态工厂方法valueOf),默认缓存范围为**-128 ~ 127**
Boolean,缓存Boolean.TRUE/Boolean.FALSE
Short,缓存**-128 ~ 127**
Byte,数值有限,全部缓存
Character,缓存\u0000 ~ \u007F
自动装箱 + 自动拆箱12Integer integer = 1;int unboxing = integer++;
1234561: invokestatic // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;8: invokevirtual // Method java/lang/Integer.intValue:()I11: iconst_112: iadd13: invokes ...
Java核心 -- 动态代理
编程语言分类
动态类型和静态类型:语言类型是运行时检查,还是编译期检查
强类型和弱类型:为不同类型的变量赋值时,是否需要进行显式的类型转换
Java是静态的强类型语言,但提供了类似反射等机制,因此也具备了部分动态类型语言的能力
反射
反射机制是Java语言提供的一种基础功能,赋予程序在运行时自省的能力
通过反射可以直接操作类或者对象
获取某个对象的类定义
获取类声明的属性和方法
调用方法或者构造函数
运行时修改类定义
setAccessible
AccessibleObject.setAccessible(boolean flag):可以在运行时修改成员的访问限制
setAccessible的应用遍布在日常开发、测试、依赖注入等框架中
在O/R Mapping框架中,为一个Java实体对象,运行时自动生成getter/setter方法
绕过API的访问控制,来调用内部API
动态代理
动态代理是一种方便运行时动态构建代理、动态处理代理方法调用的机制
很多场景都是利用类似的机制来实现的,例如用来包装RPC调用和AOP
实现动态代理的方式
JDK自身提供的动态代理,主要利用反 ...
Java并发 -- 面向对象
封装共享变量
面向对象思想里面有一个很重要的特性:_封装_
封装:将属性和实现细节封装在对象内部,外部对象只能通过目标对象的公共方法来间接访问目标对象的内部属性
利用面向对象思想写并发程序的思路:将共享变量作为对象属性封装在内部,对所有公共方法制定_并发访问策略_
Countervalue为共享变量,作为Counter的实例属性,将get()和addOne()声明为synchronized方法,Counter就是一个线程安全的类
1234567891011public class Counter { private long value; public synchronized long get() { return value; } public synchronized long addOne() { return ++value; }}
不可变的共享变量
实际场景中,会有很多共享变量,如银行账户有卡号、姓名、身份证、信用额度等,其中卡号、姓名和身份证是不会变的
对于不可变的共享变 ...
Java并发 -- 局部变量
斐波那契数列1234567891011int[] fibonacci(int n) { // 创建结果数组 int[] r = new int[n]; // 初始化第一、第二个数 r[0] = r[1] = 1; // ① // 计算 2..n for (int i = 2; i < n; i++) { r[i] = r[i - 2] + r[i - 1]; } return r;}
方法调用过程123int a = 7;int[] b = fibonacci(a);int[] c = b;
当调用fibonacci(a)的时候,CPU需要先找到方法fibonacci()的地址,然后跳转到这个地址去执行代码
最后CPU执行完fibonacci()方法之后,要能够返回,需要找到调用fibonacci()方法的下一条语句的地址
即int[] c = b;然后跳转到这个地址去执行
栈寄存器
CPU支持一种栈结构,与方法调用相关,称为调用栈,Java虽然靠JVM解释执行,但方法调用也 ...
Java并发 -- 线程数量
多线程的目的
使用多线程的目的是为了_提高程序性能_
度量程序性能的核心指标:_延迟 + 吞吐量_
延迟:发出请求到收到响应的时间,延迟越短,意味着程序执行得越快,性能越好
吞吐量:在单位时间内能处理请求的数量,吞吐量越大,意味着程序能处理的请求越多,性能越好
同等条件下,延迟越短,吞吐量越大,但两者隶属于不同的维度(一个时间维度,一个空间维度),并不能互相转换
提升程序性能:_降低延迟,提高吞吐量_
多线程的应用场景
要达到降低延迟,提高吞吐量的目的,有两个方向:一个是优化算法,一个是_将硬件的性能发挥到极致_
前者属于算法范畴,后者与并发编程息息相关
在并发编程领域,_提高性能本质上就是要提高硬件的利用率_,主要是提升IO利用率和CPU利用率
操作系统解决硬件利用率问题的对象往往是单一的硬件设备,而并发编程要解决CPU和IO设备综合利用率的问题
综合利用率假设程序按照CPU计算和IO操作交叉执行的方式运行,而且CPU计算和IO操作的耗时是1:1
单线程
单线程时,执行CPU计算的时候,IO设备空闲,执行IO操作时,CPU空闲,所以CPU利用率和IO设备的利用率都是50%
两线程
...
Java并发 -- 线程生命周期
通用的线程生命周期
初始状态
线程已经被创建,但还不允许分配CPU执行
该状态属于编程语言所特有,仅仅在编程语言层面被创建,在操作系统层面,真正的线程还没有创建
可运行状态
线程可以分配CPU执行,该状态下真正的操作系统线程已经被创建
运行状态
当有空闲的CPU时,操作系统会将其分配给处于可运行状态的线程,被分配到CPU的线程的状态就转换为运行状态
休眠状态
处于运行状态的线程如果调用一个阻塞的API或者等待某个事件,那么线程状态就会切换为休眠状态
切换为休眠状态的同时会释放CPU使用权,_处于休眠状态的线程永远没有机会获得CPU使用权_
当等待的事件出现后,线程就会从休眠状态切换到可运行状态
终止状态
线程执行完或者出现异常就会进入终止状态,处于终止状态的线程不会切换到其它状态
进入终止状态意味着线程生命周期的结束
简化合并
通用的线程生命周期里的5种状态在不同的编程语言会有简化合并
Java把可运行状态和运行状态合并了
这两个状态对操作系统调度层是有价值的,但JVM把线程调度交给了操作系统处理,JVM并不关心这两个状态
JVM同时也细化了休眠状态
Java线程的生命周期 ...
Java核心 -- 字符串
String
String是Java语言非常基础和重要的类,提供了构造和管理字符串的各种基本逻辑,是典型的Immutable类
String是Immutable类的典型实现,原生的保证了基础线程安全,因为无法对它内部数据进行任何修改
被声明为final class,由于String的不可变性,类似拼接、裁剪字符串等动作,都会产生新的String对象
由于字符串操作的普遍性,所以相关操作的效率往往对应用性能有明显影响
StringBuffer
StringBuffer是为了解决拼接产生太多中间对象的问题而提供的一个类
StringBuffer本质是一个线程安全的可修改字符串序列,保证了线程安全,但也带来了额外的性能开销
StringBuffer的线程安全是通过把各种修改数据的方法都加上synchronized关键字实现的
这种方式非常适合常见的线程安全类的实现,不必纠结于synchronized的性能
过早的优化是万恶之源,可靠性、正确性和代码可读性才是大多数应用开发的首要考虑因素
StringBuilder
StringBuilder在能力上和StringBuffer没有本质区别,但去掉了线程 ...
Java并发 -- 管程
概述
Java语言在1.5之前,唯一提供的并发原语是管程
在Java 1.5提供的JUC包中,也是以管程技术为基础的
管程是一把解决并发问题的万能钥匙
管程
在Java 1.5之前,仅仅提供synchronized关键字和wait/notify/notifyAll方法
Java采用的是管程技术,synchronized关键字以及wait/notify/notifyAll方法都是管程的组成部分
管程和信号量是等价的(即用管程能实现信号量,用信号量也能实现管程),但管程更容易使用,所以Java选择了管程
Monitor,在Java领域会翻译成监视器,在操作系统领域会翻译成管程
管程:_管理共享变量以及对共享变量的操作过程,让它们支持并发_
对应Java领域:管理类的成员变量和成员方法,让这个类是线程安全的
MESA模型
在管程的发展史上,先后出现了三种不同的管程模型,分别是:Hasen模型、Hoare模型和MESA模型
现在广泛应用的是MESA模型,Java管程的实现也参考了MESA模型
管程可以解决并发领域的两大核心问题:_互斥+同步_
互斥:在同一时刻只允许 ...