3.线程安全问题(重点)

Java多线程文章目录

目录

方式一:同步代码块

同步监视器总结

总结一:

总结二:

总结三:

方式二:同步方法

同步方法与代码块区别

方式三:Lock锁

Lock和synchronized的区别

线程同步的优缺点


        不知道你们在做购买火车票的代码的时候,遇没遇见出现两张一样的火车票的情况,我在运行代码的时候,前三次都是出现2张第9张火车票

PS:图先欠着,当时忘截图了 (⁄ ⁄•⁄ω⁄•⁄ ⁄)

出现上述原因:重票 --->线程安全引起的问题。多个线程在争抢CPU资源的时候,导致资源共享出现问题。一个线程还没执行完,另一个进程就进来了。相当于一个人吃饭没吃完,碗被人抢走了 ( ̄□ ̄;)

解决方法:加锁(lock) --> 加同步 --> 同步监视器(synchronized)

方式一:同步代码块

使用方式:

synchronized(XXXX.clsss){
    // 需要被锁的代码块
}

        其中XXXX.clsss 被称为同步监视器,也就是锁,原理是:当线程开始执行同步代码块前,必须先获得对同步代码块的锁定。并且任何时刻只能有一个线程可以获得对同步监视器的锁定,当同步代码块执行完成后,该线程会释放对该同步监视器的锁定。

同步监视器总结

锁必须多个线程用的是同一把锁!!!不能更改

总结一:

  • 必须是引用数据类型,不能是基本数据类型
  • 也可以创建一个专门的同步监视器,没有任何业务含义
  • 一般使用共享资源做同步监视器即可
  • 在同步代码块中不能改变同步监视器对象的引用
  • 尽量不要String和包装类Integer做同步监视器(尽管String类型唯一,但是它的值可以改变)
  • 建议使用final修饰同步监视器

总结二:

同步代码块执行过程:

  1. 第一个线程来到同步代码块,发现同步监视器open状态,需要close,然后执行其中的代码
  2. 第一个线程执行过程中,发生了线程切换(阻塞 就绪),第一个线程失去了CPU,但是没有开锁open
  3. 第二个线程获取了CPU,来到了同步代码块,发现同步监视器close状态,无法执行其中的代码,第二个线程也进入阻塞状态
  4. 第一个线程再次获取CPU,接着执行后续的代码;同步代码块执行完毕,释放锁open
  5. 第二个线程也再次获取cpu,来到了同步代码块,发现同步监视器open状态,拿到锁并且上锁,由阻塞状态进入就绪状态,再进入运行状态,重复第一个线程的处理过程(加锁)

强调:同步代码块中能发生CPU的切换吗?能!!! 但是后续的被执行的线程也无法执行同步代码块(因为锁仍旧close)

总结三:

        多个代码块使用了同一个同步监视器(锁),锁住一个代码块的同时,也锁住所有使用该锁的所有代码块,其他线程无法访问其中的任何一个代码块。多个代码块使用了同一个同步监视器(锁),锁住一个代码块的同时,也锁住所有使用该锁的所有代码块,其他线程无法访问其中的任何一个代码块

方式二:同步方法

public synchronized void testThread() {
    //需要被同步的代码块
}

        对于关键字synchronized修饰的方法,不需要再指定同步监视器,这个同步方法(非static方法)无需显式地指定同步监视器,同步方法的同步监视器就是this,也就是调用该方法的对象

注:synchronized可以修饰方法,修饰代码块,但是不能修饰构造器、成员变量等

同步方法与代码块区别

  • 同步代码块的效率要高于同步方法
    • 原因:同步方法是将线程挡在了方法的外部,而同步代码块锁将线程挡在了代码块的外部,但是却是方法的内部
  • 同步方法的锁是this,一旦锁住一个方法,就锁住了所有的同步方法
  • 同步代码块只是锁住使用该同步监视器的代码块,而没有锁住使用其他监视器的代码块

方式三:Lock锁

Lock锁是JDK1.5之后引入新一代的线程同步方式

优先级顺序:Lock --->同步代码块 ---> 同步方法

@Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            // open锁
            lock.lock();
            // 如果代码出现异常可能关闭不了锁,那样会耽误其它代码执行,所以用try catch finally 进行捕获
            try {
                if (ticketNum > 0) {
                    // Thread.currentThread().getName() 获取当前线程的名字
                    System.out.println("我在" + Thread.currentThread().getName() + "买到了第" + ticketNum-- + "张车票");
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                // close锁 --->即使有异常,锁也能关闭
                lock.unlock();
            }
        }
    }

Lock和synchronized的区别

  1. synchronized是Java中的关键字,这个关键字的识别是靠JVM来识别完成的,是虚拟机级别的
  2. Lock锁是API级别的,提供了相应的接口和对应的实现类,这个方式更灵活,性能更优
  3. Lock是显式锁(手动开启和关闭锁),synchronized是隐式锁
  4. Lock只有代码块锁,synchronized有代码块锁和方法锁

线程同步的优缺点

  • 如果代码线程安全,效率低
  • 如果代码线程不安全,效率高
  • 可能造成死锁
    • 不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
    • 出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续
  • 解决方法: 减少同步资源的定义,避免嵌套同步
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值