4.synchronized的特性/死锁/JJM及wait(),noity()-bite

synchronized用法

使用synchronized 的时候,本质上是在针对某个“对象"进行加锁

synchronized的本质操作,是修改了对象中的"对象头"里面的一个标记

释义为同步,同步在多线程中指的是互斥.

1.修饰普通方法

在这里插入图片描述
锁的对象是 this,在this的对象头设置标志位

当两个线程同时尝试对同一个对象加锁的时候,才有竞争如果是两个线程在针对两个不同对象加锁,就没有竞争

2.修饰一个代码块

2.修饰一个代码块
需要显式指定针对哪个对象加锁.(Java中的任意对象都可以作为锁对象)

这种随手拿个对象都能作为所对象的用法,这是Java中非常有特色的设定.(别的语言都不是这么搞.正常的语言都是有专门的锁对象)

3.修饰一个静态方法

相当于针对当前类的类对象加锁.
Counter.class(反射)
在这里插入图片描述
上图两个方法等价

synchronized的特性

1.互斥

2.刷新内存:和volatile类似

3.不可重入/可重入

同一个线程对同一个锁,连续加锁两次,
出现死锁,就是不可重入
没有不会出现死锁,就是可重入

在这里插入图片描述

外层锁:进入方法,加锁成功
里层锁:进入代码块,因为两次加锁都是同一对象,得等外层锁解锁后,才能再次加锁,
故:陷入了死锁

而synchronized是可重入锁,故上述代码片段不会导致死锁,

可重入锁内部,会记录当前的锁被哪个线程占用的同时也会记录一个"加锁次数"线程a针对锁第一次加锁的时候,显然能够加锁成功,锁内部就记录了当前的占用着是a,同时加锁次数为1.后续再a对锁进行加锁,此时就不是真加锁,而是单纯的把计数给自增,加锁次数变为2,后续再解锁的时候,先把计数进行-1 .当锁的计数减到0的时候,就真的解锁

synchronized的锁优化机制

1.锁膨胀/锁升级

无锁->偏向锁->自旋锁->重量级锁
偏向锁:首个线程加锁,就会先进入偏向锁状态.偏向锁,不是真的加锁,只是做了个标记,带来的好处就是后续如果没人竞争的时候,就避免了加锁解锁的开销
自旋锁:又有其他线程加锁产生竞争了,进入轻量级锁状态
重量级锁:如果竞争进一步加剧,就会进入重量级锁状态.

这里的偏向锁,和懒汉模式也有点像,只是在必要的时候,才进行操作如果不必要,则能省就省

2.锁粗化

加锁代码涉及到的范围.
加锁代码的范围越大,认为锁的粒度越粗范围越小,则认为粒度越细

编译器就会有一个优化,就会自动判定说,如果某个地方的代码锁的粒度太细了就会进行粗化,如果两次加锁之间的间隔较大(中间隔的代码多),一般不会进行这种优化.如果加锁之间间隔比较小(中间隔的代码少),就很可能触发这个优化.

3.锁消除

有些代码,明明不用加锁,结果你给加上锁了,编译器就会发现这个加锁好像没啥必要,就直接把锁给去掉了.

死锁的其他场景

1.一个线程,一把锁
2.两个线程,两把锁
3.N个线程,M把锁

死锁的四个必要条件
1.互斥使用~ -个锁被一个线程占用了之后,其他线程占用不了(锁的本质,保证原子性)
2.不可抢占-一个锁被一 个线程占用了之后,其他的线程不能把这个锁给抢走. (挖墙脚是不行的)
3.请求和保持当-一个线程占据了多把锁之后,除非显式的释放锁,否则这些锁始终都是被该线程持有的~
4.环路等待等待关系,成环了~~ A等B,B等C,C又等A

要避免环路等待

标准库中线程安全/不安全类

安全
在这里插入图片描述
不安全
在这里插入图片描述

JJM(Java memory Model)Java内存模型

cpu->工作内存(work memory)
雷村->主内存(main memory)

让线程执行按照固定顺序wait.notify

在这里插入图片描述

wait内部会做三件事
1.先释放锁
⒉.等待其他线程的通知.
3.收到通知之后,重新获取锁,并继续往下执行

因此要想使用wait / notify,就得搭配 synchronized

public class Demo18 {
    private static Object locker = new Object();

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            // 进行 wait
            synchronized (locker) {
                System.out.println("wait 之前");
                try {
                    locker.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("wait 之后");
            }
        });
        t1.start();

        Thread.sleep(3000);

        Thread t2 = new Thread(() -> {
            // 进行 notify
            synchronized (locker) {
                System.out.println("notify 之前");
                locker.notify();
                System.out.println("notify 之后");
            }
        });
        t2.start();
    }
}

在这里插入图片描述

wait notify.都是针对同一个对象来操作的.
例如现在有一个对象O~~
有10个线程,都调用了o.wait. 此时10个线程都是阻塞状态.
如果调用了o.notify,就会把10个其中的一个给唤醒.(唤醒哪个?不确定)
针对notifyAll,就会把所有的10个线程都给唤醒
wait唤醒之后,会重新尝试获取到锁~~(这个过程就会发生竞争)
相对来说,更常用的还是notify

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值