Java关键字
Java关键字
Java中有一些比较常见且重要的关键字:synchronzied、volatile、final、static。
synchronized
在Java早期版本中,synchronized属于重量级锁,效率不高,因为底层是操作系统的mutex lock来实现的。在Java 6之后,synchronized引入了大量优化手段,如自旋锁等,极大减少了开销。
synchronized能修饰哪些东西?
代码块
给括号里指定的对象/类加锁:
synchronized(object)
或者synchronized(类.class)
。例如:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23public class SynchronizedDemo implements Runnable {
static SynchronizedDemo instance = new SynchronizedDemo();
public void run() {
synchronized (this) { //进入同步代码块前要获得指定对象的锁
System.out.println("Thread: " + Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e){
e.printStackTrace();
}
System.out.println("Terminated: " + Thread.currentThread().getName());
}
}
public static void main(String[] args) {
Thread t1 = new Thread(instance);
Thread t2 = new Thread(instance);
t1.start();
t2.start();
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24public class SynchronizedDemo implements Runnable {
static SynchronizedDemo instance1 = new SynchronizedDemo();
static SynchronizedDemo instance2 = new SynchronizedDemo();
public void run() {
synchronized (SynchronizedDemo.class) { //进入同步代码块前要获得对应类的锁
System.out.println("Thread: " + Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e){
e.printStackTrace();
}
System.out.println("Terminated: " + Thread.currentThread().getName());
}
}
public static void main(String[] args) {
Thread t1 = new Thread(instance1);
Thread t2 = new Thread(instance2);
t1.start();
t2.start();
}
}实例方法
给当前对象实例加锁,进入同步代码块前要获得当前对象实例的锁。例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25public class SynchronizedDemo implements Runnable {
static SynchronizedDemo instance = new SynchronizedDemo();
public void run() {
method();
}
private synchronized void method() {
System.out.println("Thread: " + Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e){
e.printStackTrace();
}
System.out.println("Terminated: " + Thread.currentThread().getName());
}
public static void main(String[] args) {
Thread t1 = new Thread(instance);
Thread t2 = new Thread(instance);
t1.start();
t2.start();
}
}静态方法
修饰静态方法,默认的锁就是当前的类。因为静态成员是归整个类所有,被类的所有实例共享。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26public class SynchronizedDemo implements Runnable {
static SynchronizedDemo instance1 = new SynchronizedDemo();
static SynchronizedDemo instance2 = new SynchronizedDemo();
public void run() {
method();
}
private static synchronized void method() {
System.out.println("Thread: " + Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e){
e.printStackTrace();
}
System.out.println("Terminated: " + Thread.currentThread().getName());
}
public static void main(String[] args) {
Thread t1 = new Thread(instance1);
Thread t2 = new Thread(instance2);
t1.start();
t2.start();
}
}
PS:构造方法不能用synchronzied,因为其本身就是线程安全的。
synchronized底层原理
创建如下一段代码:
1 | public class SynchronizedDemo { |
使用javac命令进行编译生成.class文件:
1 | javac SynchronizedDemo.java |
使用javap命令反编译查看.class文件:
1 | javap -verbose SynchronizedDemo.class |
可以得到详细的字节码信息:
其中,第6行的monitorenter
和第8行以及第14行的monitorexit
是关键。
monitorenter
指向同步代码块的开始位置,monitorexit
指向同步代码块的结束位置。
上面的字节码中包含一个 monitorenter
指令以及两个 monitorexit
指令,这是为了保证锁在同步代码块代码正常执行以及出现异常这两种情况下都能被正确释放。
monitorenter
:每一个对象在同一时间只会和一个monitor相关联,一个monitor在同一时间只能被一个线程所获取。因此,一个对象在尝试获得其关联的monitor锁的时候,会发生三种情况:
- monitor计数器为0,表示锁尚未被任何对象获取,那么这个线程就会获得锁,并且计数器+1
- 若已获得了该锁,则会重入,且计数器随着重入次数的增加而增加
- monitor计数器不为0,则已被其他线程获取,等待锁释放
monitorexit
:释放对于monitor的所有权,过程就是将monitor的计数器减1,如果减完以后,计数器不是0,则代表刚才是重入进来的,当前线程还继续持有这把锁的所有权,如果计数器变成0,则代表当前线程不再拥有该monitor的所有权,即释放锁。
Java 6后的优化手段
前面提到了synchronized在早期Java中的开销很大,主要是因为实现依赖于底层操作系统的Mutex Lock,需要将当前线程挂起并从用户态切换到内核态来执行。后期引入了大量的优化手段:
- 锁粗化
- 锁消除
- 轻量级锁
- 偏向锁
- 适应性自旋
volatile
volatile的常见用途
保证可见性
防止重排序
例如,并发环境下实现单例模式,通常采用双重检查加锁DCL的方式:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16public class Singleton {
public static volatile Singleton singleton;
private Singleton(){}
public static Singleton getInstance(){
if (singleton == null){
synchronized (Singleton.class){
if (singleton == null){
singleton = new Singleton();
}
}
}
return singleton;
}
}
通常来说,实例化一个对象可以分为三个步骤:分配内存空间、初始化对象、将内存空间的地址赋值给相应的引用。
但是由于操作系统可能会对指令进行重排序,导致未初始化的对象引用暴露在外。因此,用volatile加以修饰防止这种情况发生。
volatile底层原理
volatile可见性实现原理
一是通过插入内存屏障来禁止指令重排序。在写操作之后插入一个写屏障,读操作之前插入一个读屏障,确保写操作不会被重排序到读操作之前;
二是强制刷新主存。强制线程将修改后的新值立即刷新到主存,保证其他线程读到的是新值。
volatile有序性实现原理
依靠happens-before的规则。
final
final可用来修饰类、方法和变量。
- final修饰的类不能被继承,final类中的所有成员方法都会被隐式地指定为final方法
- final修饰的方法不能被重写
- final修饰的变量是常量,即:数据类型的变量不能被修改,引用类型的变量不能修改指向
static
static应用场景一般为:
修饰成员变量和成员方法
static修饰的成员隶属于这个类,被类中所有对象共享,可以通过类名进行调用。
静态代码块
静态代码块定义在类中的方法外,在非静态代码块之前执行(静态代码块 -> 非静态代码块 -> 构造方法)。无论类创建多少个对象,静态代码块只执行一次。
静态内部类
静态内部类不能使用任何外围类的非static成员变量和方法。
PS:this和super不能用在static方法中。
因为this代表对本类对象的引用,super代表对父类对象的引用;二者属于对象范畴,而静态方法属于类范畴。