synchronized是Java同步的基础,只有彻底了解synchronized的对象锁、可重入锁...特性,将来在使用高阶的ReentrantLock、ReentrantReadWriteLock才能得心应手
本文将透过大量的具体实例来了解synchronized的特性
-
具体实例
-
假设现在有一个MyObject对象myObject,他有methodA()和methodB()方法,创造两个线程Thread1 和 Thread2 ,使他们都去执行这个myObject对象的methodA方法,代码如下
class MyObject { public void methodA() { System.out.println("begin method A, 谁执行我: " + Thread.currentThread().getName()); System.out.println("end"); } public void methodB() { System.out.println("begin method B, 谁执行我: " + Thread.currentThread().getName()); System.out.println("end"); } } class Thread1 extends Thread { private MyObject object; Thread1(MyObject object) { this.object = object; } @Override public void run() { object.methodA(); // thread 1 执行 myObject 对象的 methodA() 方法 } } class Thread2 extends Thread { private MyObject object; Thread2(MyObject object) { this.object = object; } @Override public void run() { object.methodA(); // thread 2 也执行 myObject 对象的 methodA() 方法 } } public class Main { public static void main(String[] args) { MyObject object = new MyObject(); Thread1 th1 = new Thread1(object); th1.setName("thread 1"); Thread2 th2 = new Thread2(object); th2.setName("thread 2"); th1.start(); th2.start(); } }
-
因为methodA()方法上没有加上
synchronized
关键字进行线程同步,所以thread1和thread2会争抢著执行methodA(),所以结果如下begin methodA, 谁执行我: thread 1 begin methodA, 谁执行我: thread 2 end end
-
这时如果在methodA上加上
synchronized
关键字,thread1和thread2会一个一个排队执行methodA,结果如下class MyObject { synchronized public void methodA() { ... } public void methodB() { ... } }
begin method A, 谁执行我: thread 1 end begin method A, 谁执行我: thread 2 end
-
这时如果再新增一个对象MyObject对象myObject2,让Thread2改为执行myObject2对象的mehodA()方法,则结果如下
-
会使得结果又变为不同步的原因是因为
synchronized
其实是一个对象锁,而不是代码锁 -
意思是指假设当Thread1开始执行myObject的methodA()时,实际上是锁住了myObject这个对象的methodA()方法,而不是锁住了class MyObject们的methodA()方法
-
所以在jvm中,若是创建了多个实例对象,则会产生多把锁,每个对象自己一把锁,对象间的锁彼此不干扰
-
而在这个例子中,thread1取得的是myObject的对象锁,而thread2取得的是myObject2的对象锁,因为他们不是使用同一把锁,所以才会使得线程不同步
class MyObject { synchronized public void methodA() { ... } public void methodB() { ... } } class Thread1 extends Thread { public void run(){ object.methodA(); //thread 1 执行的是 myObject 对象的methodA()方法 } } class Thread2 extends Thread { public void run(){ object.methodA(); //thread 2 执行的是 myObject2 对象的methodA()方法 } }
begin method A, 谁执行我: thread 1 begin method A, 谁执行我: thread 2 end end
-
-
如果将myObject2删了,改回Thread1和Thread2都使用myObject这个对象,并且改成Thread1执行methodA(),Thread2执行methodB(),注意methodA有
synchronized
,但methodB没有,其结果如下-
会使得线程又不同步的原因是因为methodB是一个非同步的方法,即是methodB没有加上
synchronized
关键字 -
因此虽然thread1已经先抢到
synchonized
锁了,但是因为methodB不是一个线程同步的方法,因此他根本不care什麽对象锁,直接就能够被调用了
class MyObject { synchronized public void methodA() { ... } public void methodB() { ... } } class Thread1 extends Thread { public void run(){ object.methodA(); //thread 1 执行myObject对象的 methodA() 方法 } } class Thread2 extends Thread { public void run(){ object.methodB(); //thread 2 执行myObject对象的 methodB() 方法 } }
begin method A, 谁执行我: thread 1 begin method B, 谁执行我: thread 2 end end
-
-
尝试将methodB()也加上
synchronized
关键字,而Thread1仍旧是执行myObject的methodA()方法,Thread2仍旧执行myObject的methodB()方法,结果如下-
Thread1明明调用的是methodA,但是为什麽会使得同个对象的其他
synchronized
方法受到影响呢?为什麽会使得结果同步? -
原因是因为
synchronized
是一个对象锁,所以当Thread1取得锁开始执行methodA时,他实际上其实是取得了myObject这个对象的synchronized
锁 -
因此当Thread2想要开始执行methodB时,他会去看myObject这个对象的
synchronized
锁被取走了没,而他就会发现已经被Thread1取走了,因此他必须等待Thread1完成后,才能去取得myObject的对象锁,去执行methodB,所以结果才会是同步的
class MyObject { synchronized public void methodA() { ... } synchronized public void methodB() { ... } } class Thread1 extends Thread { public void run(){ object.methodA(); //thread 1 执行myObject对象的 methodA() 方法 } } class Thread2 extends Thread { public void run(){ object.methodB(); //thread 2 执行myObject对象的 methodB() 方法 } }
begin method A, 谁执行我: thread 1 end begin method B, 谁执行我: thread 2 end
-
-
如果把Thread1和Thread2都改成执行methodA方法,不过将methodA方法改成他还会去调用methodB方法,注意methodA和methodB方法都有加上
synchronized
关键字,结果如下-
可以看到当Thread1抢到myObject对象的锁执行methodA到一半,当他想要执行methodB时,他就必须去取得methodB的对象锁,也就是myObject对象锁,也就是他当初近来methodA时已经拿到的锁
-
由于Thread1已经拿到了这个锁,所以当他再次取得这个对象锁时,能够再次取到,这就是锁重入性
-
这也说明当一个
synchronized
方法methodA的内部调用本类的其他synchronized
方法methodB时,是永远可以得到锁的,因为当初进入methodA时此线程就已经得过对象锁了
class MyObject { synchronized public void methodA() { System.out.println("begin method A"); methodB(); //methodA去调用同个对象的另一个synchronized方法methodB System.out.println("end"); } synchronized public void methodB() { System.out.println("begin method B"); System.out.println("end"); } } class Thread1 extends Thread { public void run(){ object.methodA(); //thread 1 执行myObject对象的 methodA() 方法 } } class Thread2 extends Thread { public void run(){ object.methodA(); //thread 2 执行myObject对象的 methodA() 方法 } }
begin method A, 谁执行我: thread 1 begin method B, 谁执行我: thread 1 end end begin method A, 谁执行我: thread 2 begin method B, 谁执行我: thread 2 end end
-
-
-
根据以上实例,小结
synchronized
关键字的特性-
synchronized
是一个对象锁-
假设thread1线程已经持有某object对象的synchronized lock
-
那麽thread2仍然可以调用此object对象中的非同步的方法,即是thread2可以直接调用那些没有加上sychronized关键字的方法,不需要等待
-
但是thread2如果调用了加上synchronized关键字的方法,就必须要等thread1释放lock才行
-
-
synchronized
对象锁具有锁重入性-
当一个线程取得某object对象的对象锁时,再次请求此对象锁是可以再次得到该对象锁的
-
也就是说,当一个
synchronized
方法methodA的内部调用本类的其他synchronized
方法methodB时,是永远可以得到锁的,因为当初进入methodA时此线程就已经得过对象锁了
-
-
当线程在执行
synchronized
方法时出现异常,对象锁会自动释放 -
同步不具有继承性
-
假设Parent类中有一个
synchronized public void hello()
方法,那麽继承到Child类中时此方法会变成public void hello()
-
如果Child也想要让此方法变成同步方法,那麽必须手动加上
synchronized
关键字才行
-
-
-
具体实例二
-
到刚才为止,我们都是将
synchronized
修饰在方法上,但是直接修饰方法可能会使得critical section范围设置过大,而真正要同步的区块其实没有这麽大,因此synchronized
还支持修饰代码块,或是修饰一个变量-
使用
synchronized (this)
可以同步某一段代码块,而这个方式也是一个对象锁,此例就是会锁住myObject -
要注意只有在
synchronized (this)
裡的代码是同步的,其他地方都是异步的-
所以当thread1执行到methodA的运作很久的方法时,因为他没有被synchronized修饰,所以是异步的,所以他不会去管什麽锁,直接就能执行了
-
但是当thread1执行到
synchronized (this)
时,他会想要去取得myObject的对象锁,而他会发现已经被thread2的methodB取走了,所以他要等thread2释放对象锁之后,才能开始执行synchronized (this)
裡面的代码块
-
class MyObject { public void methodA() { System.out.println("method A 运作很久的一个方法"); //这是异步的 synchronized (this) { System.out.println("begin method A"); System.out.println("end"); } } synchronized public void methodB() { System.out.println("begin method B"); System.out.println("end"); } } class Thread1 extends Thread { public void run(){ object.methodA(); //thread 1 执行myObject对象的 methodA() 方法 } } class Thread2 extends Thread { public void run(){ object.methodB(); //thread 2 执行myObject对象的 methodB() 方法 } }
method A 运作很久的一个方法 begin method B end begin method A end
-
-
接续刚才的例子,但是将
sychronized
改成用来修饰一个变量,methodA去同步myObject对象裡的变量x,methodB去同步myObject对象裡的变量y-
可以看到结果是不同步的,原因是因为当
synchonized
修饰变量时,他的锁会变成这个变量对象的锁,而不是myObject这个对象的锁了 -
因此methodA取得的是myObject中的x的这个对象锁,而methodB取得的是myObject中的y的对象锁,因为他们拿的是两个不同的锁,所以他们才不会被同步
class MyObject { private String x = new String(); private String y = new String(); public void methodA() { synchronized (x) { System.out.println("begin method A"); System.out.println("end"); } } public void methodB() { synchronized (y) { System.out.println("begin method B"); System.out.println("end"); } } } class Thread1 extends Thread { public void run(){ object.methodA(); //thread 1 执行myObject对象的 methodA() 方法 } } class Thread2 extends Thread { public void run(){ object.methodB(); //thread 2 执行myObject对象的 methodB() 方法 } }
begin method A begin method B end end
-
-
如果再新增一个myObject2,让thread1执行myObject的methodA,thread2执行myObject2的methodA方法,结果如下
-
结果会是不同步的,原因是因为
synchronized
是一个对象锁,thread1取得的是myObject中的x的对象锁,thread2取得的是myObject2中的x对象锁,是两个锁,所以才会不同步
class MyObject { private String x = new String(); public void methodA() { synchronized (x) { System.out.println("begin method A, 谁执行我: " + Thread.currentThread().getName()); System.out.println("end"); } } public void methodB() { ... } } class Thread1 extends Thread { public void run(){ object.methodA(); //thread 1 执行 myObject 对象的 methodA() 方法 } } class Thread2 extends Thread { public void run(){ object.methodA(); //thread 2 执行 myObject2 对象的 methodA() 方法 } }
begin method A, 谁执行我: thread 1 end begin method A, 谁执行我: thread 2 end
-
-
如果将myObject2删了,改回Thread1和Thread2都使用myObject这个对象,并且改回Thread1执行methodA,Thread2执行methodB,然后methodA去同步一个变量对象
x
,但methodB去同步一个类对象myObject
,结果如下-
结果会是不同步的,是因为他们其实取的锁是不同的锁,methodA取得的是myObject中的变量锁x,而methodB是取得myObject锁
-
所以可以知道,就算取得了类对象
myObject
的锁,也不会影响其中变量x
的锁,也就是说并非取得对象锁myObject就会把所有他的变量锁x
也都全部锁起来,他们之间是独立的两个锁,分别是myObject
和x in myObject
-
所以假设又来了一个新的对象myObject2,那麽锁就有4把,分别是
myObject
、x in myObject
、myObject2
、x in myObject2
class MyObject { private String x = new String(); public void methodA() { synchronized (x) { System.out.println("begin method A"); System.out.println("end"); } } public void methodB() { synchronized (this) { System.out.println("begin method B"); System.out.println("end"); } } } class Thread1 extends Thread { public void run(){ object.methodA(); //thread 1 执行myObject对象的 methodA() 方法 } } class Thread2 extends Thread { public void run(){ object.methodB(); //thread 2 执行myObject对象的 methodB() 方法 } }
begin method A begin method B end end
-
-
使用
synchronized
修饰String变量时,要小心jvm中的String常量池特性-
jvm中的String常量池缓存会导致String指向同个对象,像是a、b预期中应该是两个
"1"
对象,但是因为String常量池的缓存影响,所以a、b储存的是同个对象,才导致a == b 输出是truepublic class Test { public static void main() { String a = "1"; String b = "1"; System.out.println(a == b); //输出true } }
-
将methodA改成使用
synchornized
修饰传进来的input对象,只有当传进来的对象一样,那麽他们取得的锁才会是同个对象锁,由于因为String常量池的关系,Thread1和Thread2传进来的"xx"
会被视为视同个对象,所以才会使得结果是同步的class MyObject { public void methodA(String input) { synchronized (input) { System.out.println("begin method A, 谁执行我: " + Thread.currentThread().getName()); System.out.println("end"); } } public void methodB() { ... } } class Thread1 extends Thread { public void run(){ object.methodA("xx"); //thread 1 执行myObject对象的 methodA() 方法 } } class Thread2 extends Thread { public void run(){ object.methodA("xx"); //thread 2 执行myObject对象的 methodA() 方法 } }
begin method A, 谁执行我: thread 1 end begin method A, 谁执行我: thread 2 end
-
如果传进来的对象改成别的不是String类型的对象,那麽就不会有因为缓存而带来的这个问题,因此Thread1和Thread2传进来的对象不会是同一个对象,因此他们取得的锁也不是同把锁,所以才会使得结果是不同步的
class MyObject { public void methodA(MyType type) { synchronized (type) { System.out.println("begin method A, 谁执行我: " + Thread.currentThread().getName()); System.out.println("end"); } } public void methodB() { ... } } class Thread1 extends Thread { public void run(){ object.methodA(new MyType()); } } class Thread2 extends Thread { public void run(){ object.methodA(new MyType()); } }
begin method A, 谁执行我: thread 1 begin method A, 谁执行我: thread 2 end end
-
-
synchronized
除了修饰方法、变量、代码块,也可以用来修饰静态方法static
,如果synchronized
加在static
方法上,那就是加上了一个class锁,和使用synchronized(MyObject.class)
是一样的,都是要去取得class锁-
运行结果是同步的,原因就是因为Thread1和Thread2都想要去取得MyObject的class锁,所以才会使得结果同步
class MyObject { synchronized public static void methodA() { System.out.println("begin static method A"); System.out.println("end"); } public void methodB() { synchronized (MyObject.class) { System.out.println("begin method B"); System.out.println("end"); } } } class Thread1 extends Thread { @Override public void run() { MyObject.methodA(); //thread 1 执行 MyObject.methodA() 静态方法 } } class Thread2 extends Thread { @Override public void run() { object.methodB(); //thread 2 执行myObject对象的 methodB() 方法 } }
begin static method A end begin method B end
-
-
假设新增一个对象myObject2,并且改成Thread1执行myObject的methodB,Thread2执行myObject2的methodB,结果如下
-
结果会是同步的,原因是因为class锁可以对所有对象的起作用,也就是所有这个类的对象们myObject、myObject2...只有一份class锁MyObject.class,因此不管生成再多对象,他们使用的都是同一个class锁,所以结果才会是同步的
class MyObject { synchronized public static void methodA() { ... } public void methodB() { synchronized (MyObject.class) { System.out.println("begin method B, 谁执行我: " + Thread.currentThread().getName()); System.out.println("end"); } } } class Thread1 extends Thread { @Override public void run() { object.methodA(); //thread 1 执行 myObject 对象的 methodB() 方法 } } class Thread2 extends Thread { @Override public void run() { object.methodB(); //thread 2 执行 myObject2 对象的 methodB() 方法 } }
begin method B, 谁执行我: thread 1 end begin method B, 谁执行我: thread 2 end
-
-
不过要注意,class锁、myObject对象锁、变量x对象锁,他们彼此之间都是互相独立的,所以取得了class锁不代表可以畅行无阻myObject对象锁和变量x对象锁
-
把刚刚的myObject2删掉,让Thread1执行myObject的methodA,Thread2执行myObject的methodB,Thread3执行myObject的methodC
-
结果是不同步的是因为methodA、methodB、methodC各自用了3种不同的锁
-
methodA是静态方法,所以他使用的是class锁
-
methodB使用的是myObject对象锁
-
methodC使用的是变量x in myObject对象锁
-
class MyObject { private String x = new String(); synchronized public static void methodA() { System.out.println("begin static method A"); System.out.println("end"); } synchronized public void methodB() { System.out.println("begin method B"); System.out.println("end"); } public void methodC() { synchronized (x) { System.out.println("begin method C"); System.out.println("end"); } } } class Thread1 extends Thread { @Override public void run() { MyObject.methodA(); //thread 1 执行 MyObject.methodA() 静态方法 } } class Thread2 extends Thread { @Override public void run() { object.methodB(); //thread 2 执行myObject对象的 methodB() 方法 } } class Thread3 extends Thread { @Override public void run() { object.methodC(); //thread 3 执行myObject对象的 methodC() 方法 } }
begin static method A begin method B begin method C end end end
-
-
-
根据以上实例,总结
synchronized
关键字的特性-
synchronized
对象锁具有锁重入性-
也就是当一个线程取得某object对象的对象锁时,再次请求此对象锁是可以再次得到该对象锁的
-
-
当线程在执行
synchronized
方法时出现异常,对象锁会自动释放 -
同步不具有继承性
-
synchronized
不仅可以用在方法上,也能用在变量、代码块、静态方法上-
class锁、myObject对象锁、x变量 in myObject的对象锁、y变量 in myObject的对象锁,彼此间都是独立的,并非取得myObject对象锁就能够取得所有他其中的变量的对象锁,也并非取得class锁就能取得所有对象myObject、myObject2...的对象锁
-
class锁可以对所有对象的起作用,也就是所有这个类的对像们myObject、myObject2...只有一份class锁
-
-
使用
synchronized
在String变量上时,要注意String的常量池特性 -
就算改了某个对象中的某个属性,像是myObject.setInfo("123),只要myObject对象不改变,那麽
synchronized
运行的结果仍然会是同步的
-