一.Synchronized的基本使用
Synchronized
是解决并发问题的一种最常用的方法,也是最简单的一种方法。它的主要作用有三个:
- 确保线程互斥的访问同步代码
- 保证共享变量的修改能够及时可见
- 有效解决重排序问题
总有有三种用法:
- 修饰普通方法
- 修饰静态方法
- 修饰代码块
代码举例:
- 没有同步的情况:
package com.paddx.test.concurrent;
public class SynchronizedTest {
public void method1(){
System.out.println("Method 1 start");
try {
System.out.println("Method 1 execute");
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Method 1 end");
}
public void method2(){
System.out.println("Method 2 start");
try {
System.out.println("Method 2 execute");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Method 2 end");
}
public static void main(String[] args) {
final SynchronizedTest test = new SynchronizedTest();
new Thread(new Runnable() {
@Override
public void run() {
test.method1();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
test.method2();
}
}).start();
}
}
执行结果如下:
线程1和线程2同时进入执行状态,线程2的执行速度比线程1快,所以线程2先执行完成,这个过程中线程1和线程2是同时执行的。
2. 对普通方法同步:
package com.paddx.test.concurrent;
public class SynchronizedTest {
public synchronized void method1(){
System.out.println("Method 1 start");
try {
System.out.println("Method 1 execute");
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Method 1 end");
}
public synchronized void method2(){
System.out.println("Method 2 start");
try {
System.out.println("Method 2 execute");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Method 2 end");
}
public static void main(String[] args) {
final SynchronizedTest test = new SynchronizedTest();
new Thread(new Runnable() {
@Override
public void run() {
test.method1();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
test.method2();
}
}).start();
}
}
执行结果如下:
可以看出:线程2需要等待线程1的method1
方法执行完成才能开始执行method2
方法。
3. 静态方法同步
package com.paddx.test.concurrent;
public class SynchronizedTest {
public static synchronized void method1(){
System.out.println("Method 1 start");
try {
System.out.println("Method 1 execute");
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Method 1 end");
}
public static synchronized void method2(){
System.out.println("Method 2 start");
try {
System.out.println("Method 2 execute");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Method 2 end");
}
public static void main(String[] args) {
final SynchronizedTest test = new SynchronizedTest();
final SynchronizedTest test2 = new SynchronizedTest();
new Thread(new Runnable() {
@Override
public void run() {
test.method1();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
test2.method2();
}
}).start();
}
}
执行结果如下:
对静态方法的同步本质上是对类的同步(静态方法本质上是属于类的方法,而不是对象上的方法),所以即使test
和test2
属于不同的对象,但是他们都是属于SynchronizedTest
类的实例,所以也只能顺序的执行method1
和method2
方法。
4. 代码块的同步:
package com.paddx.test.concurrent;
public class SynchronizedTest {
public void method1(){
System.out.println("Method 1 start");
try {
synchronized (this) {
System.out.println("Method 1 execute");
Thread.sleep(3000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Method 1 end");
}
public void method2(){
System.out.println("Method 2 start");
try {
synchronized (this) {
System.out.println("Method 2 execute");
Thread.sleep(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Method 2 end");
}
public static void main(String[] args) {
final SynchronizedTest test = new SynchronizedTest();
new Thread(new Runnable() {
@Override
public void run() {
test.method1();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
test.method2();
}
}).start();
}
}
执行结果如下:
虽然线程1和线程2都进入了对应的方法开始执行,但是线程2在进入同步块之前,需要等待线程1中同步块执行完成。
二. synchronized原理
利用反编译来看一下synchronized
是如何实现对代码块的同步的:
package com.paddx.test.concurrent;
public class SynchronizedDemo {
public void method() {
synchronized (this) {
System.out.println("Method 1 start");
}
}
}
反编译结果:
这两条指令的作用:
一. monitorenter
每个对象都有一个监视器锁(monitor
),当monitor
被占用时就会处于锁定状态,线程执行monitorenter
指令时尝试获取monitor
的所有权,过程如下:
- 如果
monitor
的进入数为0,则该线程进入monitor
,然后将其进入数设置为1,该线程即为monitor
的所有者。 - 如果线程已经占有该
monitor
,只是重新进入,则进入monitor
的进入数加1 - 如果其他线程已经占用了
monitor
,则线程进入阻塞状态,直到monitor
的进入数为0,再重新尝试获取monitor
的所有权。
二. monitorexit
执行 monitorexit
的线程必须是objectref
所对应的monitor
的所有者。指令执行时,monitor
的进入数减1,如果减1后进入数为0,那线程退出monitor
,不再是这个monitor
的所有者。其他被这个monitor
阻塞的线程可以尝试去获取这个monitor
的所有权。
我们可以看出synchronized
的实现原理,synchronized
的语义底层是通过一个monitor
的对象来完成,其实wait/notify
等方法也依赖于monitor
对象,这就是为什么只有在同步的块中或者方法中才能调用wait/notify
等方法,否则会抛出java.lang.IllegalMonitorStateException
的异常的原因。
同步方法的反编译结果:
package com.paddx.test.concurrent;
public class SynchronizedMethod {
public synchronized void method() {
System.out.println("Hello World!");
}
}
反编译结果:
可以看到,方法的而同步并没有通过指令monitorenter
和monitorexit
来完成,相对于普通方法,其常量池中多了ACC_SYNCHRONIZED
标识符。JVM就是根据该标识符来实现方法同步的:**当方法调用时,调用指令将会检查方法的ACC_SYNCHRONIZED
访问标志是否被设置,如果设置了,执行线程将先获取monitor
,获取成功之后才能执行方法体,方法执行完后再释放monitor
。在方法执行期间,其他任何线程都无法再获得同一个monitor
对象。其实本质上没有区别,只是方法的同步是一种隐式的方式来实现的,无需通过字节码来完成。
三. 运行结果的解释
- 对普通方法同步
虽然method1
和method2
是不同的方法,但是这两个方法都进行了同步,并且是通过同一个对象去调用的,所以调用之前都需要先去竞争同一个对象上的锁(monitor
),也就只能互斥的获取到锁,因此method1
和method2
只能顺序的执行。 - 静态方法同步
虽然test
和test2
属于不同的对象,但是test
和test2
属于同一类的不同实例,由于method1
和method2
都属于静态同步方法,所以调用的时候需要获取同一个类上monitor
(每个类只对应一个class对象),所以也只能顺序的执行。 - 代码块的同步
对于代码块的同步实质上需要获取synchronized
关键字后面括号中对象的monitor
,由于这段代码中括号的内容都是this
,而method1
和method2
又是通过同一个对象去调用的,所以进入同步代码块之前需要去竞争同一个对象上的锁,因此只能顺序执行同步块。
感谢并参考:
http://www.cnblogs.com/paddix/