线程安全
线程安全就是多个线程去执行某类,这个类始终能表现出正确的行为,那么这个类就是线程安全的
我们判断是否要处理线程安全问题,就看有没有多个线程同时访问一个共享变量
- 能不能保证操作的原子性,考虑atomic包下的类够不够我们使用。
- 能不能保证操作的可见性,考虑volatile关键字够不够我们使用
- 如果涉及到对线程的控制(比如一次能使用多少个线程,当前线程触发的条件是否依赖其他线程的结果),考虑CountDownLatch/Semaphore等等。
- 如果是集合,考虑java.util.concurrent包下的集合类。
- 如果synchronized无法满足,考虑lock包下的类
死锁:当前线程拥有其他线程需要的资源,当前线程等待其他线程已拥有的资源,都不放弃自己拥有的资源
避免的方法:
固定加锁顺序
尽可能缩短加锁范围,等到真正操作共享变量时再加锁
使用可释放的定时锁
CAS
CAS是典型的乐观锁
乐观锁:认为其他线程不会同时修改数据,因此在更新数据时,不会先对数据进行锁定,而是通过版本号等机制判断在此期间是否有人修改了数据。如果别人修改了数据,则放弃操作,否则执行操作。
悲观锁:相反,在操作数据时认为其他线程会同时修改数据,因此直接对数据进行锁定,直到操作完成后才会释放锁。在此期间,其他线程不能修改数据。
CAS的全称为compare and swap,比较并交换,是原子性的操作
CAS 有三个操作数:当前值A、内存值V、要修改的新值B
假设 当前值A 跟 内存值V 相等,那就将 内存值V 改成B
假设 当前值A 跟 内存值V 不相等,要么就重试,要么就放弃更新
将当前值与内存值进行对比,判断是否有被修改过,这就是CAS的核心
为什么要先比较?
如果内存中的值已经被其他线程修改过,那么这个值与预期值不同,CAS操作就会失败,需要重新尝试。因此,通过比较预期值和内存中的值,可以保证数据的一致性和操作的原子性
LongAdder
阿里巴巴开发手册提及到 推荐使用 LongAdder 对象,比 AtomicLong 性能更好(减少乐观锁的重试次数)这是为什么
LongAdder内部维护了一个或多个变量,这些变量被称为"cell"。每个线程都可以独立地访问这些变量进行加法操作,而不会发生竞争。当多个线程同时对同一个变量进行加法操作时,LongAdder会将这些加法操作分摊到不同的"cell"上,从而减少竞争条件的出现。
原理
LongAdder的底层实现原理主要包括两个重要概念:“Cell"和"Base”。
“Cell”(单元)
LongAdder内部维护了一个或多个"cell",每个"cell"都是一个独立的变量,用于存储部分加法操作的结果。在初始化时,默认情况下会创建一个"cell"。“Base”(基准)
除了"cell",LongAdder还维护了一个名为"base"的变量,用于存储没有被分配到"cell"的加法操作的结果。"base"是一个普通的long类型变量。当一个线程进行加法操作时,LongAdder会采取以下策略:
如果当前线程是第一个进行加法操作的线程,那么直接将操作的结果累加到"base"上。
如果当前线程不是第一个进行加法操作的线程,那么LongAdder会将操作的结果累加到当前线程所分配的"cell"上。
当多个线程同时进行加法操作时,每个线程都会被分配到一个独立的"cell",从而减少了竞争条件。当需要获取最终的加法结果时,LongAdder会将"base"和所有"cell"中的值进行求和,得到最终的结果。
需要注意的是,LongAdder在进行求和操作时,并不会阻塞其他线程的加法操作。这样就实现了高并发环境下的高性能加法操作
Synchronized
悲观锁
一种互斥锁,一次只能允许一个线程进入被锁住的代码块
synchronized是Java的一个关键字,它能够将代码块/方法锁起来
如果synchronized修饰的是实例方法,对应的锁则是对象实例
如果synchronized修饰的是静态方法,对应的锁则是当前类的Class实例
如果synchronized修饰的是代码块,对应的锁则是传入synchronized的对象实例
当使用synchronized修饰静态方法时,对应的锁就是当前类的Class实例。这里的"锁"是指用于同步控制的对象,即被上锁的目标。
在Java中,synchronized关键字用于实现同步控制,确保同一时刻只有一个线程可以执行某个方法或代码块。当一个线程进入同步方法或代码块时,它会获取对应的锁,如果其他线程也想要获取这个锁,则会被阻塞,直到第一个线程释放这个锁。
对于静态方法,如果使用synchronized修饰,则该方法属于类级别的方法,而不是实例级别的方法。因此,在执行该方法时,会使用当前类的Class实例作为锁对象。
这样做的好处是,静态方法属于类本身,而不是类的实例,因此使用类实例作为锁可能会导致不同实例之间的竞争。而使用类级别的锁可以确保同一时刻只有一个线程可以执行该类的静态方法
公平锁
公平锁指的就是:在竞争环境下,先到临界区的线程比后到的线程一定更快地获取得到锁
非公平就很好理解了:先到临界区的线程未必比后到的线程更快地获取得到锁