JAVA之线程安全问题

本文探讨了Java并发编程中的线程安全问题,涉及原子性、可见性和有序性的概念,以及如何使用synchronized、lock和volatile关键字确保线程安全。还讨论了死锁的形成原因和解决策略,以及sync机制在处理多线程问题上的思考顺序和Lock与synchronized的区别。
摘要由CSDN通过智能技术生成

当我们使用多个线程访问同一资源(可以是同一个变量、同一个文件、同一条记录等)的时候,若多个线程只有读操作,那么不会发生线程安全问题。但是如果多个线程中对资源有读和写的操作,就容易出现线程安全问题。

1.线程安全相关:


要考虑线程安全问题,就需要先考虑Java并发的三大基本特性:原子性、可见性以及有序性
1、原子性:原子性是指在一个操作中就是cpu不可以在中途暂停然后再调度,即不被中断操作,要不全部执行完成,要不都不执行。
2、可见性:当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。若两个线程在不同的cpu,那么线程1改变了i的值还没刷新到主存,线程2又使用了i,那么这个i值肯定还是之前的,线程1对变量的修改线程没看到这就是可见性问题。
3、有序性
程序执行的顺序按照代码的先后顺序执行,在多线程编程时就得考虑这个问题。

2.如何确保线程安全问题?


解决办法:使用多线程之间使用关键字synchronized、或者使用锁(lock),或者volatile关键字。
①synchronized(自动锁,锁的创建和释放都是自动的);
②lock 手动锁(手动指定锁的创建和释放)。
③volatile关键字
如果可能会发生数据冲突问题(线程不安全问题),只能让当前一个线程进行执行。代码执行完成后释放锁,然后才能让其他线程进行执行。这样的话就可以解决线程不安全问题。

3.synchronized关键字

将可能会发生线程安全问题地代码,给包括起来,也称为同步代码块。synchronized使用的锁可以是对象锁也可以是静态资源,如×××.class,只有持有锁的线程才能执行同步代码块中的代码。没持有锁的线程即使获取cpu的执行权,也进不去。

锁的释放是在synchronized同步代码执行完毕后自动释放。
同步的前提:
1,必须要有两个或者两个以上的线程,如果小于2个线程,则没有用,且还会消耗性能(获取锁,释放锁)
2,必须是多个线程使用同一个锁

弊端:多个线程需要判断锁,较为消耗资源、抢锁的资源。

同步函数
将synchronized加在方法上
分为两种:

第一种是非静态同步函数,即方法是非静态的,使用的this对象锁。

第二种是静态同步函数,即方法是用static修饰的,使用的锁是当前类的class文件(xxx.class)。

4.死锁问题

不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。简单来说就是:同步中嵌套同步,导致锁无法释放。
一旦出现死锁,整个程序既不会发生异常,也不会给出任何提示,只是所有线程处于阻塞状态,无法继续。

诱发死锁的原因:
互斥条件
占用且等待
不可抢夺(或不可抢占)
循环等待
以上4个条件,同时出现就会触发死锁。

解决死锁:
死锁一旦出现,基本很难人为干预,只能尽量规避。可以考虑打破上面的诱发条件。
针对条件1:互斥条件基本上无法被破坏。因为线程需要通过互斥解决安全问题。
针对条件2:可以考虑一次性申请所有所需的资源,这样就不存在等待的问题。
针对条件3:占用部分资源的线程在进一步申请其他资源时,如果申请不到,就主动释放掉已经占用的资源。
针对条件4:可以将资源改为线性顺序。申请资源时,先申请序号较小的,这样避免循环等待问题。

5.同步机制解决线程安全问题的思考顺序


1、如何找问题,即代码是否存在线程安全?(非常重要)
(1)明确哪些代码是多线程运行的代码
(2)明确多个线程是否有共享数据
(3)明确多线程运行代码中是否有多条语句操作共享数据

2、如何解决呢?(非常重要)
对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以参与执行。即所有操作共享数据的这些语句都要放在同步范围中


6.lock关键字


可以视为synchronized的增强版,提供了更灵活的功能。该接口提供了限时锁等待、锁中断、锁尝试等功能。synchronized实现的同步代码块,它的锁是自动加的,且当执行完同步代码块或者抛出异常后,锁的释放也是自动的。
但是Lock锁是需要手动去加锁和释放锁,所以Lock相比于synchronized更加的灵活。且还提供了更多的功能比如说

tryLock()方法会尝试获取锁,如果锁不可用则返回false,如果锁是可以使用的,那么就直接获取锁且返回true

7.volatile关键字


当多个线程同时访问一个数据的时候,可能本地内存没有及时刷新到主内存,所以就会发生线程安全问题
解决办法:
当出现这种问题时,就可以使用Volatile关键字进行解决。

Volatile 关键字的作用是变量在多个线程之间可见。使用Volatile关键字将解决线程之间可见性,强制线程每次读取该值的时候都去“主内存”中取值。

只需要在flag属性上加上该关键字即可。
子线程每次都不是读取的线程本地内存中的副本变量了,而是直接读取主内存中的属性值。

volatile虽然具备可见性,但是不具备原子性。

8.synchronized、volatile和Lock之间的区别

synochronizd和volatile关键字区别:

1)volatile关键字解决的是变量在多个线程之间的可见性;而sychronized关键字解决的是多个线程之间访问共享资源的同步性。

tip: final关键字也能实现可见性:被final修饰的字段在构造器中一旦初始化完成,并且构造器没有把“this”的引用传递出去(this引用逃逸是一件很危险的事情,其它线程有可能通过这个引用访问到了"初始化一半"的对象),那在其他线程中就能看见final;

2)volatile只能用于修饰变量,而synchronized可以修饰方法,以及代码块。(volatile是线程同步的轻量级实现,所以volatile性能比synchronized要好,并且随着JDK新版本的发布,sychronized关键字在执行上得到很大的提升,在开发中使用synchronized关键字的比率还是比较大);

3)多线程访问volatile不会发生阻塞,而sychronized会出现阻塞;

4)volatile能保证变量在多个线程之间的可见性,但不能保证原子性;而sychronized可以保证原子性,也可以间接保证可见性,因为它会将私有内存和公有内存中的数据做同步。

线程安全包含原子性和可见性两个方面。

对于用volatile修饰的变量,JVM虚拟机只是保证从主内存加载到线程工作内存的值是最新的。

一句话说明volatile的作用:实现变量在多个线程之间的可见性。

synchronized和lock区别:

1)Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;

2)synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;

3)Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;

4)通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。

5)Lock可以提高多个线程进行读操作的效率(读写锁)。

在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。所以说,在具体使用时要根据适当情况选择。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值