java并发编程(二)线程的安全性

线程安全的核心在于对状态的访问操作,特别是共享状态和可变状态。共享意味着可以同时被多个线程访问,可变意味着生命周期内可以发生变化。
要是对象是线程安全的就要使用同步机制来保证对对象状态的访问。
java中主要的同步机制 包括关键字synchronized独占锁,Lock显式锁,原子变量(amoticInteger等)等,当然也包括volatile类型变量,注意volatile类型变量并不能保证数据的原子性。
在项目开发过程中应遵循先保证代码正确运行,然后再提高代码的性能的原则。即便如此也要在测试或者应用需求告诉你必须提高性能,且测试结果表明优化能显著提高性能时才去优化。原因是因为并发错误是非常难以重现和调试的,如果只是在某段不常用的代码上得到性能的提升,很可能被程序运行时的失败风险抵消。

什么是线程安全性

当多个线程访问某个类时,不管任何环境任何方式或者线程间如何交替运行,在调用者的代码里不需要任何处理,这个类都能正确的被执行。那么这个类就被称为线程安全的。
无状态对象一定是线程安全的,无状态对象是指计算过程中不使用全局变量,计算过程中的全部变量都是局部变量,且未调用其他的非无状态对象。正因为如此在使用全局变量时多考虑是否必要,尽量多使用局部变量。

原子性

在并发编程中由于不正确的执行顺序产生不正确的执行结果叫做竞态条件反过来说就是当计算的正确性取决于执行顺序时就会发生竞态条件。
典型的竞态条件就是“先检查后执行”,比如饱汉单例模式,当调用对象时先检查对象是否存在,而后创建对象。如果一个线程在创建对象成功前另一个对象检查对象是否存在就会出现线程安全问题。
原子性操作是指对于同一个状态的所有操作要么全执行,要么全不执行。

锁机制

在一个对象内存在两个全局变量,如果两个变量都已经做了线程安全相关的处理,是否意味着这个对象一定是线程安全的呢?
答案是不是的,因为这只保证了两个变量各自的原子性,并没有保证整个对象的原子性,如果一个变量对另一个变量产生约束时,需要两个变量同时更新才能保证整个操作的原子性。
java提供了内置锁来处理这种情况,使用synchronized修饰方法加锁保证整个方法的原子性。
java内置锁是一种互斥锁,意味着同一时间只能有一个线程持有这种锁。
并发中的原子性的含义和事物中的原子性相同,一组语句作为一个不可分割的单元执行。
重入是当某个线程试图获取一个已经由他持有的锁,那么请求会成功。也就意味着重入锁的获取锁操作的粒度是线程。
典型的重入情况,当子类重写父类的synchronized方法,然后调用父类的这个方法,因为不论调用哪个方法都会先调用父类的方法,所以当调用子类方法时就已经获取了父类的锁,这时再调用父类的方法不会死锁。
可重入,就是可以重复获取相同的锁,比如一段代码内锁内嵌套这锁,锁都是this那么当线程获取了外层锁时可以直接使用嵌套的锁。

将一段复合操作的执行过程加锁会使这段复合操作称为原子操作,但是仅仅如此是不够的,如果对象的全局变量有被其他线程调用的话 对象依然是不安全的,必须要处理这些变量的同步问题,虽然大多数类都是通过加锁来保证变量的原子性,但是这并不是要求一定要用锁来保证变量的原子性。例如java的atomic包也可以实现同样的效果。

活跃性与性能

当对某一个方法加了synchronied锁以后,这个方法就只能同时被一个线程调用。如果这个方法耗时很长,导致大量线程等待,我们将这种程序称为不良并发程序
所以在实际开发中我们要尽量的缩小同步代码块的作用范围,尽量将不影响共享状态且耗时较长的代码分离出去。

当执行时间较长的操作或可能无法快速完成的操作(I/O操作)时,一定不要加锁。

发布了56 篇原创文章 · 获赞 4 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/xs925048899/article/details/104612851