类锁 、对象锁探究

熟练掌握多线程编程是程序猿的基本技能之一,很多朋友在平时的工作中,也许用惯了开源库,虽然知道自己写的代码是支持多线程的,却不懂多线程实现的原理。作者差不多也是这种状态,每次遇到问题才去翻资料。今天恰巧又想到了多线程的一个问题,所以得空自己写个demo证实下。

假如我们有一个工具类Utils,包含两个同步方法,如下:

    public synchronized void makeCall() {
        for (int i = 0; i < 5; i++) {
            try {
                System.out.println("makeCall");
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public synchronized void sendMail() {
        for (int i = 0; i < 5; i++) {
            try {
                System.out.println("sendMail");
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

在main函数开启两个线程分别调用上面两个方法

 public static void main(String[] agrs) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                Utils utils = new Utils();
                utils.makeCall();
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                Utils utils = new Utils();
                utils.sendMail();
            }
        }).start();
    }

结果会是怎样的呢,会存在互斥的问题吗?看下面的结果

makeCall
sendMail
.......
makeCall
sendMail

Process finished with exit code 0

从结果来看,并不存在互斥的情况,有些同学有疑问了,Utils的两个方法不是都加锁了吗,为什么没有同步呢?
我们把调用方法的函数改一改,如下:

 public static void main(String[] agrs) {
       final Utils utils = new Utils();
        new Thread(new Runnable() {
            @Override
            public void run() {
                utils.makeCall();
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                utils.sendMail();
            }
        }).start();
    }

看下打印结果:

makeCall
makeCall
makeCall
makeCall
makeCall
sendMail
sendMail
sendMail
sendMail
sendMail

从结果来看,本次调用出现了互斥现象,原因很简单,第一个实验,两个方法都加锁了是没毛病的,问题在于Utils的每个方法的锁是当前对象,对于两个不同的对象,相当于两把不同的锁,当然不存在互斥了。实验二恰好使用的是同一个对象,也就是同一把锁,也就存在互斥行为了。

继续实验三

// 将makeCall改成static的
public static synchronized void makeCall() {
        for (int i = 0; i < 5; i++) {
            try {
                System.out.println("makeCall");
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    public static void main(String[] agrs) {
       final Utils utils = new Utils();
        new Thread(new Runnable() {
            @Override
            public void run() {
            // 直接调用Utils的静态方法
                Utils.makeCall();
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                utils.sendMail();
            }
        }).start();
    }

打印结果如下:

makeCall
sendMail
........
makeCall
sendMail

本次打印结果也不存在互斥现象,在改下代码

public class Utils {
    //静态同步方法
    public static synchronized void makeCall() {
        for (int i = 0; i < 5; i++) {
            try {
                System.out.println("makeCall");
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    //静态同步方法
    public static synchronized void sendMail() {
        for (int i = 0; i < 5; i++) {
            try {
                System.out.println("sendMail");
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
 public static void main(String[] agrs) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                Utils.makeCall();
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                Utils.sendMail();
            }
        }).start();
    }

打印结果如下:

makeCall
makeCall
makeCall
makeCall
makeCall
sendMail
sendMail
sendMail
sendMail
sendMail

从输出来看,出现了互斥现象。实验三将makeCall改成静态同步方法,这把锁是类锁,而sendMail的锁依然是一个对象。在实验四,将两个方法都改成静态同步方法,使用的就是同一把锁了。

上面的几个实验都是为了引出两个概念,类锁/对象锁。
对象锁:JVM 在创建对象的时候,默认会给每个对象一把唯一的对象锁,一把钥匙
类锁:每一个类都是一个对象,每个对象都拥有一个对象锁。

总结:
1.对象锁钥匙只能有一把才能互斥,才能保证共享变量的唯一性
2.在静态方法上的锁,和实例方法上的锁,默认不是同样的,如果同步需要制定两把锁一样。
3.关于同一个类的方法上的锁,来自于调用该方法的对象,如果调用该方法的对象是相同的,那么锁必然相同,否则就不相同。比如 new A().x() 和 new A().x(),对象不同,锁不同,如果A的单利的,就能互斥。
4.静态方法加锁,能和所有其他静态方法加锁的进行互斥
5.静态方法加锁,和xxx.class 锁效果一样,直接属于类的

延伸一下:既然有了synchronized修饰方法的同步方式,为什么还需要synchronized修饰同步代码块的方式呢?
当某个线程进入同步方法获得对象锁,那么其他线程访问这里对象的同步方法时,必须等待或者阻塞,这对高并发的系统是致命的。如果某个线程在同步方法里面发生了死循环,那么它就永远不会释放这个对象锁,那么其他线程就要永远的等待。当然同步方法和同步代码块都会有这样的缺陷,只要用了synchronized关键字就会有这样的风险和缺陷。既然避免不了这种缺陷,那么就应该将风险降到最低。这也是同步代码块在某种情况下要优于同步方法的方面。
例如在某个类的方法里面:这个类里面声明了一个对象实例,

Object obj=new Object ();

在某个方法里面调用了这个实例的方法obj.program();但是调用这个方法需要进行同步,不能同时有多个线程同时执行调用这个方法。这时如果直接用synchronized修饰调用了obj.program();代码的方法,那么当某个线程进入了这个方法之后,这个对象其他同步方法都不能给其他线程访问了。假如这个方法需要执行的时间很长,那么其他线程会一直阻塞,影响到系统的性能。如果这时用synchronized来修饰代码块:

synchronized(obj){
obj.program();
}

那么这个方法加锁的对象是obj这个对象,跟执行这行代码的对象没有关系,当一个线程执行这个方法时,这对其他同步方法时没有影响的,因为他们持有的锁都完全不一样。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,290评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,399评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,021评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,034评论 0 207
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,412评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,651评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,902评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,605评论 0 199
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,339评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,586评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,076评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,400评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,060评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,083评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,851评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,685评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,595评论 2 270

推荐阅读更多精彩内容

  • Java8张图 11、字符串不变性 12、equals()方法、hashCode()方法的区别 13、...
    Miley_MOJIE阅读 3,637评论 0 11
  • 本文出自 Eddy Wiki ,转载请注明出处:http://eddy.wiki/interview-java.h...
    eddy_wiki阅读 1,906评论 0 14
  • 从三月份找实习到现在,面了一些公司,挂了不少,但最终还是拿到小米、百度、阿里、京东、新浪、CVTE、乐视家的研发岗...
    时芥蓝阅读 42,015评论 11 349
  • 有人问我 什么是幸福 我说 陪孩子一起理发 只和老婆吵架 被父母一顿臭骂 然后吃着她们做的饭 陪着笑脸嘻嘻 有三俩...
    一度一阅读 161评论 0 0
  • 4/21 盛美琪 泸州【每日一结构】结构思考力21天思维改善训练营 G:【技能提升】30天橙子两种活动中,我能额外...
    帅的起飞阅读 114评论 0 0