场景:面试的时候经常用得到!

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);//
synchronized(this){ //同步代码块
//Thread.sleep(100);//
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方法;  

注意:
在使用synchronized关键字时候,应该尽可能避免在synchronized方法或synchronized块中使用sleep或者yield方法,因为synchronized程序块占有着对象锁,你休息那么其他的线程只能一边等着你醒来执行完了才能执行。不但严重影响效率,也不合逻辑。
同样,在同步程序块内调用yeild方法让出CPU资源也没有意义,因为你占用着锁,其他互斥线程还是无法访问同步程序块。当然与同步程序块无关的线程可以获得更多的执行时间。(待补充)

(转)Synchronized(对象锁)和Static Synchronized(类锁)的区别的更多相关文章

  1. java synchronized类锁,对象锁详解(转载)

    觉得还不错 留个记录,转载自http://zhh9106.iteye.com/blog/2151791 在java编程中,经常需要用到同步,而用得最多的也许是synchronized关键字了,下面看看 ...

  2. Java锁Synchronized,对象锁和类锁举例

    Java的锁分为对象锁和类锁. 1. 当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内针对该对象的操作只能有一个线程得到执行.另一个线程必须 ...

  3. Java锁Synchronized对象锁和类锁区别

    java的内置锁:每个java对象都可以用做一个实现同步的锁,这些锁成为内置锁.线程进入同步代码块或方法的时候会自动获得该锁,在退出同步代码块或方法时会释放该锁.获得内置锁的唯一途径就是进入这个锁的保 ...

  4. 并发编程大师系列之:Synchronized的类锁和对象锁

    说到并发编程,感觉跟大多数人一样,谈之色变,说它简单把,其实很有内容,说难吧,用起来也挺容易,最近我硬着头皮,决心要把并发编程好好的搞一遍.以前,面试的时候,面试官问,并发编程会吗?嗯,接触过,就加一 ...

  5. Java 中对象锁和类锁的区别? 关键字 Synchronized的用法?

    一  对象锁和类锁的关系 /* * 对象锁和[类锁] 全局锁的关系? 对象锁是用于对象实例方法,或者一个对象实例上的 this 类锁是用于类的静态方法或者一个类的class对象上的. Ag.class ...

  6. Synchronized方法锁、对象锁、类锁区别

    synchronized,这个东西我们一般称之为”同步锁“,他在修饰代码块的时候需要传入一个引用对象作为“锁”的对象. 在修饰方法的时候,默认是当前对象作为锁的对象 在修饰类时,默认是当前类的Clas ...

  7. 类锁、对象锁、互斥锁与synchronized

    本文总结自: https://blog.csdn.net/luckey_zh/article/details/53815694 互斥锁: 若对象有互斥锁,则在任一时刻,只能有一个线程访问对象.类锁.对 ...

  8. java 多线程10:synchronized锁机制 之 锁定类静态方法 和锁定类.Class 和 数据String的常量池特性

    同步静态方法 synchronized还可以应用在静态方法上,如果这么写,则代表的是对当前.java文件对应的Class类加锁.看一下例子,注意一下printC()并不是一个静态方法: public ...

  9. 多线程同步锁和死锁以及synchronized与static synchronized 的区别

    线程:线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程.一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序.简而言之:一个程序运行后至少有一个进程,一个进程 ...

  10. Java类锁和对象锁实践(good)

    一.前言 之前对类锁和对象锁是否是互斥的不是太确定,因此决定编写相关的程序进行实践一下.编写前对相关定义约定约定如下: 1. 类锁:在代码中的方法上加了static和synchronized的锁,或者 ...

随机推荐

  1. PHP的SQL注入技术实现以及预防措施

    SQL 攻击(SQL injection,台湾称作SQL资料隐码攻击),简称注入攻击,是发生于应用程序之数据库层的安全漏洞.简而言之,是在输入的字符串之中注入SQL指 令,在设计不良的程序当中忽略了检 ...

  2. (转)addEventListener()与removeEventListener()详解

    转自:http://www.111cn.net/wy/js-ajax/48004.htm addEventListener()与removeEventListener()用于处理指定和删除事件处理程序 ...

  3. 警告: [SetContextPropertiesRule]{Context} Setting property 'source' to 'org.eclipse.jst.jee.server:JsonBlog' did not find a matching property.

    这个问题困扰很久了,逛了很多论坛,终于得以解决 我的控制台错误如下: 五月 , :: 下午 org.apache.catalina.startup.VersionLoggerListener log ...

  4. Grunt压缩HTML和CSS

    我的小伙伴们!我明明 在压缩图片之前发过一篇,关于Grunt压缩cCSS是和HTML的!但是不知道为什么,今天再一看.迷之消失了! 没办法.只好今天在写一次,从头开始!首先.我来介绍一下为什么要用构建 ...

  5. solr5Ik分词2

    <!--IK分词器--><fieldType name="text_ik" class="solr.TextField"><ana ...

  6. html <input type="text" />加上readonly后在各种浏览器的差异。

    <html> <body> <p>Name:<input type="text" name="email" /> ...

  7. PHPCMS v9点击量增加值加大的方法

    PHPCMS v9点击量增加值加大的方法 在根目录/api 50行 $views = $r['views'] + 1; 修改数字1即可修改每次刷新页面点击量增加的数值.

  8. 使用Iterator的方式也可以顺利删除和遍历

    使用Iterator的方式也可以顺利删除和遍历 eg: public void iteratorRemove() { List<Student> students = this.getSt ...

  9. hibernate,createCriteria in条件 是一个集合。list 或 数组等

    hibernate,createCriteria in条件 是一个集合.list 或 数组等 cq.in("states", new String[]{"2", ...

  10. 基于Java SE的模拟双色球彩票系统

    1.双色球规则: ①双色球分为红球和蓝球,红球选择的范围为1-33,而且红球选择6个数字:蓝球选择的范围为1-16,而且只能选择1个数字. ②选择方式为随机选择号码和手动输入选择号码. ③生成号码的顺 ...