Java关键字
问题1:关于final关键字的总结?
修饰变量:如果是基本类型,在初始化之后不可变;如果是引用类型,初始化之后不能指向其他对象,但指向对象的内容是可变的;必须在定义时或者构造器中进行初始化
修饰类:表示类无法继承;final类中所有成员方法都是final方法;
修饰方法:把方法锁定,以防止任何继承类修改它; private方法隐式地制定为final;
问题2: static关键字的总结?
- static 关键字可以用来修饰:属性、方法、内部类、代码块;
- static 修饰的资源属于类级别,是全体对象实例共享的资源;
- 使用 static 修饰的属性,静态属性是在类的加载期间初始化的,使用类名.属性访问
修饰成员变量和方法:属于类,被类对象共享。静态变量存放在 Java 内存区域的方法区
,属于线程共享区;
静态代码块:静态代码块定义在类方法外,静态代码块在非静态代码块之前执行(静态代码块—>非静态代码块—>构造方法
)。 该类不管创建多少对象,静态代码块只执行一次。
静态内部类:static只能修饰内部类。静态内部类和非静态内部类的最大区别:非静态内部类在编译后会隐含地保存着一个引用,该引用指向创建它的外围类,但静态内部类没有。这意味着:不需要依赖外围类创建;不能使用任何外围类的非static成员变量和方法。
静态导包:不需要使用类名可直接使用类中静态成员变量和成员方法。
问题3: this和super关键字总结?
this关键字用于当前引用类的当前实例;
super关键字用于从子类访问父类的变量和方法;
注意:在构造器中使用super()
调用父类的其他构造方法时,该语句必须处于构造器首行。另外this调用本类的其他构造方法时,也要处于首行;this、super不能用在static方法中,this和super是属于对象范畴的东西,而静态方法是属于类范畴的东西。
问题4:synchronized关键字总结?
synchronized关键字解决的是多个线程之间访问资源的同步性问题,可以保证修饰的方法或者代码块在任意时刻只能有一个线程执行。是非公平锁。
- 修饰实例方法:对当前对象加锁,进入同步代码前需要获得当前对象的锁;
- 修饰静态方法:对当前类加锁,作用于类的所有对象实例;
- 修饰代码块:对给定对象加锁
synchronized 实现原理
JVM是通过进入、退出对象监视器(Monitor)
来实现对方法、同步块的同步。在编译之后在同步方法调用前加入一个 monitor.enter
指令,在退出方法和异常处插入 monitor.exit
的指令。其本质就是对一个对象监视器进行获取,而这个获取过程具有排他性从而达到了同一时刻只能一个线程访问的目的。
synchronized 原则
- 可重入:一个线程可以多次执行synchronzied重复获取同一把锁;
- 不可中断:一个线程获得锁后,另一个线程想要获得锁必须处于阻塞或等待状态;
synchronized和ReentrantLock的区别?
-
synchronized和ReentrantLock都是可重入锁,自己可以再次获取自己的内部锁,通过维护计数器实现。
-
synchronized依赖于JVM,而ReentrantLock依赖于API;
-
ReentrantLock增加许多功能,如等待可中断,可实现公平锁(先等待的线程先获得锁),可实现选择性通知
synchronized 锁升级
锁主要存在四种状态:无锁 --> 偏向锁 --> 轻量级锁 --> (自旋锁) --> 重量级锁,锁可升不可降。
偏向锁偏向第一个获取它的线程;轻量级锁在多线程竞争的情况下使用自旋的操作,让没有竞争到锁的线程不挂起(挂起会产生用户态和内核态的状态,耗时)。轻量级锁的加锁和释放锁都依赖于CAS算法;
问题5: volatile关键字总结?
-
保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。(保持内存可见性),所有线程都能看到共享内存的最新状态;
volatile通过以下特殊规则实现内存可见性:
- read、load、use动作必须连续出现;
- assign、store、write动作必须连续出现;
所以volatile变量能够保证每次读取前必须先从主内存刷新最新的值;每次写入后必须立即同步回主内存中。
-
禁止进行指令重排序。(实现有序性),通过内存屏障来防止指令重排序;
为了实现volatile的内存语义,编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。
在每个volatile写操作的前面插入一个StoreStore屏障。
在每个volatile写操作的后面插入一个StoreLoad屏障。
在每个volatile读操作的后面插入一个LoadLoad屏障。
在每个volatile读操作的后面插入一个LoadStore屏障。
-
只能保证对单次读/写的原子性。但原子性只限于读和写,无法保证变量上任何操作是原子的。
问题6:transient 关键字总结?
Java在序列化过程中,把Java对象转换为二进制进行数据传输。一般使用在数据持久化/rmi/rpc远程调用,可以通过反序列化获取保存的数据信息。一般通过实现serializable接口
自动序列,可以手动显示的生命serialVersionUID = 1L,系统也提供了默认的序列号,这个序列号是根据类编译的信息生成的,只要class文件没有改变,序列号也不会改变。Java在序列化和反序列化操作中通过序列号判断信息一致性,如果序列号不一致,操作失败。序列化后保存的对象的值和类型,静态变量不参与序列化。被声明为transient的属性不参与序列化;
问题7:对象锁和类锁
- 对象锁:类声明后,我们可以 new 出来很多的实例对象。这时候,每个实例在 JVM 中都有自己的引用地址和堆内存空间,这时候,我们就认为这些实例都是独立的个体,很显然,在实例上加的锁和其他的实例就没有关系,互不影响了。
- 锁住非静态变量:实例自身变量,不会与其它实例共享;
- 锁住this对象:当前对象实例本身;
- 直接加在非静态方法:只有使用同一实例的线程才会受锁的影响,多个实例调用同一方法也不会受影响。
- 类锁:类锁是加载类上的,而类信息是存在 JVM 方法区的,并且整个 JVM 只有一份,方法区又是所有线程共享的,所以类锁是所有线程共享的。
- 锁住静态变量
- 直接加在静态方法上
- 锁住当前类的
.class
文件