# 关键字
# final关键字
final
关键字主要用在三个地方:变量、方法、类。
- 如果是基本数据类型的变量,其数值在初始化之后就不能更改;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。
- 当用
final
修饰一个类时,表明这个类不能被继承。final
类中的所有成员方法都会被隐式的指定为final
方法。 - 使用
final
的原因有两个。一个是把方法锁定,以防任何继承类来修改它的含义。所以类中所有的private
方法都被隐式的指定为final
;第二个原因是效率。
# static关键字
static
关键字的主要意义是在于创建独立于具体对象的域变量和方法。即使没有创建对象,也会使用属性和调用方法。static
关键字可以用来形成静态代码块以优化程序性能。static
块可以放置于类中的任何地方,类中可以有多个static
块。当类被初次加载时,就会按照static
块的顺序来执行每个static
块,并且只会执行一次。
static
优化性能的原因是在于它只会在类加载的时候执行一次。所以有时候可以将一些只需要执行一次的初始化操作放在static
代码块中执行。
static
关键字主要有以下四种使用场景:
- 修饰成员变量和成员方法:被
static
修饰的成员属于类,不属于单个这个类的某个对象,被类中所有对象共享,可以直接通过类名调用。被static
声明的成员变量属于静态成员变量,静态变量存放在Java内存区域的方法区。调用格式:类名.静态变量名
和类名.静态方法名()
。static
是不允许修饰局部变量的。 - 静态代码块:静态代码块定义在类中方法外,静态代码块在非静态代码块之前执行。该类不管创建多少对象,静态代码块只执行一次。
- 静态内部类(
static
只能修饰内部类):非静态内部类在编译完成之后会隐含地保存着一个引用,该引用是指向创建它的外围类,但是静态内部类却没有。没有这个引用意味:1. 它的创建是不需要依赖外围类的创建;2. 它不能使用任何外围类的非static
成员变量和方法。这种方式不仅具有延迟初始化的好处,而且由 JVM 提供了对线程安全的支持。 - 静态导包(用来导入类中的静态资源):格式为
import static
,这个关键字连用可以指定导入某个类中的指定静态资源,并且不需要使用类名调用类中静态成员,可以直接使用类中静态成员变量和成员方法。因为会影响代码的可读性,所以一般情况下不建议使用。
# 静态变量
被static
修饰的成员变量叫静态变量,也叫static
变量。它是属于这个类的,而不是属于这个对象的。比如一个类名是A的类,那么调用它就是A.func()
,而不用A a = new A();
去实例化A后,再使用a.func()
的方式去调用。
静态变量在内存中只会有一份,在类的加载过程中,JVM只会为静态变量分配一次内存空间。
# 实例变量
没有被static
修饰的成员变量叫做实例变量,实例变量是属于这个类的实例对象。即必须使用上述的a.func()
的方式,先实例话这个类的对象,再调用。
在每次创建对象时,都会为每个对象分配成员变量内存空间。而实例变量是属于实例对象的,所以在内存中,创建几次对象,就会有几份成员变量。
# 静态方法
用static
修饰的方法也叫做静态方法。注意构造方法不是静态方法。
# 静态代码块
静态代码块的格式是
static {
// 语句体;
i = 3;
}
一个类中的静态代码块可以有多个,位置可以随便放,它不在任何的方法体内,JVM加载类时会执行这些静态的代码块,如果静态代码块有多个,JVM将按照它们在类中的出现的先后顺序依次执行它们,每个代码块只会被执行一次。
静态代码块对于定义在它之后的静态变量,可以赋值,但是不能访问。
# this和super关键字
this
关键字用于引用类的当前实例,此关键字是可选的,使用此关键字可能会使代码更易读或易懂。
super
关键字用于从子类访问父类的变量和方法。
- 使用
super()
调用父类中的其它构造方法时,该语句必须处于首行,否则编译器会报错。另外,this
调用本类中的其它构造方法时,也要放在首行。 this
、super
不能用在static
方法中。
# synchroinzed关键字
# synchronized的底层实现原理
synchronized
是基于JVM内置锁实现的,通过进入与退出Monitor(监视器锁)的对象实现的。
在JDK1.5之前,它是一个重量级的锁,是直接通过对Monitor对象的MonitorEnter/MonitorExit指令操作来进行加锁。这样会导致「用户态」和「内核态」的来回切换,效率降低。
JDK1.5之后,通过加入锁粗化(Lock Coarsening)、锁消除(Lock Elimination)、轻量级锁(Lightweight Locking)、偏向锁(Biased Locking)、**适应性自旋(Adaptive Spinning)**等技术来减少锁操作的开销。
大致流程就是:
# 监视器锁-Monitor
监视器锁是基于操作系统底层的Mutex Lock(互斥锁)实现的,这里会涉及到如下的切换:
所以,它是一个重量级的锁。
# 锁粗化
当JVM虚拟机感知到一系列连续操作都是对同一个对象进行加锁时,这时候是没有其它的线程发起竞争的,那么,频繁地对同一个对象进行加锁、解锁操作是非常消耗性能的,所以JVM会对锁的范围进行扩大。
例如,如下代码:
public int getNumStatic() {
for (int i1 = 0; i1 < 1000; i++) {
// 循环对同一个对象加锁1000次
synchronized(object) {
num++;
}
}
return num;
}
因为上面这个代码是对同一个对象循环进行加锁,所以,代码会被优化为:
public int getNumStatic() {
synchronized(object) {
for (int i1 = 0; i1 < 1000; i++) {
num++;
}
}
return num;
}
即将synchronized
提取到循环体之外。
# 锁消除
当JVM通过逃逸分析判断某一段代码块不需要加锁时,就在JVM阶段将锁去除。
# 偏向锁
如果某个锁总是由同一线程多次获得,那么为了减少同一线程获取锁的代价(比如锁切换时会涉及到CAS操作),就会引入偏向锁。
偏向锁的核心思想是,如果一个线程获得了锁,那么锁就会进入偏向模式,此时Mark Word的结构也会变成偏向结构。如此,当这个线程再次请求锁的时候,就无需再做任何同步的操作,即获取锁的过程,这样就省略了大量不必要的申请锁的操作,从而优化了程序的性能。
# 默认开启
# 开启偏向锁
-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0
# 关闭偏向锁
-XX:-UseBiasedLocking
# 轻量级锁
只有在偏向锁失败的情况下,JVM才会升级成为轻量级锁。
轻量级锁会认为:在大多数情况下,在一个线程执行期间,是不存在锁竞争的(即线程交替执行)。
所以,如果出现了大量线程竞争同一把锁的情况下,轻量级锁也就会失效了。为此,JVM引入了自旋锁。
# 适应性自旋
轻量级锁失败后,虚拟机为了避免线程真实地在操作系统层面被乖巧,JVM就会让等待线程进行几次空循环,让轻量级锁被释放,再被循环中的等待线程占有。如果在指定的循环次数中都无法获取锁,那么锁就会升级为重量级锁。
# synchronized与Lock锁的区别 Uncompleted
# volatile关键字
volatile
主要用在多线程、同步变量。当一个共享变量被volatile
修饰之后,就具备了两个含义:
- 线程修改了变量的值时,变量的新值对于其它线程是立即可见的,即「不同线程对这个变量进行操作时具有可见性」。
- 禁止使用指令重排序。
禁止指令重排序包含两个含义:
- 当程序执行到
volatile
变量的操作时,在其前面的操作已经全部执行完毕,并且结果会对后面的操作可见,在其后面的操作还没有进行; - 在进行指令优化时,
volatile
变量之前的语句不能在volatile
后面执行,volatile
之后的也不会在之前执行;
# volatile的底层实现原理 Uncompleted
# 成员变量和局部变量的区别
- 语法形式:成员变量是属于类的,而局部变量是在方法中定义的变量或者是方法的参数;成员变量可以被
public
、private
、static
等修饰符修饰,而局部变量不能被访问控制修饰符及static
所修饰;但是它们都能被final
修饰符修饰。 - 变量在内存中的存储方式:如果成员变量是使用
static
修饰的,那么这个成员变量是属于类的;如果没有使用static
修饰,这个成员变量是属于实例的。而对象存在于堆内存,局部变量则存在于栈内存。 - 变量在内存中的生存时间:成员变量是对象的一部分,随着对象的创建而存在;而局部变量会随着方法的调用而自动消失。
- 成员变量如果没有被赋初值,则会自动以类型的默认值而赋值(如果被
final
修饰,则必须显式赋值),而局部变量不会自动赋值。