JavaEE:synchronized关键字

目录

synchronized的特性

1)互斥

注意:

2)直接修饰普通方法

3)修饰静态方法

4)修饰代码块

锁当前对象:

锁类对象

​编辑

关于锁对象:

 5)可重入


synchronized的特性

1)互斥

多个线程如果同时针对同一个对象进行加锁时(进入synchronized修饰的代码块相当于加锁,退出synchronized修饰的代码块相当于解锁),会发生“锁竞争”,但只有一个线程(先进行加锁操作的)能够加锁成功,其他线程就会阻塞等待


    进入 synchronized 修饰的代码块, 相当于 加锁
    退出 synchronized 修饰的代码块, 相当于 解锁

 

就比如说我们在追女神的时候,有好几个哥么一起追,但是不会说我先追的女神,女神就必须当我的女朋友,这是要去竞争的(这就相当于锁竞争),当有一个兄弟追到了女神,我们只能眼睁睁看着(阻塞等待),等到女神和这个兄弟分手后再次竞争(锁竞争),并不会遵循先来后到的规则

 但是如果多个线程针对不同的对象进行加锁时,不会发生锁竞争:线程1针对A对象加锁,线程2针对B对象加锁,此时不会发生“锁竞争”,也就不会产生阻塞等待。好比几个兄弟各自使用不同的坑位

加锁需要考虑好锁那段代码,锁的代码范围不一样,对代码执行的效果会有很大的影响,锁的代码越多,就叫做"锁定粒度越大/越粗" ; 锁的代码越少,就叫做"锁的粒度越小/越细"

注意:

在这里我们提出一共疑问:当我们需要用到两个线程时,一个线程加锁,一个线程不加锁,这个时候会咋样?线程安全吗?

注意:线程安全,不是加了锁就一定安全.而是通过加锁,让并发修改同一个变量=>串行修改同一个变量,才安全的 ,不正确的加锁姿势,不一定能解决线程安全问题!!!!

class Count{
    public int count = 0;
    public synchronized void increase(){
        for (int i = 0; i < 50000; i++) {
            count++;
        }
    }
    public void increase2(){
        for (int i = 0; i < 50000; i++) {
            count++;
        }
    }
}

public class Demo {
    public static void main(String[] args) throws InterruptedException {
        Count c = new Count();
        Thread t1 = new Thread(() -> {
           c.increase();
        });
        Thread t2 = new Thread(() -> {
           c.increase2();
        });
        t1.start();
        t2.start();

        t1.join();
        t2.join();

        System.out.println("count = " + c.count);
    }
}

 

如果只给一个线程加锁,这个是没啥用的.一个线程加锁,不涉及到"锁竞争",也就不会阻塞等待也就不会并发修改=>串行修改

好比A追到了妹子~~B也想追妹子.虽然A官宣了(加锁了),但是B不讲武德,搞偷袭,搞挖墙脚~~(只要锄头挥得好,没有墙角挖不倒)

2)直接修饰普通方法

synchronized修饰普通成员方法时,被加锁的对象(即锁对象)为当前对象本身,相当于this

public class Test {
    public static int count;
    //synchronized修饰普通成员方法
    public synchronized void increase(){
        count++;
    }
    public static void main(String[] args) throws InterruptedException {
        Test test = new Test();
        //让两个线程同时进行50000次count++操作
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                test.increase();
            }
        });
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                test.increase();
            }
        });
        t1.start();
        t2.start();
 
        t1.join();
        t2.join();
 
        System.out.println("count = " + Test.count);
    }
}

一个线程在调用increase方法进行count++操作时,另一个线程只能阻塞等待,这样就相当于把count++这个不是原子的操作,打包成一个原子操作,从而使count得到正确的结果:

 

具体操作就如同这张图中展示的,当一个线程在进行操作时先加锁了,另一个线程阻塞等待,直到上一个线程释放锁时再执行

3)修饰静态方法

 

4)修饰代码块

锁当前对象:

锁类对象

关于锁对象:

明确指定锁哪个对象:在Java当中,任意的对象,都可以作为锁对象

关于synchronized(),的()的里面咱可以有:

等等...

这里的synchronized(),()里面要填的就是->你要针对哪个对象加锁!!(被用来加锁的对象,就简称为"锁对象")

使用锁的时候,一定要明确,当前是针对哪个对象加锁!!,very关键!!

咱们写多线程的时候,不关心这个锁对象究竟是谁,是那种形态,咱们只关心,多个线程是否锁的是同一个对象,是锁同一个对象,是锁同一个对象就有竞争,不同对象就无竞争

在大部分情况下,咱们可以直接在()中写this,但是在多线程代码中,切忌"无脑写",在大部分情况下是可以直接写this 的,但是具体还是看你的实际需求,希望在那些场景下产生竞争,哪些场景下不产生竞争,锁的对象是不同的!!!

 5)可重入

synchronized 同步块对同一条线程来说是可重入的,不会出现自己把自己锁死的问题;

但是一个线程针对同一把锁加锁两次,就可能造成死锁!

public synchronized void func(){
    synchronized (this){
        count++;
    }
}

第一次加锁,加锁成功,第二次加锁时第一次加的锁还没有释放,阻塞等待

就相当于:第一把锁需要完成锁内的任务才能释放锁,而他的任务包含了第二把锁,而第二把锁需要等待到第一把锁释放锁才能继续完成任务,这就形成了"死锁",这样的锁就称为:"不可重入锁"

Java 中的 synchronized 是 可重入锁, 因此没有上面的问题

在可重入锁的内部,包含“线程持有者”和“计数器”两个信息:

线程持有者:记录第一次加锁的线程,如果该线程在释放第一次加的锁之前再次进行加锁,那么仍然可以加锁成功,并让计数器自增一次。

计数器:加锁时自增一次,解锁时自减一次,只有当计数器递减为0的时候,线程进行解锁操作才会真正释放锁(其他线程才能获取到这个锁)。
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

豆腐乾净找方规

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

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

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

打赏作者

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

抵扣说明:

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

余额充值