1. CAS无锁机制
1)乐观锁和悲观锁
乐观锁
总是认为不会产生并发问题,每次去取数据的时候总认为不会有其他线程对数据进行修改,因此不会上锁,但是在更新时会判断其他线程在这之前有没有对数据进行修改,一般会使用版本号机制或CAS操作实现。
version方式:一般是在数据表中加上一个数据版本号version字段,表示数据被修改的次数,当数据被修改时,version值会加1。当线程A要更新数据值时,在读取数据的同时也会读取version值,在提交更新时,若刚才读取到的version值为当前数据库中的version值相等时才更新,否则重试更新操作,直到更新成功。
核心SQL代码:
update table set x=x+1, version=version+1 where id=#{id} and version=#{version};
CAS操作方式:即compare and swap 或者 compare and set,涉及到三个操作数,数据所在的内存值,预期值,新值。当需要更新时,判断当前内存值与之前取到的值是否相等,若相等,则用新值更新,若失败则重试,一般情况下是一个自旋操作,即不断的重试。
总是假设最坏的情况,每次取数据时都认为其他线程会修改,所以都会加锁(读锁、写锁、行锁等),当其他线程想要访问数据时,都需要阻塞挂起。可以依靠数据库实现,如行锁、读锁和写锁等,都是在操作之前加锁,在Java中,synchronized的思想也是悲观锁。
2)CAS机制
独占锁是一种悲观锁,synchronized就是一种独占锁,会导致其它所有需要锁的线程挂起,等待持有锁的线程释放锁。
而另一个更加有效的锁就是乐观锁,所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。
乐观锁用到的机制就是CAS,Compare and Swap 比较和交换
3)CAS实现原理
CAS有三个操作数:
内存值V、旧的预期值A、要修改的值B,当且仅当预期值A和内存值V相同时,将内存值修改为B并返回true,否则什么都不做并返回false。
模拟CAS的实现;
//CAS模拟实现,内存值为V,旧的预期值A,要修改的值B
public class SimulatedCAS {
private int value;
public synchronized int get() {
return value; // 内存值 V - value
}
// 旧的预期值 A - expectedValue, 要修改的值 B - newValue
public synchronized int compareAndSwap(int expectedValue, int newValue) {
int oldValue = value;
if (oldValue == expectedValue) {
value = newValue;
}
return oldValue;
}
public synchronized boolean compareAndSet(int expectedValue, int newValue) {
return (expectedValue == compareAndSwap(expectedValue, newValue));
}
}
4)JAVA中使用了CAS 的类:
AtomicXXX类,非阻塞队列ConcurrentLinkedQueue,跳表ConcurrentSkipList。
5)CAS存在的不足之处:
1. ABA问题。因为CAS需要在操作值的时候检查下值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。ABA问题的解决思路就是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加一,那么A-B-A 就会变成1A-2B-3A。
2. 循环时间长开销大。自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销。如果JVM能支持处理器提供的pause指令那么效率会有一定的提升,pause指令有两个作用,第一它可以延迟流水线执行指令(de-pipeline),使CPU不会消耗过多的执行资源,延迟的时间取决于具体实现的版本,在一些处理器上延迟时间是零。第二它可以避免在退出循环的时候因内存顺序冲突(memory order violation)而引起CPU流水线被清空(CPU pipeline flush),从而提高CPU的执行效率。
3. 只能保证一个共享变量的原子操作。当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁,或者有一个取巧的办法,就是把多个共享变量合并成一个共享变量来操作。比如有两个共享变量i=2,j=a,合并一下ij=2a,然后用CAS来操作ij。从Java1.5开始JDK提供了AtomicReference类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行CAS操作。
AQS,非阻塞数据结构和原子变量类(java.util.concurrent.atomic包中的类)
这些concurrent包中的基础类都是使用这种模式来实现的,而concurrent包中的高层类又是依赖于这些基础类来实现的。从整体来看,concurrent包的实现示意图如下:
2. Futrue模式
Future模式核心思想是异步调用。当我们需要调用一个函数的时候,如果这个函数执行很慢,那我们只能进行等待。但有时候,我们可能不急着要结果。因此,我们可以让被调者立即返回,让他慢慢在后台处理这个请求。
一般结合Callable接口实现
Callable位于java.util.concurrent包下,它也是一个接口,在它里面也只声明了一个方法,只不过这个方法叫做call():
Callable实现点击:这篇文章
1) Future接口
Future就是对于具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果。必要时可以通过get方法获取执行结果,该方法会阻塞直到任务返回结果。
Future类位于java.util.concurrent包下,它是一个接口:
public interface Future<V> {
boolean cancel(boolean mayInterruptIfRunning);
boolean isCancelled();
boolean isDone();
V get() throws InterruptedException, ExecutionException;
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
在Future接口中声明了5个方法,下面依次解释每个方法的作用:
- cancel方法用来取消任务,如果取消任务成功则返回true,如果取消任务失败则返回false。参数mayInterruptIfRunning表示是否允许取消正在执行却没有执行完毕的任务,如果设置true,则表示可以取消正在执行过程中的任务。如果任务已经完成,则无论mayInterruptIfRunning为true还是false,此方法肯定返回false,即如果取消已经完成的任务会返回false;如果任务正在执行,若mayInterruptIfRunning设置为true,则返回true,若mayInterruptIfRunning设置为false,则返回false;如果任务还没有执行,则无论mayInterruptIfRunning为true还是false,肯定返回true。
- isCancelled方法表示任务是否被取消成功,如果在任务正常完成前被取消成功,则返回 true。
- isDone方法表示任务是否已经完成,若任务完成,则返回true;
- get()方法用来获取执行结果,这个方法会产生阻塞,会一直等到任务执行完毕才返回;
- get(long timeout, TimeUnit unit)用来获取执行结果,如果在指定时间内,还没获取到结果,就直接返回null。
也就是说Future提供了三种功能:
1)判断任务是否完成;
2)能够中断任务;
3)能够获取任务执行结果。
因为Future只是一个接口,所以是无法直接用来创建对象使用的
使用例子:
1.使用Callable+Future获取执行结果
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
class Task implements Callable<Integer> {
public Integer call() throws Exception{
System.out.println("thread start --");
int res = 0;
for (int i = 0; i < 5; i++){
try{
Thread.sleep(200);
}catch(InterruptedException e){
e.printStackTrace();
}
res += i;
}
return res;
}
}
public class Main {
public static void main(String[] args) throws Exception{
ExecutorService excutor = Executors.newCachedThreadPool();
Task task = new Task();
Future<Integer> fu = excutor.submit(task);
excutor.shutdown();
try{
System.out.println(fu.get());
}catch(ExecutionException e){
e.printStackTrace();
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
2)FutureTask实现类
我们先来看一下FutureTask的实现:
public class FutureTask<V> implements RunnableFuture<V>
FutureTask类实现了RunnableFuture接口,我们看一下RunnableFuture接口的实现:
public interface RunnableFuture<V> extends Runnable, Future<V> {
void run();
}
可以看出RunnableFuture继承了Runnable接口和Future接口,而FutureTask实现了RunnableFuture接口。所以它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值。
FutureTask提供了2个构造器:
public FutureTask(Callable<V> callable) {
}
public FutureTask(Runnable runnable, V result) {
}
事实上,FutureTask是Future接口的一个唯一实现类。
FutureTask 和 Future 接口的不同之处:
ExecutorService executor = Executors.newCachedThreadPool();
Task task = new Task();
Future<Integer> result = executor.submit(task);
executor.shutdown();
实现分析:
在快递员送厨具的期间,我们肯定不会闲着,可以去超市买食材。所以,在主线程里面另起一个子线程去网购厨具。
package test;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class FutureCook {
public static void main(String[] args) throws InterruptedException, ExecutionException {
long startTime = System.currentTimeMillis();
// 第一步 网购厨具
Callable<Chuju> onlineShopping = new Callable<Chuju>() {
@Override
public Chuju call() throws Exception {
System.out.println("第一步:下单");
System.out.println("第一步:等待送货");
Thread.sleep(5000); // 模拟送货时间
System.out.println("第一步:快递送到");
return new Chuju();
}
};
FutureTask<Chuju> task = new FutureTask<Chuju>(onlineShopping);
new Thread(task).start();
// 第二步 去超市购买食材
Thread.sleep(2000); // 模拟购买食材时间
Shicai shicai = new Shicai();
System.out.println("第二步:食材到位");
// 第三步 用厨具烹饪食材
if (!task.isDone()) { // 联系快递员,询问是否到货
System.out.println("第三步:厨具还没到,心情好就等着(心情不好就调用cancel方法取消订单)");
}
Chuju chuju = task.get();
System.out.println("第三步:厨具到位,开始展现厨艺");
cook(chuju, shicai);
System.out.println("总共用时" + (System.currentTimeMillis() - startTime) + "ms");
}
// 用厨具烹饪食材
static void cook(Chuju chuju, Shicai shicai) {}
// 厨具类
static class Chuju {}
// 食材类
static class Shicai {}
}
--------------------------- end ---------------------------
项目推荐:
Java微服务实战296集大型视频-谷粒商城【附代码和课件】
Java开发微服务畅购商城实战【全357集大项目】-附代码和课件
-----------------------------------------------------------------------------