synchronized的使用与理解
synchronized从锁的对象的维度来看分为俩种情况
锁住类
由于静态成员不专属于任何一个实例对象,是类成员,因此通过class对象锁可以控制静态 成员的并发操作。需要注意的是如果一个线程A调用一个实例对象的非static synchronized方法,而线程B需要调用这个实例对象所属类的静态 synchronized方法,是允许的,不会发生互斥现象,因为访问静态 synchronized 方法占用的锁是当前类的class对象,而访问非静态 synchronized 方法占用的锁是当前实例对象锁。
另外,从内存的角度来讲,静态成员所分配的内存空间也并不和任何一个对象的内存空间重叠,而是单独在一块内存里面分配。
锁对象实例
由于一个对象只有一个锁,所以只会同步该对象的方法或以此为锁的代码块,如果是相同的类对象,但是是不同的对象实例,他们的方法都是不会同步的。
synchronized修饰的分类
synchronized从修饰方式上可以分为修饰函数和修饰代码块俩种
修饰函数
修饰正常的实例函数:
public synchronized void method(){
//TODO 此时锁住的是该类的实例对象
}
这种使用方式会使一个实例对象的此方法在不同线程保持同步,但是new出来的不同实例对象调用同一个方法不会保持线程同步
实例方法并不是类所独有的,每个对象实例独立拥有它,它并不被对象实例所共享。这也比较能推出,在实例方法上加入synchronized,那么它获取的就是这个类的锁,锁住的就是这个对象实例。
修饰静态函数:
public static synchronized void method(){
//TODO 此时锁住的是类对象
}
这种使用方式,不同的实例对象调用或者直接点出调用,都会保持方法同步。
静态方法是属于“类”,不属于某个实例,是所有对象实例所共享的方法。也就是说如果在静态方法上加入synchronized,那么它获取的就是这个类的锁,锁住的就是这个类。
修饰代码块
实例对象
synchronized(this){
//TODO 此时锁住的是该类的实例对象
}
这种使用方式与上面的修饰实例函数的作用是完全一致的,即相同实例调用保持同步反则亦然
class对象
synchronized(demo.class){
//TODO 此时锁住的是类对象
}
理解同锁静态方法
任意实例对象Object
//锁住的是配置的实例对象,例如以String对象为锁
String lock = "";
synchronized(lock){
//TODO 此时锁住的是该类的实例对象Object
}
理解同锁实例对象
原理
synchronized底层原理是使用了对象持有的监视器(monitor)。但是同步代码块和同步方法的原理存在一点差异:
同步代码块是使用monitorenter和monitorexit指令实现的
同步方法是由方法调用指令读取运行时常量池中方法的ACC_SYNCHRONIZED 标识隐式实现,实际上还是调用了monitorenter和monitorexit指令
总结
-
无论synchronized关键字加在方法上还是对象上,如果它作用的对象是非静态的,则它取得的锁是对象;如果synchronized作用的对象是一个静态方法或一个类,则它取得的锁是对类,该类所有的对象同一把锁。
-
每个对象只有一个锁(lock)与之相关联,谁拿到这个锁谁就可以运行它所控制的那段代码。
-
实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。