# 关键字

# final关键字

final关键字主要用在三个地方:变量、方法、类。

  1. 如果是基本数据类型的变量,其数值在初始化之后就不能更改;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。
  2. 当用final修饰一个类时,表明这个类不能被继承。final类中的所有成员方法都会被隐式的指定为final方法。
  3. 使用final的原因有两个。一个是把方法锁定,以防任何继承类来修改它的含义。所以类中所有的private方法都被隐式的指定为final;第二个原因是效率。

# static关键字

static关键字的主要意义是在于创建独立于具体对象的域变量和方法。即使没有创建对象,也会使用属性和调用方法。static关键字可以用来形成静态代码块以优化程序性能。static块可以放置于类中的任何地方,类中可以有多个static块。当类被初次加载时,就会按照static块的顺序来执行每个static块,并且只会执行一次

static优化性能的原因是在于它只会在类加载的时候执行一次。所以有时候可以将一些只需要执行一次的初始化操作放在static代码块中执行。

static关键字主要有以下四种使用场景:

  1. 修饰成员变量和成员方法:被static修饰的成员属于类,不属于单个这个类的某个对象,被类中所有对象共享,可以直接通过类名调用。被static声明的成员变量属于静态成员变量,静态变量存放在Java内存区域的方法区。调用格式:类名.静态变量名类名.静态方法名()static是不允许修饰局部变量的
  2. 静态代码块:静态代码块定义在类中方法外,静态代码块在非静态代码块之前执行。该类不管创建多少对象,静态代码块只执行一次。
  3. 静态内部类(static只能修饰内部类):非静态内部类在编译完成之后会隐含地保存着一个引用,该引用是指向创建它的外围类,但是静态内部类却没有。没有这个引用意味:1. 它的创建是不需要依赖外围类的创建;2. 它不能使用任何外围类的非static成员变量和方法。这种方式不仅具有延迟初始化的好处,而且由 JVM 提供了对线程安全的支持。
  4. 静态导包(用来导入类中的静态资源):格式为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调用本类中的其它构造方法时,也要放在首行。
  • thissuper不能用在static方法中。

# synchroinzed关键字

# synchronized的底层实现原理

synchronized是基于JVM内置锁实现的,通过进入与退出Monitor(监视器锁)的对象实现的。

在JDK1.5之前,它是一个重量级的锁,是直接通过对Monitor对象的MonitorEnter/MonitorExit指令操作来进行加锁。这样会导致「用户态」和「内核态」的来回切换,效率降低。

JDK1.5之后,通过加入锁粗化(Lock Coarsening)锁消除(Lock Elimination)轻量级锁(Lightweight Locking)偏向锁(Biased Locking)、**适应性自旋(Adaptive Spinning)**等技术来减少锁操作的开销。

大致流程就是:无锁状态偏向锁轻量级锁重量级锁无锁状态\rightarrow 偏向锁\rightarrow 轻量级锁\rightarrow 重量级锁

# 监视器锁-Monitor

监视器锁是基于操作系统底层的Mutex Lock(互斥锁)实现的,这里会涉及到如下的切换:

用户态内核态用户态用户态\rightarrow 内核态\rightarrow 用户态

所以,它是一个重量级的锁。

# 锁粗化

当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

# 成员变量和局部变量的区别

  1. 语法形式:成员变量是属于类的,而局部变量是在方法中定义的变量或者是方法的参数;成员变量可以被publicprivatestatic等修饰符修饰,而局部变量不能被访问控制修饰符及static所修饰;但是它们都能被final修饰符修饰。
  2. 变量在内存中的存储方式:如果成员变量是使用static修饰的,那么这个成员变量是属于类的;如果没有使用static修饰,这个成员变量是属于实例的。而对象存在于堆内存,局部变量则存在于栈内存。
  3. 变量在内存中的生存时间:成员变量是对象的一部分,随着对象的创建而存在;而局部变量会随着方法的调用而自动消失。
  4. 成员变量如果没有被赋初值,则会自动以类型的默认值而赋值(如果被final修饰,则必须显式赋值),而局部变量不会自动赋值。