Java并发编程(三)——synchronized
一,概述
synchronized关键字是Java中用来控制线程并发访问的基础机制,利用synchronized来控制一块代码同一时间只能有一个线程访问,其它线程等待,相当于加锁。
synchronized提供的是互斥锁,仅能实现对资源的互斥访问,而concurrent.lock不仅有互斥锁,还有读写锁。把读锁和写锁分开,写锁相当于互斥锁,而读锁是共享的,可以让多线程同时读,以提高性能。
synchronized关键字是不能继承的。
二,用法
synchronized使用方式不同会对代码产生不同的影响,主要使用方式有以下几种:
1,synchronized修饰方法
public synchronized void method(){
//
}
这是一个同步方法,使用的是对象锁,锁是该方法的调用者,就是当前对象this,这时synchronized锁定的就是调用这个同步方法对象。
也就是说,当同一个对象p1在多个不同的线程中调用这个方法时,他们之间会形成互斥,因为它们的锁都是p1这个对象锁。同时如果该对象中有多个同步方法,则当一个线程执行该对象中的一个synchronized方法,则该对象中其它同步方法也被锁住,不允许别的线程执行。但是同一个类的另一对象p2却不受p1锁的影响,因为p2对象中的同步方法使用的锁是p2。程式在这种情形下很可能摆脱同步机制的控制,造成数据混乱,可以通过单例模式来解决。
上边的示例代码等同于如下代码:
public void method() {
synchronized (this)
{
//
}
}
上面的代码使用this作为锁,也就是使用当前对象作为锁,也是对象锁。
2,synchronized修饰代码块
public void method(Object o) {
synchronized(o)
{
//..
}
}
这时,采用的也是对象锁,但锁不再是当前对象,而是对象o,哪个线程拿到这个对象锁就能够运行它所控制的那段代码。一般只需创建一个特别的instance变量来充当锁,这样最为节省资源:
private byte[] lock = new byte[0];
public void method(){
synchronized(lock)
{
//
}
}
3,synchronized修饰static函数
public class Foo {
public synchronized static void method1(){
//
}
public void method2(){
synchronized(Foo.class)
//
}
}
这两个同步方法都利用类作为锁,也就是类锁,而不再是由某个具体对象了。
三,总结
假设有类P,及其两个对象p1和p2,并发线程t1和t2,有如下结论:
使用synchronized(this)或synchronized方法时(即使用类P的对象锁时)
1,线程t1和t2并发访问同一个对象p1的一个具有对象锁的同步代码块或方法时,同一时间只能有一个线程得到执行,另一个线程必须等待当前线程执行完`这个代码块或方法`以后才能执行`该代码块或方法`;
2,然而,当t1访问p1的一个具有对象锁的同步代码块或方法时,t2仍然可以访问p1的其它不是用p1作为锁的同步代码块或方法。
3,尤其关键的是,当t1访问p1的一个具有对象锁的同步代码块或方法时,t2对p1中所有其它用p1作为锁的同步代码块或方法的访问将被阻塞。
注:上面说的“具有对象锁的代码块”,都是指具有`当前类的对象`作为锁的代码块,而不是其它类的对象。
使用synchronized(P.class)或synchronized static方法时(即使用类锁时)
1,t1访问p1的一个具有类锁的同步代码块或方法时,其他线程对p1的所有具有当前类锁的同步代码块或的访问将被阻塞;
2,t1访问类P的一个具有类锁的同步代码块或方法时,其他线程对类P的所有具有类锁的同步代码块或方法的访问将被阻塞。
3,t1访问类P的一个具有类锁的同步代码块或方法时,其他线程对类P的所有实例对象(如p1,p2)的所有具有类锁的同步代码块或方法的访问将被阻塞。
具体实例可参见https://github.com/ricklkl/demo/tree/master/src/main/java/com/rick/demo/synchron
综上所见,是否被阻塞的关键是锁是否被占用,对象锁尤其如此;而类锁的级别更高,所有类的对象以及类,都共用一个类锁。