(转)Synchronized(对象锁)和Static Synchronized(类锁)的区别
场景:面试的时候经常用得到!
1 综述
Synchronized和Static Synchronized区别
一个是实例锁(锁在某一个实例对象上,如果该类是单例,那么该锁也具有全局锁的概念),一个是全局锁(该锁针对的是类,无论实例多少个对象,那么线程都共享该锁)。
实例锁对应的就是synchronized关键字,而类锁(全局锁)对应的就是static synchronized(或者是锁在该类的class或者classloader对象上)。
注: static 说明了该类的一个静态资源,不管new了多少个对象,只有一份,所以对该类的所有对象都加了锁!(实践才能更好的理解)
实例锁是锁特定的实例(只要有synchronized就会去锁该实例),全局锁是锁所有的实例。
synchronized是对类的当前实例(当前对象)进行加锁,防止其他线程同时访问该类的该实例的所有synchronized块(注:是所有),注意这里是“类的当前实例”, 类的两个不同实例就没有这种约束了。
static synchronized恰好就是要控制类的所有实例的并发访问,static synchronized是限制多线程中该类的所有实例同时访问jvm中该类所对应的代码块。
也就是说synchronized相当于 this.synchronized,而static synchronized相当于Something.synchronized.(后面又讲解)
2 分析
2.1 synchronized和static synchronized
百看不如一练,上代码先:
/** * Project Name:Spring0725 * File Name:TestSynchronized.java * Package Name:work1128.singleton * Date:2017年11月28日下午3:44:24 * Copyright (c) 2017, 深圳金融电子结算中心 All Rights Reserved. * */ package work1128.singleton; /** * ClassName:TestSynchronized <br/> * Function: 测试实例锁和类锁 * Date: 2017年11月28日 下午3:44:24 <br/> * @author prd-lxw * @version 1.0 * @since JDK 1.7 * @see */ public class TestSynchronized { public void test1() { synchronized(this) { int i = 5; while( i-- > 0){ System.out.println(Thread.currentThread().getName() + " : " + i); try{ Thread.sleep(500); } catch (InterruptedException ie){ } } } } public synchronized void isSyncA() { int i = 5; while( i-- > 0){ System.out.println(Thread.currentThread().getName() + " : " + i); try { Thread.sleep(500); } catch (InterruptedException ie){ } } } public synchronized void isSyncB(){ int i = 5; while( i-- > 0){ System.out.println(Thread.currentThread().getName() + " : " + i); try { Thread.sleep(500); }catch (InterruptedException ie){ } } } public static synchronized void cSyncA(){ int i = 5; while( i-- > 0) { System.out.println(Thread.currentThread().getName() + " : " + i); try{ Thread.sleep(500); }catch (InterruptedException ie){ } } } public static synchronized void cSyncB() { int i = 5; while( i-- > 0) { System.out.println(Thread.currentThread().getName() + " : " + i); try{ Thread.sleep(500); }catch (InterruptedException ie){ } } } public static void main(String[] args) { final TestSynchronized myt1 = new TestSynchronized(); final TestSynchronized x = new TestSynchronized(); final TestSynchronized y = new TestSynchronized(); //同一个实例,不同的synchronized方法,对象锁有约束(同一个对象——对象锁)——a. x.isSyncA()与x.isSyncB() /*Thread test1 = new Thread(new Runnable() { public void run() { x.isSyncA(); } }, "test1"); Thread test2 = new Thread(new Runnable() { public void run() { x.isSyncB(); } }, "test2"); */ //不同的实例,同一个synchronized方法,对象锁没有约束(不同的对象——对象锁)——b. x.isSyncA()与y.isSyncA() /* Thread test1 = new Thread(new Runnable() { public void run() { x.isSyncA(); } }, "test1"); Thread test2 = new Thread(new Runnable() { public void run() { y.isSyncA(); } }, "test2");*/ //不同的实例,不同的static synchronized方法,类锁具有约束(不同的对象,类锁)c. x.cSyncA()与y.cSyncB() /* Thread test1 = new Thread(new Runnable() { public void run() { x.cSyncA(); } }, "test1"); Thread test2 = new Thread(new Runnable() { public void run() { y.cSyncB(); } }, "test2");*/ //不同的实例,相同的static synchronized方法,类锁具有约束(不同的对象,类锁)c1. x.cSyncA()与y.cSyncA() Thread test1 = new Thread(new Runnable() { public void run() { x.cSyncA(); } }, "test1"); Thread test2 = new Thread(new Runnable() { public void run() { y.cSyncA(); } }, "test2"); //与实例无关,对象锁和类锁互不影响——d. x.isSyncA()与Something.cSyncA() /*Thread test1 = new Thread(new Runnable() { public void run() { x.isSyncA(); } }, "test1"); Thread test2 = new Thread(new Runnable() { public void run() { y.cSyncA(); } }, "test2");*/ test1.start(); test2.start(); } }
主要看是 this.synchronized 还是something.synchronized, 加锁不区分锁的位置!!!!!
this.synchronized 还是something.synchronized是两种不同的锁,互不影响!!!!
那么,假如有Something类的两个实例x与y,那么下列各组方法被多线程同时访问的情况是怎样的?
a. x.isSyncA()与x.isSyncB() b. x.isSyncA()与y.isSyncA() c. x.cSyncA()与y.cSyncB()
c1. x.cSyncA()与y.cSyncA() d. x.isSyncA()与Something.cSyncA()
这里,很清楚的可以判断:
a,都是对同一个实例(x)的synchronized域访问,因此不能被同时访问。(多线程中访问实例x的不同synchronized域不能同时访问)不管锁的是同一个方法与否,有synchronized的地方就会锁该实例。
x.isSyncA()与x.isSyncB()
如果在多个线程中访问x.isSyncA(),因为仍然是对同一个实例,且对同一个方法加锁,所以多个线程中也不能同时访问。(多线程中访问x的同一个synchronized域不能同时访问)
ps:多线程中,只要是同一个对象,synchronized不管锁多少方法,对象锁都起作用。
b,是针对不同实例的,因此可以同时被访问(对象锁对于不同的对象实例没有锁的约束)
x.isSyncA()与y.isSyncA()
ps:多线程中,不是同一个对象,对象锁没有约束。
c,因为是static synchronized,所以不同实例之间仍然会被限制,相当于Something.isSyncA()与 Something.isSyncB()了,因此不能被同时访问。(注意)
x.cSyncA()与y.cSyncB()
ps:多线程中,不同的对象,类锁具有约束性。
c1 不同的实例,相同的static synchronized方法,类锁具有约束(不同的对象,类锁)c1. x.cSyncA()与y.cSyncA()
test1 : 4 test1 : 3 test1 : 2 test1 : 1 test1 : 0 test2 : 4 test2 : 3 test2 : 2 test2 : 1 test2 : 0
那么,第d呢?,书上的 答案是可以被同时访问的,答案理由是synchronzied的是实例方法与synchronzied的类方法由于锁定(lock)不同的原因。
x.isSyncA()与Something.cSyncA()
(x.isSyncA()与x.cSyncA())
ps:对象锁与类锁互不干扰,与对象无关!
个人分析也就是synchronized 与static synchronized 相当于两帮派,各自管各自,相互之间就无约束了,可以被同时访问。
其实总结起来很简单:
- 一个锁的是类对象,一个锁的是实例对象。
- 若类对象被lock,则类对象的所有同步方法全被lock;
- 若实例对象被lock,则该实例对象的所有同步方法全被lock
3 synchronized methods(){} 与synchronized(this){}
synchronized methods(){} 与synchronized(this){}之间没有什么区别。
只是synchronized methods(){} 便于阅读理解,而synchronized(this){}可以更精确的控制冲突限制访问区域,有时候表现更高效率。
synchronized 方法的缺陷:若将一个大的方法声明为synchronized 将会大大影响效率,典型地,若将线程类的方法 run() 声明为 synchronized ,由于在线程的整个生命期内它一直在运行,因此将导致它对本类任何 synchronized 方法的调用都永远不会成功。当然我们可以通过将访问类成员变量的代码放到专门的方法中,将其声明为 synchronized ,并在主方法中调用来解决这一问题,但是 Java 为我们提供了更好的解决办法,那就是 synchronized 块。
synchronized 块:通过 synchronized关键字来声明synchronized 块。语法如下:
synchronized(syncObject) { //允许访问控制的代码 }
synchronized 块是这样一个代码块,其中的代码必须获得对象 syncObject (如前所述,可以是类实例或类)的锁方能执行,具体机制同前所述。由于可以针对任意代码块,且可任意指定上锁的对象,故灵活性较高。
两种方式效率比较:
3.1 同步块synchronized(this)
代码如下:
package test01; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class TestSynchronizedThis { /** * @param args */ public static void main(String[] args) { ExecutorService service = Executors.newCachedThreadPool(); final CountDownLatch cdOrder = new CountDownLatch(1); final CountDownLatch cdAnswer = new CountDownLatch(3); final SynchonizedClass sc = new SynchonizedClass(); for(int i=0; i<3; i++){ Runnable runnable = new Runnable(){ public void run() { try{ cdOrder.await(); //线程阻塞,等待主线程中执行cdOrder.countDown(); sc.started(); cdAnswer.countDown(); }catch(Exception e){ e.printStackTrace(); } } }; service.execute(runnable); //线程池执行其中的线程 } try{ Thread.sleep((long) (Math.random()*10000)); System.out.println("线程" + Thread.currentThread().getName() + "发布执行命令"); cdOrder.countDown(); //让线程池中的线程得以执行,下面主要是统计线程池中的线程得执行时间 long beginTime = System.currentTimeMillis(); System.out.println("线程" + Thread.currentThread().getName() + "已经发送命令,正在等待结果"); cdAnswer.await(); //等待线程池中的线程执行完毕 System.out.println("线程" + Thread.currentThread().getName() + "已收到所有响应结果,所用时间为:" + (System.currentTimeMillis()-beginTime)); }catch(Exception e){ e.printStackTrace(); } service.shutdown(); } } class SynchonizedClass{ public void started() throws InterruptedException{ Thread.sleep(100);//1 synchronized(this){ //同步代码块 //Thread.sleep(100);//2 System.out.println("我运行使用了 10 ms"); } } }
synchronizedClass 执行//1处的sleep(100):
synchronizedClass 执行//2处的sleep(100):
ps:上图的两种结果的原因在于Thread.sleep(100);是否参与了实例锁的等待过程:
//1 Thread.sleep(100)不在synchronized(this)的代码块中,不参与加锁机制;
//2 Thread.sleep(100)在synchronized(this)的代码块中,参与了加锁的过程。
3.2 同步方法synchronized method()
代码如下:
package test01; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class TestSynchronizedMethod { /** * @param args */ public static void main(String[] args) { ExecutorService service = Executors.newCachedThreadPool(); final CountDownLatch cdOrder = new CountDownLatch(1); final CountDownLatch cdAnswer = new CountDownLatch(3); final SynchonizedMethodClass sc = new SynchonizedMethodClass(); for(int i=0; i<3; i++){ Runnable runnable = new Runnable(){ public void run() { try{ cdOrder.await(); //线程阻塞,等待主线程中执行cdOrder.countDown(); sc.started(); cdAnswer.countDown(); //每执行一次started()方法,cdAnswer减少1 }catch(Exception e){ e.printStackTrace(); } } }; service.execute(runnable); //线程池执行其中的线程 } try{ Thread.sleep((long) (Math.random()*10000)); System.out.println("线程" + Thread.currentThread().getName() + "发布执行命令"); cdOrder.countDown(); //让线程池中的线程得以执行,下面主要是统计线程池中的线程得执行时间 long beginTime = System.currentTimeMillis(); System.out.println("线程" + Thread.currentThread().getName() + "已经发送命令,正在等待结果"); cdAnswer.await(); //等待线程池中的线程执行完毕 System.out.println("线程" + Thread.currentThread().getName() + "已收到所有响应结果,所用时间为:" + (System.currentTimeMillis()-beginTime)); }catch(Exception e){ e.printStackTrace(); } service.shutdown(); } } class SynchonizedMethodClass{ public synchronized void started() throws InterruptedException{ Thread.sleep(100);//执行其它逻辑消耗时间 // synchronized(this){ System.out.println("我运行使用了 10 ms"); // } } }
运行结果如下:
对比3.1代码1与3.1可以看到 ——synchronized methods(){} 与synchronized(this){} 两者相差:201ms。
总结:
其实这两种锁机制都是实例锁,出现时间相差的原因是,synchronized(this){}可以在方法内部部分加锁,同步机制更加灵活,可以设置不需要加锁的部分,故而效率会高些;
synchronized methods(){} 控制的是整个方法体,所以方法里面的所有内容都会参与加锁。
对比说明同步代码块比同步方法效率更高。
3.3 汇总
除了修饰方法之外,还可以修饰代码块,一共有以下5种用法。
一、this
synchronized(this){ //互斥代码 }
这里的this指的是执行这段代码的对象,synchronized得到的锁就是this这个对象的锁,这种写法等价于我们上一篇博客中讨论的:
public synchronized void func(){ //互斥代码 }
二、A.class
synchronized(A.class){ //互斥代码 }
这里A.class得到的是A这类,所以synchronized关键字得到的锁是类的锁,这种方法同下面的方法功能是相同的:
public static synchronized void fun(){ //互斥代码 }
所有需要类的锁的方法等不能同时执行,但是它和需要某个对象的锁的方法或者是不需要任何锁的方法可以同时执行。
三、object.getClass()
synchronized(object.getClass){ //互斥代码 }
这种方法一般情况下同第二种是相同,但是出现继承和多态时,得到的结果却是不相同的。所以一般情况下推荐使用A.class的方式。
四、object
synchronized(object){ //互斥代码 }
这里synchronized关键字拿到的锁是对象object的锁,所有需要这个对象的锁的方法都不能同时执行。
public class Trans { private Object lock = new Object(); public void printNum(int num){ synchronized (lock) { System.out.print(Thread.currentThread()); for(int i=0;i<25;i++){ System.out.print(i+" "); } System.out.println(); } } }
class MyThread implements Runnable { private Trans trans; private int num; public MyThread(Trans trans, int num) { this.trans = trans; this.num = num; } public void run() { while (true) { trans.printNum(num); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } } } public class Test { public static void main(String[] args) { Trans t = new Trans(); Trans t1 = new Trans(); Thread a = new Thread(new MyThread(t, 1)); Thread b = new Thread(new MyThread(t1, 2)); a.start(); b.start(); } }
在上边的例子中试图使用这种方法达到互斥方法打印方法,但是事实是这样做是没有效果的,因为每个Trans对象都有自己的Object对象,这两个对象都有自己的锁,所以两个线程需要的是不同锁,两个锁之间没有任何相互作用,不会起到同步作用。
五、static object
上边的代码稍作修改就可以起到互斥作用,将Trans类中Object对象的声明改为下面这样:
private static Object lock = new Object();
这样不同的类使用的就是同一个object对象,需要的锁也是同一个锁,就可以达到互斥的效果了。
经过两篇博客的介绍,我们详细的讨论了synchronized关键字的用法,看似非常复杂,其实抓住要点之后还是很好区分的,只要看synchronized获得的是哪个对象或者类的锁就行啦,其他需要这个锁的方法都不能同时执行,不需要这个锁的方法都能同时执行。
最后还要告别一个误区,相信大家都不会再犯这种错误了,synchronized锁住的是一个对象或者类(其实也是对象),而不是方法或者代码段。
4 补充
1、 synchronized关键字的作用域有二种:
1)是某个对象实例内,synchronized aMethod(){}可以防止多个线程同时访问这个对象的synchronized方法(如果一个对象有多个synchronized方法,只要一个线程访问了其中的一个synchronized方法,其它线程不能同时访问这个对象中任何一个synchronized方法)。这时,不同的对象实例的synchronized方法是不相干扰的。也就是说,其它线程照样可以同时访问相同类的另一个对象实例中的synchronized方法;
2)是某个类的范围,synchronized static aStaticMethod{}防止多个线程中不同的实例对象(或者同一个实例对象)同时访问这个类中的synchronized static 方法。它可以对类的所有对象实例起作用。
2、除了方法前用synchronized关键字,synchronized关键字还可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。用法是: synchronized(this){/*区块*/}(或者synchronized(obj){/*区块*/}),它的作用域是当前对象;
3、synchronized关键字是不能继承的,也就是说,基类的方法synchronized f(){} 在继承类中并不自动是synchronized f(){},而是变成了f(){}。继承类需要你显式的指定它的某个方法为synchronized方法;