JavaEE初阶--------第五章 synchronized 总结

系列文章目录

第五章 synchronized 总结



前言

上一章,我们讲到了线程安全问题,想要解决线程安全问题,核心思路就是加锁,synchronized 关键字就可以进行加锁。


一、synchronized 的特性

互斥

  • synchronized 在使用的时候是要搭配代码块 { } 来使用的,进入 synchronized 修饰的代码块时就相当于进行了加锁,退出synchronized 修饰的代码块时就相当于解锁了。
  • 在已经加锁的状态下,当另一个线程尝试同样加这个锁时,就会产生“锁冲突/锁竞争”。那么,后一个线程就会阻塞等待,一直等到前一个已经加锁了的线程解锁为止
public class Demo2 {
    private static int count = 0;

    Object locker = new Object();
    public static void main(String[] args) throws InterruptedException {

        Object locker = new Object();

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

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

        t1.start();
        t2.start();

        t1.join();
        t2.join();
        System.out.println(count);
    }
}

在这里插入图片描述

  • 那么,在上述两条规则的限定下,大家也可以很轻松地理解在加了锁以后,两个线程其实已经不是同时执行还是变成了具有先后关系地去执行。由并发执行变成了串行执行。

在这里插入图片描述

二、synchronized 的使用

1、synchronized 修饰一个实例方法

class Counter{
    public int count;

    synchronized public void increase1(){
        count++;
    }

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

public class Demo1 {
    public static void main(String[] args) throws InterruptedException {
        Counter counter = new Counter();

        Thread t1 = new Thread(() ->{
            for (int i = 0; i < 5000; i++) {
                counter.increase1();
            }
        });

        Thread t2 = new Thread(() ->{
            for (int i = 0; i < 5000; i++) {
                counter.increase1();
            }
        });

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

        System.out.println(counter.count);
    }
}

  • 上面就是 synchronized 修饰了一个实例方法
    在这里插入图片描述
  • increase1 和 increase2 是等价的,increase1 的写法是 increase2 的简化版本。

2、synchronized 修饰一个静态方法

在这里插入图片描述

  • increase3 和 increase4 是等价的,increase3 的写法是 increase4 的简化版本。

三、synchronized 的锁机制

可重入锁(重要)

  • 所谓的可重入锁,指的是:一个线程针对一把锁加锁两次,不会出现死锁。满足这个要求,就是“可重入”,不满足,就是“不可重入”。

那么,什么又是死锁呢?

//线程t
synchronized(locker){
	synchronized(locker){
	......
	}
}	
  • 观察上面线程 t,假设第一次加锁成功了。此时 locker 就属于是“被锁定”状态,但是紧接着又要进行第二次加锁操作,发现同样加的也是 locker 锁对象,但是之前的锁还没有解锁,原则上来说就要进行阻塞等待。但是,如果这个不进行加锁,代码也就不会往下执行,那么第一次加锁操作也不会解锁,那么,就出现了死锁的情况,也就是说,线程卡死了

  • 于是把 synchronized 设计成“可重入锁”,就可以有效解决上述死锁问题。
    也就是说,让锁记录一下,是哪个线程给它锁住的,后续再加锁的时候,如果加锁线程就是持有锁的线程就直接加锁成功

  • 关于出现死锁可能情况
    1、一个线程,针对 一把锁,连续加锁两次(如果是不可重入锁,就死锁了)
    2、两个线程,两把锁
    t1 线程获取了锁A,t2 线程获取了锁B,但是现在 t1线程尝试获取B,t2 线程尝试获取A。

public class Demo3 {
    private static Object locker1 = new Object();
    private static Object locker2 = new Object();

    public static void main(String[] args) {

        //此时的 sleep 很重要,保证 t1和t2 分别都已经各自拿到了一把锁
        Thread t1 = new Thread(() ->{
            synchronized (locker1){
                try {
                    Thread.sleep(10000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }

            synchronized (locker2){
                System.out.println("t1加锁成功!");
            }
        });

        Thread t2 = new Thread(() ->{
            synchronized (locker2){
                try {
                    Thread.sleep(10000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }

            synchronized (locker1){
                System.out.println("t2加锁成功!");
            }
        });

        t1.start();
        t2.start();

    }
}

在这里插入图片描述

从这里的执行结果可以看出,出现了死锁情况,当线程 t1 获取第二把锁B时,此时锁B已经被线程 t2 获取了,于是拿不到了。

3、N个线程,M把锁

  • 死锁的成因(四个必要条件)
  1. 互斥使用(锁的基本特性)
    当一个线程持有一把锁之后,另一个线程也想获取到锁,就要阻塞等待
  2. 不可抢占(锁的基本特性)
    当锁已经被线程1拿到之后,线程2只能等待线程1主动释放,不能强行抢过来
  3. 请求保持(代码结构)(避免编写“锁嵌套”逻辑)
    一个线程尝试获取多把锁,先拿到锁1之后,再尝试获取锁2,获取锁2的时候,锁1不会释放
  4. 循环等待(等待的依赖关系,形成环了)

想要解决死锁问题,破坏上述一个必要条件即可。
1和2破坏不了(这是synchronized 自带的特性,无法人为进行干预)。对于3来说,调整代码结构,避免编写“锁嵌套”逻辑对于4来说,可以约定加锁的顺序,就可以避免循环等待了

  • 谈谈对死锁的理解
  1. 死锁其实就是线程出现了卡死的状况
  2. 有三种出现死锁的可能情况
  3. 造成死锁的四个必要条件
  4. 如何解决死锁问题
    调整代码结构,避免编写“锁嵌套”逻辑。
    约定加锁的顺序,就可以避免循环等待了。

总结

这章我们总结了 synchronized 的特性,学习了如何使用 synchronized ,还知道了 synchronized 是一个可重入锁,还知道了什么是死锁,出现死锁的可能情况以及如何解决死锁问题。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值