synchronized详情 锁属性 锁方法 锁类 ,可重入不公平锁 锁优化

最近复习一下基础,查缺补漏,输出倒逼一下输入. 以后忘了再来翻翻,看看自己输出的怎么样,反复卤煮好吧!

1 首先是几个基本特性:

  1. 一把锁只能被一个线程获取, 一旦被获取后, 其他的线程只能等待,锁释放后才可重新获取.

  2. 每个实例对象都有各自的锁, 不同实例对象互补影响,除非两种特殊情况,一种是直接锁在类上(*.class),或者是用synchronized修饰的static静态方法,这时的对象就是共用一把锁

  3. synchronized修饰的方法, 不管是正常流程结束,还是抛异常,都会释放锁.

然后是敲的一些代码例子

public class SynchronizedObjectLock implements Runnable{
    //静态对象
    static SynchronizedObjectLock instance1 =new SynchronizedObjectLock();

    public void run1() {
        //对象锁, 使用对象时被抢占,只能等
        //如果不锁就各跑各的了
        synchronized (this){
            System.out.println("我是线程"+Thread.currentThread().getName());
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"结束");
        }
    }
    Object block1=new Object();
    Object block2=new Object();
    public void run2() {
        // 这个代码块使用的是第一把锁,当他释放后,后面的代码块由于使用的是第二把锁,因此可以马上执行
        synchronized (block1){
            System.out.println("block1锁,我是线程" + Thread.currentThread().getName());
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("block1锁,"+Thread.currentThread().getName() + "结束");
        }
        //如果两块代码块用的是同一把锁,跑到这里时,如果锁也是block1
        //如果另外一个线程在上面占用了block1锁住了,那么这一块就得等另外一个线程把上面跑完释放锁,才能走
        synchronized (block2){
            System.out.println("block2锁,我是线程" + Thread.currentThread().getName());
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("block2锁,"+Thread.currentThread().getName() + "结束");
        }
    }
    static SynchronizedObjectLock instance2 =new SynchronizedObjectLock();
    public void run3() {
        method();
    }
    @Override
    //类锁
    public void run() {
        // 所有线程需要的锁都是同一把
        synchronized(SynchronizedObjectLock.class){
            System.out.println("我是线程" + Thread.currentThread().getName());
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "结束");
        }
    }
    //修饰普通方法 锁的是当前对象this
    //线程0 和线程1 都是走各自对象的方法,所以互不影响
    private synchronized void method1() {
        System.out.println("我是线程" + Thread.currentThread().getName());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "结束");
    }
    //修饰静态方法,锁的是这个class类
    //所以哪怕用不同的对象,还是会被锁住
    //这里线程1 就得等线程0跑完
    private synchronized static void method() {
        System.out.println("我是线程" + Thread.currentThread().getName());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "结束");
    }
    public static void main(String[] args) {
        //使用同一个对象
        /*Thread thread0 = new Thread(instance1);
        Thread thread1 = new Thread(instance1);*/
        Thread thread0 = new Thread(instance1);
        Thread thread1 = new Thread(instance2);
        thread0.start();
        thread1.start();
    }
}

2 synchronized原理分析

2.1 加锁和释放锁的原理

现象、时机(内置锁this)、深入JVM看字节码(反编译看monitor指令)

public class SynchronizedDemo2 {

    Object object = new Object();
    public void method1() {
        synchronized (object) {

        }
        method2();
    }

    private static void method2() {

    }
}

反编译后:

在这里插入图片描述
主要关注一下monitorenter和monitorexit

拆开来看就是monitor enter 和exit 进入和退出锁

进入时锁+1 退出时锁-1

每一个对象在同一个时间上只能和一个monitor关联

一个monitor在同一个时间上只能被一个线程获得

当一个对象在尝试获得这个关联的monitor时, monitorenter会发生以下三种情况之一

  1. 当monitor的计数器为0时, 线程A进来会给monitor加1,并记录一下进来的是我线程A

  2. 当A进来时,发现monitor上面的计数器为1, 上次的记录是我线程A,我又进来了,我是老客户,计数器再加1,变成了2,再进来再加1,因为他是可重入锁.

  3. 当B进来了,发现monitor上次是被其他线程获取了,我就得等等,等着锁释放干净以后再来抢,因为他是不公平锁.

这里会出现2和3两种情况就是因为可重入锁的便利之处,如果是A继续,就不用释放锁,继续跑就行了,如果是B就得释放锁.

就像去酒店,A进来502睡了一晚,搞了两朵大红花贴墙上,老板记下来,今晚A来睡过一次,他出去了走了.

第二天又想来继续睡,可以不用大整,稍微收拾一下卫生就行了.

然后第三天,B来了,我觉得不行,这两朵大红花算什么玩意啊,给我贴个米老鼠,所以就得等酒店人员把A的一些布置清理掉,给B贴个米老鼠才行 ,这样B才能住的舒服满意.

然后B住进来了,之前A的连续居住记录就没用了,清掉,换成记B居住1天,这样明天还是B的话,米老鼠就不用拆掉了!


继续继续

2.2 保持可见性原理

内存模型和happens-before规则

有点懵逼先跳过


3 JVM中锁优化

3.1 锁的类别

从java se 1.6开始 引入了一些锁的优化,并对锁进行了一些分类,让锁不是那么的重.

锁的膨胀方向:

无锁 => 偏向锁 => 轻量级锁 => 重量级锁(此过程不可逆)

锁的优化

  1. 锁粗化
  2. 锁消除
  3. 轻量级锁
  4. 偏向锁
  5. 自旋锁

3.2 自旋锁

引入背景: 其实提到synchronized ,第一个印象就是重, 尤其是在锁优化前.
当多线程在竞争锁时,一个线程取得锁以后,其他的线程都将被阻塞,对性能造成了极大的影响.

挂起线程和恢复线程的动作都需要转入到内核状态中完成,对系统的并发能力造成了很大的压力.

同时HotSpot团队注意到, 共享数据的锁定状态只会保持很短的一段时间,为了这么点时间去挂起和恢复线程很不值得.

如今在多处理器的环境下,完全可以让另外一个没用获取到锁的线程在门外等一会儿(自旋 ) ,但不放弃CPU的执行时间.

如果锁很快就被释放,那么另外一个线程就可以直接进入,节省了挂起和恢复的动作.

而这个自旋, 只需要让线程去执行一个忙循环,也就是自旋, 这就是自旋锁的由来, 类似while(true)

3.3 自适应自旋锁

在JDK1.6中引入的, 前面自旋锁的自旋时间是固定的,不够智能.

自适应自旋锁会根据上一个 自旋锁做判定, 比如上一个自旋锁等待成功获取了锁,且这个获取到锁的线程正在运行中.

那么JVM就会认为获取自旋锁的概率很高, 可以再加点自旋的次数,比如多循环100次.

但如果自旋很少成功获得到锁, 那么JVM甚至可以减少,甚至放弃自旋,来避免浪费处理器资源.

3.4 锁消除

参考文档: 传送门

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值