Synchronized关键字
synchronized
是Java提供的内部锁,里边有类锁和对象锁。synchronized关键字能够用于编辑4种不同类型的块:
- 实例方法
- 静态方法
- 实例方法中的代码块
- 静态方法中的代码块
这些块被同步在不同的对象上。使用哪一种synchronized块是取决于实际情况。
同步的实例方法
synchronized关键字修饰在实例方法上,则该方法就是同步的实例方法。
/**
* 同步的实例方法,对象锁
**/
public synchronized void methodOne() {
System.out.println(Thread.currentThread().getName() + " methodOne start。。。。");
try {
// 假装执行很久
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " methodOne end。。。。");
}
Java中一个同步的实例方法同步在这个方法所在的实例(锁对象)上。因此,拥有这个方法的实例,在多线程情况下,同一时刻只有一个线程能获取到该方法的执行权,其他线程会阻塞等待,知道上一个线程执行完才开始执行。如果多个实例分别执行自己的线程,则每个实例都能执行,不会阻塞等待。下面通过代码执行结果进行说明。首先是同一个实例,开启多个线程去执行同步的实例方法methodOne()
,验证代码(1-1)如下:
public static void main(String[] args) {
SynchronizedKeyword synchronizedKeyword = new SynchronizedKeyword();
new Thread(() -> synchronizedKeyword.methodOne()).start();
new Thread(() -> synchronizedKeyword.methodOne()).start();
new Thread(() -> synchronizedKeyword.methodOne()).start();
}
启动三个线程,使用同一个实例对象synchronizedKeyword
去执行同步的实例方法methodOne()
,执行结果:
从结果可以看到,一个线程执行结束后才会执行下一个线程,这个证实同步的实例方法锁的是实例对象,接下来修改执行代码,创建多个实例对象,分别去执行同步的实例方methodOne()
,验证代码(1-2)如下:
public static void main(String[] args) {
SynchronizedKeyword synchronizedKeyword_1 = new SynchronizedKeyword();
SynchronizedKeyword synchronizedKeyword_2 = new SynchronizedKeyword();
SynchronizedKeyword synchronizedKeyword_3 = new SynchronizedKeyword();
new Thread(() -> synchronizedKeyword_1.methodOne()).start();
new Thread(() -> synchronizedKeyword_2.methodOne()).start();
new Thread(() -> synchronizedKeyword_3.methodOne()).start();
}
启动三个线程,分别使用三不同的实例对象synchronizedKeyword_1
、synchronizedKeyword_2
、synchronizedKeyword_3
去执行同步的实例方法methodOne()
,执行结果:
从结果可以看到三个线程并没有等待其他线程,几乎是一起开始一起结束。另外从线程执行的顺序还可以看出synchronized
关键字同步的时候,不保证公平性,因此会有线程插队的现象。
同步的静态方法
synchronized关键字修饰在静态方法上,则该方法就是同步的静态方法。
/**
* 同步的静态方法,SynchronizedKeyword.class
*/
public static synchronized void methodTwo() {
System.out.println(Thread.currentThread().getName() + " methodTwo start。。。。");
try {
// 假装执行很久
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " methodTwo end。。。。");
}
同步的静态方法同步在方法所属的类对象(如SynchronizedKeyword.class)上。由于每个类只有一个类对象存在于JVM中,因此全局同时只有一个线程能够进入到同一个类的静态同步方法中。如果静态同步方法位于不同的类中,则一个线程能够在每个类的静态同步方法中执行。验证代码(2-1)如下:
public static void main(String[] args) {
SynchronizedKeyword synchronizedKeyword_1 = new SynchronizedKeyword();
SynchronizedKeyword synchronizedKeyword_2 = new SynchronizedKeyword();
SynchronizedKeyword synchronizedKeyword_3 = new SynchronizedKeyword();
new Thread(() -> synchronizedKeyword_1.methodTwo()).start();
new Thread(() -> synchronizedKeyword_2.methodTwo()).start();
new Thread(() -> synchronizedKeyword_3.methodTwo()).start();
}
启动三个线程,分别使用三不同的实例对象synchronizedKeyword_1
、synchronizedKeyword_2
、synchronizedKeyword_3
去执行同步的静态方法methodTwo()
,执行结果:
从结果可以看到,线程是一个接一个顺序执行完的,与验证代码(1-1)的效果一样,由此也可以证实同步的静态方法锁的是类对象。当然如果用同一个实例去执行methodTwo()
方法执行结果肯定是一样,在此就不做演示。
实例方法的同步代码块
有时不必同步整个方法,因此可以只同步一个方法中的一部分。接下来一起来看看实例方法中的同步代码块是如何使用的。
/**
* 实例方法的同步代码块,指定锁对象
*/
public void methodThree() {
synchronized (this) {
System.out.println(Thread.currentThread().getName() +" methodThree start。。。。");
try {
// 假装执行很久
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() +" methodThree end。。。。");
}
}
同步代码块使得一块代码可以被同步访问。需要注意的是,Java同步代码块与同步方法不一样的是需要指定一个锁对象,可以是实例对象也可以是类对象。在上面的例子中使用的“this”,也就是这个方法调用所在的实例对象。括号中的对象叫做「monitor object」。这段代码被叫做同步在一个monitor object上。因为在例子中我们指定的是实例对象,如果使用同一个实例去执行methodThree()
方法同步效果与验证代码(1-1)一样,如果使用多个实例分别去执行methodThree()
方法同步效果与验证代码(1-2)一样,在此就不做演示。当然也可以指定锁对象为类对象,如果指定的是类对象,同步效果与验证代码(2-1)一样,感兴趣的朋友可以自行验证。
静态方法中的同步代码块
在静态方法中使用同步代码块,则该代码块就是静态方法中的同步代码块。
/**
* 静态方法中的同步代码块,指定锁对象(只能类对象)
*/
public static void methodFour() {
synchronized (SynchronizedKeyword.class) {
System.out.println(Thread.currentThread().getName() +" methodFour start。。。。");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() +" methodFour end。。。。");
}
}
需要说明一点,由于静态方法是类所属的方法,因此在代码块无法执行实例对象作为锁对象,所以静态方法中的同步代码块只能指定类对象作为锁对象。
总结
- synchronized可以修饰在方法或代码块上
- synchronized锁对象包含实例对象和类对象
- 静态方法是类所属的方法,因此同步的静态方法和静态方法中的同步代码块都是使用类对象锁