Synchronized关键字浅学(学习)

1 篇文章 0 订阅

synchronized在代码中:

1、加在方法前面:

等价于synchronized(this);this是实例本身,因此作用范围就是当前类。

2、加在静态的方法前面:

作用于当前的类对象。

3、加在代码块里

锁的就是内部的代码块。

值得注意的是,该关键字是不能被子类继承的,应为这并不是方法定义的一部分,如果父类A当中的某个方法使用了这个关键字,并且子类B覆盖了这个方法,那么实际上如果调用B的该方法,是不能实现该方法同步的,B必须显现的使用这个关键字才能起作用。当然因为A中是有该关键字修饰,所以在对应的父类方法是能实现方法同步的。

------------------------------------------------------------------------------------------------------------------------------

具体是怎么实现的?(浅谈

如果学过ReentrantLock这把锁的应该清楚,在ReentrantLock这把锁中是cas操作改变改锁的一个属性state,他表示了锁的状态。但是在Synchronized关键字中并没有对应的这个属性给你去改变这个状态。实际上他的标识在对于实例的对象头里。在jdk1.6之前,Synchronized的效率特别低下,因为都是直接加重量级锁,被monitor管程对象管理。1.6之后新增了偏向锁和轻量级锁的概念。

opjdk中对象头的相关定义参考:http://openjdk.java.net/groups/hotspot/docs/HotSpotGlossary.html

object header

Common structure at the beginning of every GC-managed heap object. (Every oop points to an object header.) Includes fundamental information about the heap object's layout, type, GC state, synchronization state, and identity hash code. Consists of two words. In arrays it is immediately followed by a length field. Note that both Java objects and VM-internal objects have a common object header format.

 他说任何有个GC管理的堆对象的开始处的公共结构包括有关堆对象的布局、类型、GC状态、同步状态和标识哈希代码的基本信息。由两个单词组成。

klass pointer

The second word of every object header. Points to another object (a metaobject) which describes the layout and behavior of the original object. For Java objects, the "klass" contains a C++ style "vtable".

这边解释道:这个是对象头的第二个单词指向描述原始对象的布局和行为的另一个对象(元对象)及class对象。

mark word

The first word of every object header. Usually a set of bitfields including synchronization state and identity hash code. May also be a pointer (with characteristic low bit encoding) to synchronization related information. During GC, may contain GC state bits.

 这边解释道:这是对象头的第一个单词,包括同步的状态和哈希值,当然也有一些其他 信息的指针,在GC期间,可能还包含了GC的状态。

当然以上是sun公司对对象头的定义和模板,具体的实现不同的虚拟机有不同的规则,比如Hotspot,JRockit(Orcale),J9等等

------------------------------------------------------------------------------------------------------------------------------

以hotspot为例,这里用的是opjdk8。

在opjdk中的markOop.hpp文件中,对markword有这么定义

主看64位及8字节

前25位是无用的(预留字段)、然后后面的31位是hashcode值,后面1位无用,age这里代码的是分代年龄(分代这里不具体谈,jvm知识还需要加深学习)。biased_lock偏向锁标记位,lock占2位

代码实现

这里随便创建一个类

/**
 * author:@楼下飘来个鬼
 * Date:2021/7/3
 */
public class Ziyuan {
    private Integer i;
}

 test

/**
 * author:@楼下飘来个鬼
 * Date:2021/7/2
 */
@Slf4j(topic = "mylog")
public class SychronizedDome {

    public static void main(String[] args) {
        //ReentrantLock reentrantLock = new ReentrantLock(true);
        Ziyuan zy = new Ziyuan();
        //zy.hashCode();
        log.debug(ClassLayout.parseInstance(zy).toPrintable(zy));

    }
}

 运行结果:

分析:这个实例堆空间中占有16个字节,对象头占了12个字节,上面说到markword占用8个字节且从头开始。上面说到hotspot虚拟机规定前25位是无用的,应该都是0(但是这里可以看到当前第八位是1,这里涉及到大小端的问题,计算机网络的相关知识,具体百度)因此可以知道我的计算机cpu是小端存储的即高位存储在小端,第8个字节是实际上 数据的第一个字节。后面紧跟31位是hashcode,这边都是0是应为我们没有调用过obj.hashCode();我们可以将上述代码中的hasCode注释去掉,结果如下:

我们可以看到这时候就有hashcode值了。

---------------------------------------------------------------------------------------------------------------------------------

了解了上面,接下来看看锁的状态。

注:图片内容来源于

jdk1.6之后是默认开启偏向锁的,有两种情况导致不可偏向:

  • 用户关闭了jvm的偏向锁(比如:-XX:-UseBiasedLocking //关闭偏向锁)
  • 当前对象计算过hash值即调用过hashcode()方法,因为当调用过该方法,那么hash:31处就记录了hash值,如果上偏向锁,那么偏向后存线程id的地方是和hashcode有重叠部分,那到底存哪个,这是不合理的。

验证代码

/**
 * author:@楼下飘来个鬼
 * Date:2021/7/2
 */
@Slf4j(topic = "mylog")
public class SychronizedDome {

    public static void main(String[] args) throws InterruptedException {
        //ReentrantLock reentrantLock = new ReentrantLock(true);
        //这里延时5秒的原因:因为jvm启动后默认4秒后开启偏向锁,jvm本身启动的时候就会有依赖hashcode,
        //其中会有大量的同步块,他们之间就存在资源的竞争
        TimeUnit.SECONDS.sleep(5);
        Ziyuan zy = new Ziyuan();
        zy.hashCode();
        synchronized (zy){
            log.debug(ClassLayout.parseInstance(zy).toPrintable(zy));
        }
    }
}

这里可以观察计算过hashcode和没计算过hashcode的markword中对应的biased_lock位上的值。

-------------------------------------------------------------------------------------------------------------------------------

当一个线程A用完当前的资源(可偏向)并且释放后,另一个线程B来再加锁,这时候就会变成轻量锁。对象头中就会指向当前线程栈中的一个地址lock record,该内存中存放升级前的对象头中markword信息

/**
 * author:@楼下飘来个鬼
 * Date:2021/7/2
 */
@Slf4j(topic = "mylog")
public class SychronizedDome {
    static Ziyuan zy = new Ziyuan();;
    public static void main(String[] args) throws InterruptedException {
        //ReentrantLock reentrantLock = new ReentrantLock(true);
        //这里延时5秒的原因:因为jvm启动后默认4秒后开启偏向锁,jvm本身启动的时候就会有依赖hashcode,
        //其中会有大量的同步块,他们之间就存在资源的竞争
        TimeUnit.SECONDS.sleep(5);
        //zy.hashCode();
        Thread t1 = new Thread(()->{
            synchronized (zy){
                log.debug(ClassLayout.parseInstance(zy).toPrintable(zy));
            }
        });

        Thread t2 = new Thread(()->{
            synchronized (zy){
                log.debug(ClassLayout.parseInstance(zy).toPrintable(zy));
            }
        });

        t1.start();
        t1.join();
        t2.start();
    }
}

这里会有概率出现两次打赢的结果是一样的状态因为这里出现线程复用了。

-------------------------------------------------------------------------------------------------------------------------------

当一个线程A用完当前的资源(可偏向)并且未释放后,另一个线程B来再加锁,这时候就会变成重量锁。

@Slf4j(topic = "mylog")
public class SychronizedDome {
    static Ziyuan zy = new Ziyuan();
    public static void main(String[] args) throws InterruptedException {
        //ReentrantLock reentrantLock = new ReentrantLock(true);
        //这里延时5秒的原因:因为jvm启动后默认4秒后开启偏向锁,jvm本身启动的时候就会有依赖hashcode,
        //其中会有大量的同步块,他们之间就存在资源的竞争
        //zy.hashCode();
        log.debug(ClassLayout.parseInstance(zy).toPrintable(zy));
        TimeUnit.SECONDS.sleep(5);
        Thread t1 = new Thread(()->{
            synchronized (zy){
                log.debug(ClassLayout.parseInstance(zy).toPrintable(zy));
                try {
                    TimeUnit.SECONDS.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        Thread t2 = new Thread(()->{
            synchronized (zy){
                log.debug(ClassLayout.parseInstance(zy).toPrintable(zy));
            }
        });

        t1.start();
        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //t1.join();
        t2.start();

    }
}

 打印结果:

---------------------------------------------------------------------------------------------------------------------------------

以上内容只是sync 知识体系中的冰山一角。

作为学习的一种方式,写博客可以加深对知识点的理解和加深印象,今天开始,希望能坚持哈哈。

以上,第一篇博客写的比较粗糙,写的不对或者不详细的欢迎指正,谢谢。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值