美文网首页
synchronized关键字的膨胀性与用法

synchronized关键字的膨胀性与用法

作者: hekirakuno | 来源:发表于2020-03-03 13:41 被阅读0次

    众所周知,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对象是类在代码工程结构中的实体表现。因此以上两种方式是类锁,也就是模具锁。

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

    終わり

    相关文章

      网友评论

          本文标题:synchronized关键字的膨胀性与用法

          本文链接:https://www.haomeiwen.com/subject/czxclhtx.html