为什么需要synchronized?在我们并发编程中,处理公共数据时,需要考虑多个线程同时处理导致的问题,这时候需要用到synchronized来修饰方法,保证其原子性。
(1)方法
(2)代码块
(3)静态方法
下面修饰方法后,锁的是当前实例对象,如果是不同的实例对象调用此方法是不生效的。
private int i=0;
public synchronized void add() {
System.out.println("---- start");
i++;
System.out.println("---- end");
}
锁的对象是this,作用范围是当前实例对象。
private int i=0;
public void add() {
synchronized(this) {
System.out.println("---- start");
i++;
System.out.println("---- end");
}
}
锁的对象是object,作用范围是当前实例对象。
private Object object = new Object();
public void add1() {
synchronized(object) {
System.out.println("---- start");
i++;
System.out.println("---- end");
}
}
锁对象是class类,作用范围所有对象公用一把锁
public void add1() {
synchronized(SynTest.class) {
System.out.println("---- start");
i++;
System.out.println("---- end");
}
}
锁对象是class对象,作用范围所有对象公用一把锁
private static Object staticObject = new Object();
public void add3() {
synchronized(staticObject) {
System.out.println("---- start");
i++;
System.out.println("---- end");
}
}
在静态方法上面加锁,作用范围所有对象公用一把锁
private static int k=0;
public static synchronized void add4() {
System.out.println("---- start");
k++;
System.out.println("---- end");
}
下面我们看一下同步代码的源码。对以下java代码编译成class文件后,使用javap反编译查看其字节码命令。
Object object = new Object();
public void method1() {
synchronized (object) {
}
}
我们通过一下字节码命令中可以看到monitorenter和monitorexit指令
public void method1();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=3, args_size=1
0: aload_0
1: getfield #3 // Field object:Ljava/lang/Object;
4: dup
5: astore_1
6: monitorenter
7: aload_1
8: monitorexit
9: goto 17
12: astore_2
13: aload_1
14: monitorexit
15: aload_2
locals = [ class org/example/SynTest, class java/lang/Object ]
stack = [ class java/lang/Throwable ]
frame_type = 250 /* chop */
offset_delta = 4
}
monitorenter和monitorexit指令,会让对象执行时,使其锁计数器+1或者-1,每个对象同一时间只和一个monitor关联。一个monitor同一时间只能被一个线程获得,当一个对象在尝试获取和这个对象关联的monitor锁的所有权的时候,monitorenter会发生下面3中情况:
1、当monitor计数器为0时,说明没有线程占用此锁,当一个线程获取到锁后,此计数器则会加1
2、当此线程已经获取到monitor的所有权时,若再次重入锁,则计数器会在加1,变成2,随着重入的次数,计数器会一直累加
3、当此线程拥有了monitor锁之后,其他线程只能等待锁释放
monitorexit指令:当同步方法执行完之后,会进入monitorexit,计数器则会减1,如是从重入方法中退出,随着退出的次数,计数器会持续减1,直到减为0,说明释放锁。
可重入锁
可重入的含义是,下图中当调用method1方法后,使用同一把锁可以进入method2和method3。既可以使用同一把锁进入自己的子程序,即可重入。
private synchronized void method1() {
System.out.println(Thread.currentThread().getId() + ": method1()");
method2();
}
private synchronized void method2() {
System.out.println(Thread.currentThread().getId()+ ": method2()");
method3();
}
private synchronized void method3() {
System.out.println(Thread.currentThread().getId()+ ": method3()");
}
执行monitorenter获取锁
刚开始计数器为0
执行method1() 计数器+1 ->1
执行method2() 计数器+1 ->2
执行method3() 计数器+1 ->3
执行monitorexit释放锁
执行完method3() 计数器-1 ->2
执行完method2() 计数器-1 ->1
执行完method1() 计数器-1 ->0
释放锁
(1)synchronized 没有lock这么多方法,不够灵活,没有获取到锁,则一直等待,没有尝试获取的时间方法
(2)synchronized不能多个条件锁,而lock和condition可以实现多条件锁
(3)没有lock的tryLock方法,如果获取锁则会。。
(4)synchronized内部使用的monitor实现的,而lock底层使用AQS,而AQS底层使用的park和unpark
(5)synchronized不需要手动释放锁
(6)synchronized是可重入,不可中断,非公平锁,lock是可重入、可中断、公平和非公平都支持的锁