当前位置: 代码迷 >> 综合 >> Java Concurrent--线程安全性(synchronized)
  详细解决方案

Java Concurrent--线程安全性(synchronized)

热度:34   发布时间:2023-09-21 21:09:25.0

线程安全

  • 要编写线程安全的代码,其核心在于要对状态访问操作进行管理,特别是对共享和可变状态的访问。
  • 从非正式的意义来讲,对象的状态是指存储在状态变量(例如实例或静态域)中的数据,可能包含其它依赖对象的域。
  • 一个对象是否需要实现线程安全,取决于它是否会被多个线程访问。要使得对象是线程安全的,需要采取同步机制来协同对对象可变状态的访问。

Java同步机制:关键字synchronized、volatile类型的变量、显式锁(Lock)、原子变量。

无状态的对象一定是线程安全的。

原子性

竞态条件(Race Condition):计算的正确性取决于多个线程的交替执行时序时,就会发生竞态条件。例如“读取-修改-写入”操作和“先检查后执行”操作。

  • “读取-修改-写入”操作:最经典的就是自增操作。例如count++操作,该操作是非原子性的,实际上它包含三个操作:读取count的值,将值加一,将计算出的结果写入count。如果此时多个线程都访问count并++,那么不能保证最后结果正确。
  • “先检查后执行”操作:经典的例子就是单例模式。

复合操作:要避免竞态条件问题就要保证在某个线程修改变量时,通过某种方式阻止其他线程使用该变量。“读取-修改-写入”操作和“先检查后执行”操作统称为复合操作:包含了一组必须以原子方式执行的操作以确保线程安全性。

加锁机制是Java中用于确保原子性的内置机制。

内置锁Synchronized

同步代码块(Synchronized Block)。内置锁可以支持原子性和可见性。同步代码块包含两部分:

  • 一个作为锁的对象引用;
  • 一个作为由这个锁保护的代码块;

其中该同步代码块的锁就是方法调用所在的对象。静态的synchronized方法以class对象作为锁。

Java的内置锁相当于一种互斥体(或互斥锁),这意味着最多只有一个线程能够持有这种锁。

重入:

当某个线程请求一个其他线程持有的锁时,就会阻塞。因为内置锁是可重入的,如果某个线程试图获得一个已经被自己占有的锁,就会成功。“重入”意味着获取锁的操作的粒度是“线程”,而不是“调用“

重入的一种实现方法是为每一个锁设置一个计数器,同一个线程再次获取这个锁,计数值加一,而当线程退出同步代码块时,计数值减一。

如果内置锁是不可重入的,下面的代码将会死锁:

public class Widget{public synchronized void doSomeThing(){ ... }
}public class LoggingWidget extends Widget{public synchronized void doSomeThing(){//如果是非重入的锁,获取Widget上的锁时就会发生死锁super.doSomeThing();...}
}

锁的使用规则:

  • 对于可能被多个线程同时访问的可变状态变量,在访问它时都需要持有同一个锁。这种情况也可以成为该变量是由这个锁保护的。
  • 每个共享的和可变的状态变量都应该只由一个锁来保护,从而使维护人员知道是哪一个锁。
  • 对于每个包含多个变量的不变性条件,其中涉及的所有变量都需要由同一个锁来保护。

注意:

对象的内置锁和对象的状态之间没有内在的关联。当获取对象关联的锁时,并不能阻止其他线程访问该对象,只能阻止其他线程获取同一个锁。

可以使用@GuardBy标签标注使用的是哪一个锁。

对象锁和类锁

synchronized关键字修饰不同的位置含义不同:

  • 修饰一个类:作用的对象是这个类的所有对象,即它是一个类锁
  • 修饰一个方法:被修饰的方法称为同步方法,作用的对象是调用这个方法的对象,它是对象锁
  • 修改一个静态的方法:作用的对象是这个类的所有对象,它是一个类锁
  • 修饰一个代码块:其作用的范围是大括号{}括起来的代码, 作用的对象是调用这个代码块的对象,是一个对象锁

对象锁和类锁是不同的锁,多个线程同时执行这2个不同锁的方法时,是异步的。

下面用伪代码简单的说明一下类锁和对象锁的区别:

public class Test{public synchronized static methodA{}public synchronized static methodB{}public synchronized methodC{}
}

类Test中有两个类锁,一个对象锁。如果现在我们创建三个线程thread1、thread2、thread3,分别执行对象test的methodA、methodB、method3,那么结果可能是什么?

最后执行的顺序可能是:

线程A开始
线程C开始
线程C结束
线程A结束
线程B开始
线程B结束

即AB肯定是顺序(同步)执行,C和AB是异步执行。

  相关解决方案