synchronized

本文深入探讨了Java中的`synchronized`关键字,包括其原理、锁的获取与释放、锁粗化和消除的优化机制。通过示例代码展示了同步关键字的不可中断特性,并分析了其在多线程环境中的应用。此外,还讨论了`wait`、`notify`和`notifyAll`对象方法的使用规则和区别。最后,文章对比了同步阻塞和异步非阻塞的优缺点,强调了线程管理和资源利用的重要性。
摘要由CSDN通过智能技术生成

一、synchronized

1、原理:

  • synchronize是线程互斥的,所以能保证共享数据一致性。
  • synchronize底层是有 monitorenter 和 monitorexit 两个jvm指令;他能保证任何线程在enter之前从主内存中读取共享数据,在exit将共享数据刷新到主内存。(遵循happens-before原则,管程锁定规则:一个unlock操作先行发生于后面对同一个锁的lock操作。这里必须强调的是同一个锁,而"后面"是指时间上的先后顺序。)
    可参考 深入synchronize原理

2、如下反编译带有synchronize代码程序:

在这里插入图片描述

3、获取 和 释放 对象关联的monitor锁机制

在这里插入图片描述
synchronize是不可中断的
只有像sleep、wait、join这些可中断方法才能被中断,然而获取锁阻塞是无法被中断的,如下案例

public class SynchronizedBlocked implements Runnable {
    public synchronized void f() {
        System.out.println("Trying to call f()");
        while (true) // Never releases lock
            Thread.yield();
    }

    /**
     * 在构造器中创建新线程并启动获取对象锁
     */
    public SynchronizedBlocked() {
        // 该线程已持有当前实例锁
        new Thread() {
            public void run() {
                f(); // Lock acquired by this thread
            }
        }.start();
    }

    public void run() {
        // 中断判断
        while (true) {
            if (Thread.interrupted()) {
                System.out.println("中断线程!!");
                break;
            } else {
                f();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        SynchronizedBlocked sync = new SynchronizedBlocked();
        Thread t = new Thread(sync);
        // 启动后调用f()方法,无法获取当前实例锁处于等待状态
        t.start();
        TimeUnit.SECONDS.sleep(1);
        // 中断线程,无法生效
        t.interrupt();
    }
}
结果:Trying to call f()
由于构造中已经有线程抢了锁,没有释放,所以t线程抢锁导致阻塞是无法被中断的;
只有在可中断方法才能被中断(不管是否加synchronize关键字,只要运行到可中断方法就可以捕获中断信号抛出中断异常,获取锁是阻塞无法捕获中断信号的)

4、锁粗化 和 锁消除

// 锁粗化(运行时 jit 编译优化)
// jit 编译后的汇编内容, jitwatch可视化工具进行查看
public class ObjectSyncDemo3 {
    int i;

    public void test1(Object arg) {
        synchronized (this) {
            i++;
        }
        synchronized (this) {
            i++;
        }
        //上面热点代码,会优化如下,因为相同的锁进入两次消耗大量系统资源
        /* synchronized (this) {
            i++;
            i++;
        }*/
    }


    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 10000000; i++) {
            new ObjectSyncDemo3().test1("a");
        }
    }
}
// 锁消除(jit)
public class ObjectSyncDemo4 {
    public void test1(Object arg) {
        // jit 优化, 消除了锁
        StringBuffer stringBuffer = new StringBuffer();
        stringBuffer.append("a");
        stringBuffer.append(arg);
        stringBuffer.append("c");
        // System.out.println(stringBuffer.toString());
    }

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 1000000; i++) {
            new ObjectSyncDemo4().test1("123");
        }
    }
}

分析:热点代码优化导致锁消除,如何保证安全性呢?由于各个线程是操作的栈帧局部变量所以安全
//如下是StringBuffer 的 append方法
   @Override
    public synchronized StringBuffer append(String str) {
        toStringCache = null;
        super.append(str);
        return this;
    }

5、同步关键字加锁原理 及其优化过程

①、锁对象包括Mark word,对象信息及格式,如下图
在这里插入图片描述
②cas实现加锁的原理,线程栈的lock record中存入该锁对象的Mark word信息(hash、经历多少次新生代gc、和什么锁状态)
在这里插入图片描述
③锁对象加锁优化的步骤如下图:对于只有一个线程多次获取同一个锁,只需要使用偏向锁就可以啦
在这里插入图片描述
在这里插入图片描述

6、synchronize的缺陷

上图就能说明为什么wait 和 notify 、notifyAll必须写在同步中的原因,因为waitSet在monitor中。

三、同步阻塞 和 异步非阻塞

在这里插入图片描述
在这里插入图片描述
同步阻塞 和 异步非阻塞 对比

  • 同时提交任务,同步处理等待时间较长,可能会出现阻塞;频繁的创建、关闭线程,大量的浪费资源;如果线程创建数量巨大是cpu频繁切换也会降低性能。
  • 异步非阻塞是提交任务到队列将立马返回工单,创建合理的线程数量去处理队列任务,然后存入结果集中,可凭工单获取结果集。

无法控制线程停止,可中断方法可以使用interrupted方法中断,但是获取锁阻塞就无法中断了

二、object方法 wait 、notify、notifyAll

wait:

  • wait不设置时间,内部是调用wait(0)方法,表示永不超时阻塞。
  • 使当前线程进入阻塞,阻塞时间到了自动醒来,或者被其他线程调用该锁对象的notify / notifyAll方法唤醒(通知在此对象的监视器上等待的线程醒来。然后该线程将等到重新获得对监视器的所有权后才能继续执行)。
  • 方法必须在同步中使用,因为waitset在monitor中,所以使用必须拥有该对象的monitor以及monitor的所有权才能执行。
  • 执行该方法会放弃monitor所有权,进入该锁对象关联的wait set中,其他线程才有机会获取该对象的monitor所有权。

notify:

  • 唤醒该锁对象关联的wait set中的任意一个线程,如果没有则被忽略。
  • 被唤醒要重新获取到该锁对应monitor的所有权,线程才能执行。

notifyAll
唤醒全部该锁对象的wait set中的线程,然后争抢monitor锁所有权

wait 和 sleep 总结

  • 相同点:①都是使线程进入阻塞状态;②都是可中断方法,中断后抛出中断异常;
  • 不同点:
    ①wait是object方法,sleep是Thread方法;
    ②wait方法必须在同步中才能使用,sleep不用
    ③wait会释放monitor锁,sleep不会
    ④因为sleep方法必须指定时间,而wait不用,所以sleep会自动醒来进入runnable状态。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值