synchronized关键字的膨胀性与用法

众所周知,synchronized是一款基于jvm(java虚拟机)实现的锁关键字,主要用来在高并发情况下保证程序的正确性。

鉴于现在大家的jdk版本都是升级到至少1.7了,因此我们主要谈谈1.6+版本的synchronized关键字。

对于jdk1.6之后的synchronized关键字,不再是以前完全基于mutex(互斥量)的重量级锁。而是加入了一些优化。

首先,我们要知道一个常识:锁的概念是针对于线程的。只是针对于线程的!针对于线程的!!!
所以实现锁的意义,也是对于线程们而言的。
“在我执行期间,你们不许动我的东西”这就是它的霸道。然后他就会锁住门,阻拦所有想要进门的线程。直到他出门交出钥匙为止。

总之,锁是一种,在某个情境下,只让某个线程独占资源的一种手段。

那么实现方案呢?
我们会使用一个对象当做门,当一个线程执行到synchronized(对象){内容……}的时候,就是锁了这个对象(门),只有它有钥匙,之后再有别的线程执行到这里,也进不去,因为没有钥匙,当它执行完{}里面的内容之后,就会离开房间,交出钥匙,下一个线程获取钥匙之后,才可以执行它的操作。

那么实际中的具体实现呢?
首先我们要知道,在java中,任何一个对象都分为三部分,对象头,数据,填充位。
对于对象的控制我们可以利用对象头实现。在对象头上有个叫做mark Word的区域,在这里可以申请到两个比特位的空间,我们给它打个锁芯,把它作为专门的锁控制位。就可以实现上面的过程了。

它的具体过程是这样的:

1、当一个线程进入同步方法块的时候,虚拟机首先会在线程的栈帧上建立一个名叫lock Record的空间,用于储存锁对象当前的mark Word的拷贝。

2、将对象头的Mark Word拷贝到线程的锁记录(Lock Recored)中。

3、拷贝成功后,虚拟机将使用CAS操作尝试将对象的Mark Word更新为指向Lock Record的指针,并更新锁位为偏向锁。(指针指向了线程的lock Record,里面存的数据是对象头的,所以从结构上来说,是合理的)。

至此为止,这就算是完成了线程获取对象的唯一的钥匙的这一步

4、当有新的线程进入同步块的时候,检查Mark Word中的指针,如果是当前线程,那么直接放行(当前线程拥有这个对象的门的钥匙,当然可以无数次开门),如果不是,那么该线程开始自旋重新获取锁(有别的线程拿到了钥匙,所以我就只能不断地敲门,盼望下一秒它就可以结束,我瞬间抢到交还钥匙),并且更新锁位为轻量级锁。

5、如果自旋一段时间之后,还是拿不到,就把Mark Word更新为指向Monitor的指针,并更新为重量级锁。(多个线程一直自旋敲门,但是始终拿不到钥匙,所以我干脆让你们全部阻塞,等钥匙被交出了,你们再来抢。自旋是一个主动地行为,而阻塞唤醒是一个事件驱动的行为)

销毁就是释放锁,把钥匙交出去。

根据加锁对象的不同,synchronized关键字主要分为两种级别的锁:实例锁,类锁。
实例锁是个体锁,而类锁是模具锁。
这么解释就很简单了。
把jvm的对象们,想象成一间大型公寓的房门。(因为java对象的对象头的特性,对象皆可为锁)
一个线程说:我要锁住所有型号为AAA-3的门。那么,所有符合AAA-3工业标准的门,就都会被锁上。这就是类锁。一个类加载器在一个java虚拟机上只能加载一个唯一类,它的所有实例都是根据类的结构复制出来的。类,是一个工业化的模具。

另一个线程说:我要锁住5楼第三间的门。这样它锁住的只是一个门,是一个个体,而不是一类门。所以它是实例锁。

理论讲了很多,接下来看例子吧。

第一个用法:实例锁
顾名思义就是给实例加锁,这样的锁,就是个体锁。

代码块形式:手动指定锁实例对象;

Object lock1 = new Object();
Object lock2 = new Object();

    @Override
    public void run() {
//锁住对象lock1
        synchronized (lock1) {
            System.out.print("线程:" + Thread.currentThread().getName() + "的lock1开始啦\n");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.print("线程:" + Thread.currentThread().getName() + "的lock1结束啦\n");
        }
//锁住对象lock2
        synchronized (lock2) {
            System.out.print("线程:" + Thread.currentThread().getName() + "的lock2开始啦\n");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.print("线程:" + Thread.currentThread().getName() + "的lock2结束啦\n");
        }
    }

方法锁形式:synchronized修饰普通的方法,锁对象默认为this;

public synchronized void method(){
        System.out.print("线程:" + Thread.currentThread().getName() + "的lock1开始啦\n");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.print("线程:" + Thread.currentThread().getName() + "的lock1结束啦\n");
    }
public void run() {
      method();  
}

以上都是实例锁,所以在测试类中,只要使用同一个类的单例runnable就可以让线程1和线程2串行化执行;

第二个用法:类锁(class锁)
给类,即class对象加锁。

形式1:static 方法加锁;

@Override
    public void run() {
        method();
    }
    //类锁1
    static synchronized void method(){
        System.out.print("线程:" + Thread.currentThread().getName() + "的类锁1开始啦\n");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.print("线程:" + Thread.currentThread().getName() + "的类锁1结束啦\n");

    }

形式2:synchronized(*.class);

@Override
    public void run() {
        synchronized (SynchronizedRequest2.class){
            System.out.print("线程:" + Thread.currentThread().getName() + "的类锁1开始啦\n");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.print("线程:" + Thread.currentThread().getName() + "的类锁1结束啦\n");
        }
    }

static修饰的变量是类变量,修饰的方法是类级别的方法,它们都是属于类的结构。而class对象是类在代码工程结构中的实体表现。因此以上两种方式是类锁,也就是模具锁。

当然,类锁是类级别的锁,所以在测试类中,需要检验的是同一个类的不同实例,看看有没有被锁住。

終わり

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