Java锁的深度化
Java锁分为:Lock轻量级锁,和synchronized重量级锁 重入锁(什么是重入锁)、 读写锁 、悲观和乐观锁 、CAS无锁、 自旋锁、 AQS锁 、非公平锁、公平锁 、互斥锁 、排他锁 、分布式锁:(redis和Zookeeper实现分布式锁)。
悲观锁
悲观锁悲观的认为每一次操作都会造成更新丢失问题,在每次查询时加上排他锁(每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会lock直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁);缺点:因为只能保证一个连接,所以效率比较低。
乐观锁:
总是认为不会产生并发问题,每次去取数据的时候总认为不会有其他线程对数据进行修改,因此不会上锁,但是在更新时会判断其他线程在这之前有没有对数据进行修改,一般会使用版本号机制或CAS操作实现。本质上是没有锁,无阻塞、无等待,效率高。
version方式:一般是在数据表中加上一个数据版本号version字段,表示数据被修改的次数,当数据被修改时,version值会加一。当线程A要更新数据值时,在读取数据的同时也会读取version值,在提交更新时,若刚才读取到的version值为当前数据库中的version值相等时才更新,否则重试更新操作,直到更新成功。
核心SQL语句:update table set x=x+1, version=version+1 where id=#{id} and version=#{version};
重入锁
重入锁也叫做递归锁,指的是同一线程 外层函数获得锁之后 ,内层递归函数仍然有获取该锁的代码,但不受影响。(重进入是指任一线程获取到锁后能够再次获取改锁而不会受阻塞) 在JAVA环境下 ReentrantLock 和synchronized 都是可重入锁。
读写锁
ReentrantLock的是排他锁,在同一个时刻只允许一个线程访问,在程序中涉及到对一些共享资源的读和写操作,且写操作没有读操作那么频繁。在没有写操作的时候,两个线程同时读一个资源没有任何问题,所以应该允许多个线程能在同时读取共享资源。但是如果有一个线程想去写这些共享资源,就不应该再有其它线程对该资源进行读或写 ,而读写锁在同一个时刻可以允许多个线程访问,但是写线程在访问时,所以的读锁和 其他线程都会被阻塞。读写锁维护了一个锁,一个读锁和一个写锁,通过分离读锁和写锁使得并发比一般的排他锁有了很大提升,Java1.5提供了ReentrantReadWriteLock来实现读写锁。
原子类
原子变量类相当于一种泛化的 volatile 变量,能够支持原子的和有条件的读-改-写操作。AtomicInteger 表示一个int类型的值,并提供了 get 和 set 方法,这些 Volatile 类型的int变量在读取和写入上有着相同的内存语义。它还提供了一个原子的 compareAndSet 方法(如果该方法成功执行,那么将实现与读取/写入一个 volatile 变量相同的内存效果),以及原子的添加、递增和递减等方法。AtomicInteger 表面上非常像一个扩展的 Counter 类,但在发生竞争的情况下能提供更高的可伸缩性,因为它直接利用了硬件对并发的支持。
原子类是线程安全非阻塞、底层没有使用锁。 原子类底层原理使用CAS无锁技术 ,CAS:Compare and Swap,即比较再交换。
JDK1.8的实现方式:
JDK1.8之前的实现
CAS无锁机制
(1)与锁相比,使用比较交换(下文简称CAS)会使程序看起来更加复杂一些。但由于其非阻塞性,它对死锁问题天生免疫,并且,线程间的相互影响也远远比基于锁的方式要小。更为重要的是,使用无锁的方式完全没有锁竞争带来的系统开销,也没有线程间频繁调度带来的开销,因此,它要比基于锁的方式拥有更优越的性能。
(2)无锁的好处: 第一,在高并发的情况下,它比有锁的程序拥有更好的性能; 第二,它天生就是死锁免疫的。
CAS算法的过程是这样:它包含三个参数CAS(V,E,N): V表示要更新的变量,E表示预期值,N表示新值。仅当V值等于E值时,才会将V的值设为N,如果V值和E值不同,则说明已经有其他线程做了更新,则当前线程什么都不做。最后,CAS返回当前V的真实值。
CAS操作是抱着乐观的态度进行的,它总是认为自己可以成功完成操作。当多个线程同时使用CAS操作一个变量时,只有一个会胜出,并成功更新,其余均会失败。失败的线程不会被挂起,仅是被告知失败,并且允许再次尝试,当然也允许失败的线程放弃操作。基于这样的原理,CAS操作即使没有锁,也可以发现其他线程对当前线程的干扰,并进行恰当的处理。
简单地说,CAS需要你额外给出一个期望值,也就是你认为这个变量现在应该是什么样子的。如果变量不是你想象的那样,那说明它已经被别人修改过了。你就重新读取,再次尝试修改就好了。
java 内存模型分析CAS原理: