数据类型
内置类型——基本数据类型
名称 | 大小 | 默认值 | 取值说明 |
---|---|---|---|
boolean | 1bit | false |
布尔值,作二元判断,true 或者false |
byte | 8bit | 0x00 |
有符号整数,范围-128 ~ 127 ,转换成int时需注意高位补齐 |
short | 16bit | 0 |
有符号整数,范围-32768 ~ 32767 |
char | 16bit | \u0000 |
Unicode字符,少数字符使用2个char 表示 |
int | 32bit | 0 |
有符号整数,范围-2^31 ~ (2^31 - 1) |
float | 32bit | 0.0F |
浮点数,范围1.4E-45~3.4028235E38 |
long | 64bit | 0L |
有符号整数,范围-2^63 ~ (2^63 - 1) |
double | 64bit | 0.0D |
浮点数,范围4.9E-324~1.7976931348623157E308 |
ps:浮点数有精度,要不失真使用java.math.BigDecimal
和java.math.BigInteger
扩展类型——引用数据类型
形如:
String str = "I am a String !";
三大特性
面向对象编程有三大特性:
- 封装
- 继承
- 多态
类访问权限
同一个类 | 同一个包 | 不同包的子类 | 不同包的非子类 | |
---|---|---|---|---|
private | ✔️ | |||
default | ✔️ | ✔️ | ||
protected | ✔️ | ✔️ | ✔️ | |
public | ✔️ | ✔️ | ✔️ | ✔️ |
多态
所谓多态意指相同的消息给予不同的对象会引发不同的动作称之。
——维基百科
存在的必要条件:
- 继承
- 重写
- 向上转型(父类引用指向子类对象)
实现方式:
- 重写
- 接口
- 抽象类和抽象方法
比如:List<String> list = new ArrayList<>)();
重载(Overload)、重写(Override)
重载(Overload)指的是一个类里面有同名方法,其参数不同,除了参数要独一无二外没什么特殊限制。
重写(Override)指的是子类对父类方法的实现进行重新编写。
区别 | 重载(Overload) | 重写(Override) |
---|---|---|
参数列表 | 必须修改 | 不可修改 |
返回类型 | 可以修改 | 不可修改 |
异常 | 可以修改 | 可以减小异常范围(子类异常或无异常) |
访问权限 | 可以修改 | 可以降低访问限制(如protected–>public) |
Java内存模型
Java内存模型的主要目标是定义程序中各个变量(不包括局部变量,因为局部变量是线程私有的,不会被共享,不存在竞争问题)的访问规则,即在虚拟机中将变量存储到内存和从内存中取出变量这样的底层细节。
- 所有变量都存储在主内存(Main Memory)中
- 每条线程有自己的工作内存(Working Memory),保存了该线程用到的变量的主内存副本拷贝(不会整个对象拷贝)
- 线程对变量的所有操作(读取、赋值等)都必须在工作内存中进行,而不能直接读写主内存中的变量
- 不同线程间不能直接访问对方工作内存中的变量,线程间的变量值得传递需要通过主内存来完成
Object方法
Java是单根继承结构,有一个终极基类
Object
,它的存在保证所有对象都具备某些功能(具有一个共同接口),使得垃圾回收器的实现变得容易。
方法 | 作用 |
---|---|
getClass | 获得运行时对象的类型 |
hashCode | 计算对象的hash值 |
equals | 判一个对象是否与自身相等 |
toString | 将对象表示为字符串,在一些字符串操作的地方会自动调用 |
notify | (当该对象作为锁时可用)唤醒该对象监视下的一个等待的线程 |
notifyAll | (当该对象作为锁时可用)唤醒该对象监视下的所有等待的线程 |
wait | (当该对象作为锁时可用)线程进入等待状态,可以设置等待时长 |
clone | 复制对象,需要实现Cloneable接口 |
finalize | 对象终结时候执行的方法 |
sleep、notify、wait
sleep(静态方法) | notify | wait | |
---|---|---|---|
所属类 | Thread | Object | Object |
作用 | 程序停止一定时间(传入参数),让给其他程序执行 | 唤醒一个等待的线程,不确定是哪个 | 进入等待状态直到被唤醒,若带参数则会自动唤醒 |
锁的持有情况 | 不变 | 不变 | 放弃 |
String、StringBuffer、StringBuilder
String | StringBuffer | StringBuilder | |
---|---|---|---|
是否可变 | ❌ | ✔️ | ✔️ |
线程安全 | ✔️ | ✔️ | ❌ |
内部实现 | final修饰的char数组 | char数组,自动扩容、拷贝 | char数组,自动扩容、拷贝 |
作用 | 表示字符串 | 构建字符串,同步,单线程下效率较StringBuilder低 | 构建字符串,非同步,效率高 |
- 提防
+
或者+=
连续拼接大量字符串,会使得性能低下 ,大量字符串拼接应考虑StringBuilder
(单线程下首选)或者StringBuffer
(需要线程安全的情况下) - Java编译会对的
+
或者+=
字符串拼接进行优化(优化成StringBuilder
或者StringBuffer
),所以简单的拼接可以直接使用
原子性、可见性和有序性
- 原子性(Atomicity):基本数据类型的读写具备原子性(虽然非协定要求,但是long和double在现代虚拟机中也实现了原子性),在
synchronized
代码块之间的操作也具备原子性 - 可见性(Visibility):一个线程修改了共享变量的值,其他线程能立刻得知这个修改,
volatile
保证的多线程操作时变量的可见性,而普通变量则不能保证这一点,synchronized
和final
也可以保证可见性 - 有序性(Ordering):如果在本线程内观察,所有的操作都是有序的(县线程内表现为串行的语义,Within-Thread As-If-Serial Semantics)。如果在一个线程中观察另一个线程,所有操作都是无序的(”指令重排序现象“和”工作内存与主内存同步延迟现象“)。
volatile
和synchronized
来保证线程间操作的有序性
先行发生(happens-before)原则
如果连个操作之间的关系不在以下规则之中,并且无法从以下规则推到出来,它们就没有顺序性保障,虚拟机可以对它们随意的进行重排序
- 程序次序规则(Program Order Rule):在一个线程内,按照程序代码的控制流顺序,书写在前面的代码先行发生于书写在后面的代码
- 管程锁定规则(Monitor Lock Rule):一个unlock操作先行发生于后面(时间上先后)对于同一个锁的lock操作
volatile
变量规则(Volatile Variable Rule):对一个volatile
变量的写操作先行发生于后面(时间上先后)对这个变量的读操作- 线程启动规则(Thread Start Rule):Thread对象的start()方法先行发生于此线程的每一个动作
- 线程终止规则(Thread Termination Rule):线程中的每一个动作都先行发生于对此线程的终止检测
- 线程中断规则(Thread Interruption Rule):对线程中interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生
- 对象终结规则(Finalizer Rule):一个对象的初始化完成先行于它的finalize()方法的开始
- 传递性(Transitivity):如果操作A先行发生于操作B,操作B先行发生于操作C,那么,操作A先行发生于操作C
一个操作”时间上的先发生“不代表这个操作会是”先行发生“;一个操作”先行发生“也不能代表这个操作”时间上的先发生“(指令重排序)。衡量并发安全问题不要受到时间顺序的干扰,一切必须以先行发生原则为准。
volatile和synchronized
关键字
volatile
可以说是Java虚拟机提供的最轻量级的同步机制
一个变量被声明为volatile
后具备两种特性:
- 保证此变量对所有线程的可见性,即修改可以被立即得知,此变量在各个线程的工作内存中不存在一致性问题(每次使用前都要先刷新)
- 禁止指令重排序优化
因为在Java中的运算并非原子操作,所以volatile
变量的运算在并发下一样是不安全的,volatile
只能保证可见性,在不符合以下规则的场景下需要通过加锁(使用synchronized
或java.util.concurrent
中的原子类)来保证原子性:
- 运算结果不依赖变量的当前值,或者能保证只有单一线程修改变量的值
- 变量不需要与其他的状态变量(可能存在不一致)共同参与不变约束
volatile
与synchronized
的区别
volatile | synchronized | |
---|---|---|
是否加锁 | 否 | 是 |
是否造成线程阻塞 | 否 | 是 |
是否具备可见性 | 是 | 是 |
是否具备原子性 | 否 | 是 |
是否可以被编译器优化 | 否 | 是 |
同步机制重量级 | 轻 | 重 |
使用的地方 | 变量 | 变量、方法 |
线程间通信方式
- 同步:通过
synchronized
实现线程间通信 while
轮询:不断地检测条件,会浪费CPU资源,存在可见性问题(可能造成死循环)wait/notify
机制:存在通知过早问题,会打乱程序的执行逻辑- 管道通信:类似消息传递机制,使用
java.io.PipedInputStream
和java.io.PipedOutputStream
进行通信
线程的各种状态
Java语言定义了5种线程状态(新建、运行、等待、阻塞、结束),任意时间点,线程有且只能有其中一种状态。Java中一般使用抢占式的线程调度方式
- 新建(New):创建后尚未启动的线程
- 运行(Runable):包括Running和Ready
- 无限期等待(Waiting):等待被其他线程显式地唤醒,进入该状态的方法有
无参Thread.join()
、无参Object.wait()
和LockSupport.park()
等 - 限期等待(Timed Waiting):到时间后会自动唤醒,进入该状态的方法有
Thread.sleep()
、带参Thread.join()
、带参Object.wait()
、LockSupport.parkNanos()
和LockSupport.parkUntil()
等 - 阻塞(Blocked):线程被阻塞了,等待获取到一个排他锁,在另一个线程放弃这个锁的时候发生
- 结束(Terminated):已终止线程的状态,线程已经结束执行。
阻塞状态在等待另一个线程释放排它锁,而等待状态在等待时间到或者被唤醒。
关于Java中的finally
finally可能没被执行
- 在try语句执行前程序就返回(结束了)
- 在try块中有System.exit(0),虚拟机停止
finally语句执行情况
- finally语句在return语句执行之后return返回之前执行的
- finally块中的return语句会覆盖try块中的return返回
- 如果finally语句中没有return语句覆盖返回值,那么原来的返回值可能因为finally里的修改而改变也可能不变(传值问题)
- try块里的return语句在异常的情况下不会被执行,这样具体返回哪个看情况
- 当发生异常后,catch中的return执行情况与未发生异常时try中return的执行情况完全一样
参考
- 《Thinking in Java》
- 《深入理解Java虚拟机》