深入理解synchronized

本文详细介绍了Java中的synchronized关键字的特性,包括锁升级(自适应)、偏向锁、锁消除和锁粗化,以及何时使用synchronized、锁的操作方式和与ReentrantLock的区别。
摘要由CSDN通过智能技术生成

一:基本特性:

1.1:锁升级(自适应)(重点)

针对程序:首先未使用synchronized ,就是未加锁的状态,代码中开始调用执行synchronized后,synchronized锁变成偏向锁,如果遇到锁冲突,synchronized 锁进一步升级成轻量级锁,当冲突进一步升级,synchronized锁就由轻量级锁,转变成重量级锁.
(1)未加锁的状态(无锁)
(2)偏向锁

(3)轻量级锁
(4)重量级锁
上述锁升级过程,针对一个锁对象来说,是不可逆的,只能升级,不能降级,一旦升级到了重量级锁,就不会回退到轻量级锁(当前JVM的做法).

1.1.1 偏向锁

偏向锁:首次使用synchronizd对对象进行加锁的时候,并不是真的加锁,而只是做一个"标记"(非常轻量,非常快,几乎没有开销),如果后面没有其他线程尝试对这个对象加锁,就可以保持这个状态,一直到解锁,(解锁也就是修改一下上述标记,也几乎没有开销),但是,如果在偏向锁的状态下,有某个线程也尝试对这个锁对象进行加锁,立刻就把偏向锁升级成轻量锁(真的加锁了,真的有互斥了).
本质上,偏向锁策略就是"懒"字的具体体现:能不加锁,就不加锁,能晚加锁,就晚加锁.

二:锁消除

实际上是一种编译器优化策略.
编译器优化前和优化后逻辑是等价的.
当代码中写了加锁操作,编译器&JVM会对当前的代码做出判定,看当前代码是不是真的要加锁,如果这里不需要加锁,就会自动的把加锁操作给优化掉.
最典型的就是,在一个线程里,使用synchronized.

三:锁粗化

实际上也是编译器的一种优化策略
锁的粒度:加锁的范围内,包含多少代码,包含的代码越多,就认为锁的粒度就越粗,反之,锁的粒度就越细.
在有些逻辑中,需要频繁的加锁解锁,编译器就会自动的把多次细粒度的锁,合并成一次粗粒度的锁,

二:synchronized的使用

2.1:什么时候使用synchronized???

多线程中,出现了线程不安全.

2.2:锁的操作:

(1)加锁:t1加上锁之后,t2也尝试加锁,就会阻塞等待(都是系统内核控制)(在Java中可以看到BLOCKED状态)
(2)解锁:直到t1解锁了之后,t2才有可能拿到锁(加锁成功).

2.3:编写代码

首先创建一个对象,使用这个对象作为锁:
在Java中可以使用任何对象作为加锁对象.
创建锁对象的意义:
锁对象的用途:有且只有一个,那就是用来区分,多个线程是否针对同一个对象(count)加锁,如果是,就会出现锁竞争/锁冲突/互斥,就会引起阻塞等待;如果不是,就不会出现锁竞争,也就不会阻塞等待.
锁对象还会记录当前那个线程拿到了锁对象,在摸个线程中,进行加锁的时候,首先判定该线程是否获得了锁对象,如果获得了,就加锁,没获得,就阻塞等待.
通过设定不同的锁对象,来确定竞争关系.

public class Demo2 {
    public static int count=0;

    public static void main(String[] args) throws InterruptedException {
        //首先创建一个对象,使用这个对象作为锁
        Object locker = new Object();
        Thread t1=new Thread(()->{
            for (int i = 0; i < 50000; i++) {
              synchronized (locker){
                  count++;
              }

            }
        });
        Thread t2=new Thread(()->{
            for (int i = 0; i < 50000; i++) {
                synchronized (locker){
                    count++;
                }

            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("count = " +count);
    }
}

上述代码就相当于两个线程,针对同一个锁对象加锁,就会产生互斥.
t1,t2做的事情就是:判断循环条件,加锁,load ,add, save ,解锁,i++;
假设:由于是线程是并发执行的,这里假设t1线程先执行到了加锁操作,并且t1还没解锁,那么t2线程就不能获得锁对象,就不能进行加锁操作,
假设t1刚解锁,t2 就加锁了,但t1线程并不是什么代码也不执行,而是继续执行i++,循环判断条件,当t1执行到了synchronized,发现不能获得锁对象,那么t1线程只好阻塞等待了.
因此:在t1,t2两个线程中,每次count++是存在锁竞争的,会变成"串行"执行,但是执行for 循环中的条件以及i++仍然是并发执行的.

```java
public class Demo2 {
    public static int count=0;

    public static void main(String[] args) throws InterruptedException {
        //首先创建一个对象,使用这个对象作为锁
        Object locker = new Object();
        Object locker2=new Object();
        Thread t1=new Thread(()->{
            for (int i = 0; i < 50000; i++) {
              synchronized (locker){
                  count++;
              }

            }
        });
        Thread t2=new Thread(()->{
            for (int i = 0; i < 50000; i++) {
                synchronized (locker2){
                    count++;
                }

            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("count = " +count);
    }
}


上述代码就是两个线程针对不同对象加锁,就不会产生互斥,也就不会发生阻塞等待现象了,但线程确是不安全的.
操作系统中的加锁,解锁功能,核心还是CPU提供的指令(硬件提供了这样的能力,软件上才有对应的功能)

三:synchronized的锁机制

synchronized 既是悲观锁,又是乐观锁,既是轻量级锁,又是重量级锁,轻量级锁是自旋锁实现,重量级锁是挂起等待锁实现,synchronized是可重入锁,不是读写锁,是非公平锁.

四:synchronized 和ReentrantLock 的区别

共同点:都是可重入锁.
(1)synchronized 进入"{“就是加锁,出”}"就是解锁,而RenntrantLock则提供了加锁解锁方法

 Object locker1 =new Object();
        synchronized (locker1){

        }
ReentrantLock locker=new ReentrantLock(true);
        //参数写true就是创建一个公平锁
        //不写或写false就是创建一个非公平锁
        try{
            locker.lock();//加锁
        }finally {
            locker.unlock();//解锁
            //因为可以会忘记写解锁方法,或者因其他方法返回,没执行了解锁操作,
            //所以经常把解锁操作放到finally中,该方法就一定会被执行
        }

(2)synchronized 只是非公平锁,但RenntrantLock既提供了公平锁的实现,又提供了非公平锁的实现.
(3)ReentrantLock提供了tryLock操作,给加锁提供了更多的操作空间,尝试加锁,如果锁已经被其他线程占有,直接返回失败,而不会继续阻塞等待(也可以通过tryLock指定等待超时时间),但synchronized 遇到锁竞争,就阻塞等待(死等).
(4)synchronized 是搭配wait ,notify等待通知机制,RenntrantLock 是搭配Condition类完成等待通知.
Condition要比wait ,notify更强一点,(多个线程wait,但notify是随机唤醒一个,Condition可以指定线程唤醒)
(5)用法不同:synchronized 可以用来修饰普通方法、静态方法和代码块;ReentrantLock 只能用于代码块;
(6)响应中断不同:synchronized 不能响应中断;ReentrantLock 可以响应中断,可用于解决死锁的问题;
(7)底层实现不同:synchronized 是 JVM 层面通过监视器实现的;ReentrantLock 是基于 AQS 实现的。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

十一.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值