一.synchronized(对象) — 对象锁
假设类对象instance的某段代码块被synchronized(obj){}包裹,线程访问该段代码块时便会拿到obj对象的内置锁。在obj对象的内置锁释放前,其他线程仍然可以访问instance对象非同步的方法和代码块(现象一),但是:
1. 不能进入任何也以obj为锁的代码块;(现象二)
2. 当obj与instance是同一个对象时,也不能进入任何instance对象的同步方法(现象三)
由此可见,第一个线程拿到obj对象的内置锁其实就相当于给instance这个对象加上了一个用于同步的独占排他锁(可重入),我们称obj对象为对象锁。
特别说明:对于不同的两个对象,只有当请求的对象锁相同时,线程间才会产生竞争!其他情况下, 并不会互相影响。(现象四)
a.验证现象一
为类SynchronizedVerify添加如下main()方法:
public static void main(String[] args){
final SynchronizedVerify sv = new SynchronizedVerify();
new Thread(){//定为线程一
public void run() { sv.blockStyle(); };
}.start();
new Thread(){//定为线程二
public void run() { sv.blockNormal(); };
}.start();
}
sv.blockStyle() 以私有成员ObjA为对象锁
+ sv.blockNormal() 非同步方法且不含同步代码块
输出(无竞争):
blockStyle:0 |Thread-0
blockNormal:0 |Thread-1
blockStyle:1 |Thread-0
blockNormal:1 |Thread-1
blockNormal:2 |Thread-1
blockStyle:2 |Thread-0
b.验证现象二
为类SynchronizedVerify添加如下main()方法:
public static void main(String[] args){
final SynchronizedVerify sv = new SynchronizedVerify();
new Thread(){//定为线程一
public void run() { sv.blockStyle(); };
}.start();
new Thread(){//定为线程二
public void run() { sv.blockContrast(); };
}.start();
}
sv.blockStyle() 以私有成员ObjA为对象锁
+ sv.blockContrast() 以私有成员ObjA为对象锁
输出(竞争):
blockStyle:0 |Thread-0
blockStyle:1 |Thread-0
blockStyle:2 |Thread-0
blockContrast:0 |Thread-1
blockContrast:1 |Thread-1
blockContrast:2 |Thread-1
将线程二的调用改为sv.blockDiffObj() 以私有成员ObjB为对象锁
sv.blockStyle() + sv.blockDiffObj()输出(无竞争。多尝试几遍,或者加大输出量):
blockStyle:0 |Thread-0
blockStyle:1 |Thread-0
blockDiffObj:0 |Thread-1
blockStyle:2 |Thread-0
blockDiffObj:1 |Thread-1
blockDiffObj:2 |Thread-1
组合sv.blockStyle() + sv.blockDiffObj()中,两个方法体都用synchronized包裹。但所施加的对象锁一个是objA一个是objB,这是两个对象,所以线程进入两个方法的同步代码块时并不会产生竞争。
c. 验证现象三
为类SynchronizedVerify添加如下main()方法:
public static void main(String[] args){
final SynchronizedVerify sv = new SynchronizedVerify();
new Thread(){//定为线程一
public void run() { sv.blockStyle(); };
}.start();
new Thread(){//定为线程二
public void run() { sv.methodStyle(); };
}.start();
}
sv.blockStyle() 以私有成员ObjA为对象锁
+ sv.methodStyle() 同步成员方法
输出(无竞争):
blockStyle:0 |Thread-0
methodStyle:0 |Thread-1
blockStyle:1 |Thread-0
methodStyle:1 |Thread-1
methodStyle:2 |Thread-1
blockStyle:2 |Thread-0
线程一调用方法改为sv.blockOneself() 以this为对象锁
sv.blockOneself() + sv.methodStyle()输出(竞争):
blockOneself:0 |Thread-0
blockOneself:1 |Thread-0
blockOneself:2 |Thread-0
methodStyle:0 |Thread-1
methodStyle:1 |Thread-1
methodStyle:2 |Thread-1
当线程进入某个对象的同步方法时,实际上是拿这个对象的内置锁作为对象锁。(详情见场景二:synchronized修饰方法)
blockOneself()以this为锁锁住代码块,等同于拿当前对象的内置锁作为对象锁。所以当其他线程再进入这个对象的blockOneself()时,因为第一个线程已经独占了对象锁,随后的线程只能阻塞。
d. 验证现象四
在这里使用类SameObjLockVerify来验证,类定义如下:
public class SameObjLockVerify {
public static Object staticObj = new Object();
public void objLock(Object obj){
synchronized (obj) { track("objLock");}
}
public void staticObjLock(){
synchronized (SameObjLockVerify.staticObj) { track("staticObjLock");}
}
/**循环输出一段内容**/
private static void track(String callerName){
for(int i = 0;i < 3 ;i++)
System.out.println(
callerName+":"+i+"\t|"+
Thread.currentThread().getName());
Thread.yield();
}
}
如果有若干个不同的对象,这些对象中没有一个同步代码块使用与其他对象相同的对象锁。那么多线程访问这些对象,线程间无论如何都不会相互影响。(自行验证)
这里反向模拟现象四,让我们来看看同一个对象obj作对象锁时,不同线程进入不同对象solv1、solv2时有无竞争?
为上述SameObjLockVerify类添加如下main方法:
public static void main(String[] args){
final SameObjLockVerify solv1 = new SameObjLockVerify();
final SameObjLockVerify solv2 = new SameObjLockVerify();
final Object objLock = new Object();
new Thread(){//定为线程一
public void run() { solv1.objLock(objLock); };
}.start();
new Thread(){//定为线程二
public void run() { solv2.objLock(objLock); };
}.start();
}
solv1.objLock(objLock)+solv2.objLock(objLock)输出(竞争):
objLock:0 |Thread-0
objLock:1 |Thread-0
objLock:2 |Thread-0
objLock:0 |Thread-1
objLock:1 |Thread-1
objLock:2 |Thread-1
或者这样:
public static void main(String[] args){
final SameObjLockVerify solv1 = new SameObjLockVerify();
final SameObjLockVerify solv2 = new SameObjLockVerify();
new Thread(){//定为线程一
public void run() { solv1.staticObjLock(); };
}.start();
new Thread(){//定为线程二
public void run() { solv2.staticObjLock(); };
}.start();
}
solv1.staticObjLock() + solv2.staticObjLock()输出(竞争):
staticObjLock:0 |Thread-0
staticObjLock:1 |Thread-0
staticObjLock:2 |Thread-0
staticObjLock:0 |Thread-1
staticObjLock:1 |Thread-1
staticObjLock:2 |Thread-1
即使不是同一个类对象,只要synchronized(对象锁){}使用了相同的对象锁,就会造成多线程的资源竞争。
二、synchronized(Class对象) — 类锁
类锁:是相对于对象锁而抽象出来的一种独占排他锁。当线程拿到一个类A的类锁时,其他线程无法访问以类A为锁(A.class)的同步代码块,也无法进入类A的所有静态同步方法。
类锁其实并不真实存在。所谓类锁,实际上是限制对象仅为Class类对象的对象锁。因此当我们以类A为类锁锁定类A,以类A对象a为对象锁锁定对象a时,两个线程分别访问类A锁定的代码块、对象a锁定的代码块时,并不会产生竞争。(现象五)
a.验证现象五
依旧使用类SynchronizedVerify验证,为其添加如下main方法:
public static void main(String[] args){
final SynchronizedVerify sv = new SynchronizedVerify();
new Thread(){//定为线程一
public void run() { sv.blockOneself(); };
}.start();
new Thread(){//定为线程二
public void run() { sv.blockClass(); };
}.start();
}
sv.blockOneself() + sv.blockClass()输出(无竞争):
blockOneself:0 |Thread-0
blockClass:0 |Thread-1
blockClass:1 |Thread-1
blockClass:2 |Thread-1
blockOneself:1 |Thread-0
blockOneself:2 |Thread-0
本文的第一部分,已经验证了在synchronized包裹代码块的场景下,若干同步代码块只要synchronized(对象){}设定的锁相同,多线程进入这若干同步代码块就会产生竞争。既然类锁是特殊的对象锁,那么在此便不验证这点。
线程进入同步成员方法,需要申请的是该同步成员方法对应对象的内置锁作为对象锁。而线程进入同步静态方法,需要申请的是该类对应的Class类对象的内置锁。这使得synchronized(A.class){}代码块 与 A类的同步静态方法前后被多线程访问时,需要申请同一个锁,于是便产生了竞争。(现象六)
b.验证现象六
为SynchronizedVerify类添加如下main()方法:
public static void main(String[] args){
final SynchronizedVerify sv = new SynchronizedVerify();
new Thread(){//定为线程一
public void run() { sv.blockClass(); };
}.start();
new Thread(){//定为线程二
public void run() { SynchronizedVerify.methodStatic(); };
}.start();
}
sv.blockClass() + SynchronizedVerify.methodStatic()输出(竞争):
blockClass:0 |Thread-0
blockClass:1 |Thread-0
blockClass:2 |Thread-0
methodStatic:0 |Thread-1
methodStatic:1 |Thread-1
methodStatic:2 |Thread-1