java并发常用处理方式_Java基础-浅析解决并发的几种方式

前言

在上一篇中,我们讨论了Java中的关键字volatile和synchronized

那么我们可以再想想,除了synchronized我们还有什么解决并发的方式呢?

目录

ce1dc6cfe919

目录

除了我们目录里面,还有其他的解决并发的方式,如读写锁等,这里不作介绍。

一. synchronized

请看我的上一篇文章,这里不再赘述。

二. 锁对象

在JDK 1.5中,出现了ReentrantLock类,为了方便使用,先看一下它的构造函数:

public ReentrantLock(boolean fair) {

// fair代表着是否是公平锁

// 1. 如果是公平锁,当获取锁的时候,先来的线程会先获取到锁

// 2. 如果不是公平锁,不用排队,直接获取锁

sync = fair ? new FairSync() : new NonfairSync();

}

(1) 使用

private ReentrantLock myLock = new ReentrantLock(false);

private void doSomeThing(){

// 线程如果想访问锁里面的代码

// 必须得先获取锁,如果锁被之前的线程还没有释放锁,那么线程就会阻塞,

// 直到之前的线程释放锁

myLock.lock();

try{

...

}finally{

// 处理完了记得释放锁

myLock.unlock();

}

}

(2) 注意

听起来公平锁要合理的多,不过公平锁要比常规锁慢的多,通常还是不建议使用公平锁的。

和synchronized类似,线程可以重复地获得已经持有地锁,锁会用一个持有计数来跟踪线程对lock()方法的嵌套调用。

三. 原子操作类

1. CAS(compare and set)

大家应该都很熟悉AtomicXXX了,除了使用synchronized,这应该是实现原子操作最常用的一种方式。AtomicXXX的是一种乐观锁,每次去修改数据的时候都会认为别人没有在更新数据,等到要更新结果的时候再去比对值,确定值没有被修改的前提下再更新值。

(1) 使用

AtomicXXX下面有很多种类型,比如AtomicBoolean、AtomicLong和AtomicReference等,这里以 AtomicInteger为例:

private AtomicInteger num = new AtomicInteger(3);

...

// observed是一个int类型的数字

private void max() {

int oldValue = num.get();

int newValue = Math.max(oldValue, observed);

num.set(oldValue);

}

你以为这样就实现原子操作了?非也非也,其实这是个错误的示范,那我们看一下正确的使用姿势:

private AtomicInteger num = new AtomicInteger(3);

// observed是一个int类型的数字

private void add() {

int oldValue,newValue;

do {

// 1. 先获取旧的值

// 2. 比较大小

// 3. 检查num是否发生过更改

// 4. 如果num发生变化,就意味着我们的操作失败,重复执行如上1-3操作

// 5. 如果num没发生变化,就更新当前的值

oldValue = num.get();

newValue = Math.max(oldValue, observed);

}while(!num.compareAndSet(oldValue, newValue));

}

(2) 注意

相比synchronized和ReentrantLock,乐观锁会减去线程挂起和恢复的开销,提升系统的运行效率。当然了,乐观锁也会有缺点,当线程的并发数量上来的时候,大量的线程操作相同的原子值,乐观更新的失败几率比较高,可能需要重复多次,因此,乐观锁适用于多读的系统。

2. Java 8新增的xxxAdder和xxxAccumulator

Java 8新增的这些原子操作类就是为了解决上述乐观锁的并发问题的

(1) 使用

LongAdder是以LongAccumulator为基础的,LongAdder只能使用累加,并且初始值只能设置为0,相比之下,LongAccumulator可以实现更复杂的运算和设置初始值。这里以LongAccumulator为例:

// 先自定义实现LongBinaryOperator

class MyOp implements LongBinaryOperator{

@Override

public long applyAsLong(long left, long right) {

return left + right;

}

}

// 第一个参数是处理自定义实现的处理方法

// 第二个参数就是LongAccumulator累加器的初始值

private LongAccumulator num = new LongAccumulator(new MyOp(), 2);

private void add(int observed) {

num.accumulate(observed);

}

(2) 使用场景

我们来看一下《Java核心技术卷》是怎么说的:

LongAdder包括多个变量(加数),其总和为当前值。可以有多个线程更新不同的加数,线程个数增加时会自动提供新的加数。通常情况下,只有当所有工作都完成之后才需要总和的值,对于这种情况,这种方法会很高效。

因此,LongAdder和LongAccumulator适合高并发下的计数问题。

(3) 注意

看到这里,你可能会有这样的想法,XXXAdder和XXXAccumulator这么厉害了,是不是意味着我们可以抛弃AtomicXXX了?当然不可能,XXXAdder和XXXAccumulator只适合高并发下的计数问题,除此之外,LongAccumulator

也使用了CAS的方式处理数据。

四. 总结

ce1dc6cfe919

总结

总结得出来的使用技巧是:

优先考虑synchronized,需要性能调优的时候考虑Lock。

优先使用传统的互斥方式,当性能方面的需求有明确指示的时候,考虑Atomic。

高并发计数优先考虑XXXAdder和XXXAccumulator。

本人水平有限,难免会有错误,如有错误,欢迎提出。

Over~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值