Java锁的深度化

悲观锁、乐观锁、排他锁

场景

当多个请求同时操作数据库时,首先将订单状态改为已支付,在金额加上200,在同时并发场景查询条件下,会造成重复通知。 SQL: Update

悲观锁与乐观锁

  • 悲观锁:

    悲观锁悲观的认为每一次操作都会造成更新丢失问题,在每次查询时加上排他锁。 每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。
  • 乐观锁:

    乐观锁会乐观的认为每次查询都不会造成更新丢失,利用版本字段控制

重入锁

锁作为并发共享数据,保证一致性的工具,在JAVA平台有多种实现(如 synchronized 和 ReentrantLock等等 ) 。这些已经写好提供的锁为我们开发提供了便利。 重入锁,也叫做递归锁,指的是同一线程 外层函数获得锁之后 ,内层递归函数仍然有获取该锁的代码,但不受影响。 在JAVA环境下 ReentrantLock 和synchronized 都是 可重入锁


public class SynchronizedTest implements Runnable {
public synchronized void get(){
System.out.println(Thread.currentThread().getName()+" get()");
set();
} private synchronized void set() {
System.out.println(Thread.currentThread().getName()+" set()");
} @Override
public void run() {
get();
}
public static void main(String[] args){
SynchronizedTest test = new SynchronizedTest();
new Thread(test).start();
new Thread(test).start();
new Thread(test).start();
new Thread(test).start();
}
//Thread-0 get()
//Thread-0 set()
//Thread-3 get()
//Thread-3 set()
//Thread-2 get()
//Thread-2 set()
//Thread-1 get()
//Thread-1 set()
}

import java.util.concurrent.locks.ReentrantLock; public class ReentrantLockDemo extends Thread{
ReentrantLock lock = new ReentrantLock();
public void get(){
lock.lock();
System.out.println(Thread.currentThread().getId());
set();
lock.unlock();
} private void set() {
lock.lock();
System.out.println(Thread.currentThread().getId());
lock.unlock();
} @Override
public void run() {
get();
}
public static void main(String[] args){
ReentrantLockDemo demo = new ReentrantLockDemo();
new Thread(demo).start();
new Thread(demo).start();
new Thread(demo).start();
}
//10
//10
//11
//11
//12
//12
}

读写锁

相比Java中的锁(Locks in Java)里Lock实现,读写锁更复杂一些。假设你的程序中涉及到对一些共享资源的读和写操作,且写操作没有读操作那么频繁。 在没有写操作的时候,两个线程同时读一个资源没有任何问题,所以应该允许多个线程能在同时读取共享资源。但是如果有一个线程想去写这些共享资源, 就不应该再有其它线程对该资源进行读或写(读-读能共存,读-写不能共存,写-写不能共存)。

原子类

java.util.concurrent.atomic包:原子类的小工具包,支持在单个变量上解除锁的线程安全编程

原子变量类相当于一种泛化的 volatile 变量,能够支持原子的和有条件的读-改-写操作。AtomicInteger 表示一个int类型的值,并提供了 get 和 set 方法,这些 Volatile 类型的int变量在读取和写入上有着相同的内存语义。它还提供了一个原子的 compareAndSet 方法(如果该方法成功执行,那么将实现与读取/写入一个 volatile 变量相同的内存效果),以及原子的添加、递增和递减等方法。AtomicInteger 表面上非常像一个扩展的 Counter 类,但在发生竞争的情况下能提供更高的可伸缩性,因为它直接利用了硬件对并发的支持。

为什么会有原子类

  • CAS:Compare and Swap,即比较再交换。

    jdk5增加了并发包java.util.concurrent.*,其下面的类使用CAS算法实现了区别于synchronouse同步锁的一种乐观锁。JDK 5之前Java语言是靠synchronized关键字保证同步的,这是一种独占锁,也是是悲观锁。
  • 如果同一个变量要被多个线程访问,则可以使用该包中的类(原子类)
    • AtomicBoolean
    • AtomicInteger
    • AtomicLong
    • AtomicReference

常用原子类

Java中的原子操作类大致可以分为4类:原子更新基本类型、原子更新数组类型、原子更新引用类型、原子更新属性类型。这些原子类中都是用了无锁的概念,有的地方直接使用CAS操作的线程安全的类型。


import java.util.concurrent.atomic.AtomicInteger; public class AtomicIntegerDemo implements Runnable{
private static Integer count = 1;
private static AtomicInteger atomicInteger = new AtomicInteger(); @Override
public void run() {
while (true){
// int count = getCount();
int count = getCountAtomic();
System.out.println(count);
if (count>=50){
break;
}
}
} private Integer getCountAtomic() {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
return atomicInteger.incrementAndGet();
} public synchronized Integer getCount(){
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
return count++;
}
public static void main(String[] args){
AtomicIntegerDemo demo = new AtomicIntegerDemo();
Thread t1 = new Thread(demo);
Thread t2 = new Thread(demo);
t1.start();
t2.start();
}
}

CAS(乐观锁算法)无锁机制

  • 与锁相比,使用比较交换(下文简称CAS)会使程序看起来更加复杂一些。但由于其非阻塞性,它对死锁问题天生免疫,并且,线程间的相互影响也远远比基于锁的方式要小。更为重要的是,使用无锁的方式完全没有锁竞争带来的系统开销,也没有线程间频繁调度带来的开销,因此,它要比基于锁的方式拥有更优越的性能。
  • 无锁的好处:
    • 在高并发的情况下,它比有锁的程序拥有更好的性能
    • 它天生就是死锁免疫的

CAS缺点

CAS存在一个很明显的问题,即ABA问题。

  • 问题:如果变量V初次读取的时候是A,并且在准备赋值的时候检查到它仍然是A,那能说明它的值没有被其他线程修改过了吗?

    如果在这段期间曾经被改成B,然后又改回A,那CAS操作就会误认为它从来没有被修改过。针对这种情况,java并发包中提供了一个带有标记的原子引用类AtomicStampedReference,它可以通过控制变量值的版本来保证CAS的正确性。

AtomicReference

  • AtomicReference用以支持对象的原子操作:AtomicReference 可以封装引用一个V实例
  • public final boolean compareAndSet(V expect, V update) ,可以支持并发访问,set的时候进行对比判断,如果当前值和操作之前一样则返回false,否则表示数据没有变化

import java.util.concurrent.atomic.AtomicReference; /**
* CAS算法:它包含三个参数CAS(V,E,N): V表示要更新的变量,E表示预期值,N表示新值。
* 仅当V值等于E值时,才会将V的值设为N,如果V值和E值不同,则说明已经有其他线程做了更新,则当前线程什么都不做。
* 最后,CAS返回当前V的真实值
*/
public class SpinLockDemo implements Runnable {
static int sum;
private SpinLock lock;
public SpinLockDemo(SpinLock lock) {
this.lock = lock;
}
@Override
public void run() {
this.lock.lock();
sum++;
this.lock.unlock();
}
public static void main(String[] args) throws InterruptedException{
SpinLock spinLock = new SpinLock();
for (int i = 0; i < 100; i++) {
SpinLockDemo demo = new SpinLockDemo(spinLock);
Thread thread = new Thread(demo);
thread.start();
}
Thread.currentThread().sleep(1000);
System.out.println(sum);//100
}
} //自旋锁是采用让当前线程不停地的在循环体内执行实现的,当循环的条件被其他线程改变时 才能进入临界区
class SpinLock{
//Java中的原子操作(CAS)
//持有自旋锁的线程对象
AtomicReference<Thread> sign = new AtomicReference<>();
public void lock(){
Thread thread = Thread.currentThread();
//lock函数将thread设置为当前线程,并且预测原来的值为null
//当有第二个线程调用lock操作时由于thread的值不为空,导致循环
//一直被执行,直至第一个线程调用unclock函数将sign设置为null,第二个线程才能进入临界区
while (!sign.compareAndSet(null,thread));
}
public void unlock(){
//unlock将sign的值设置为null,并且预测值为当前线程
Thread thread = Thread.currentThread();
sign.compareAndSet(thread,null);
}
}

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicReference; public class AtomicReferenceTest {
private static AtomicReference<Integer> ar = new AtomicReference<>(0);
public static void test() throws InterruptedException{
int count = 3;
final int c = 3;
final CountDownLatch latch = new CountDownLatch(count);
for (int i = 0; i < count; i++) {
new Thread(()->{
for (int j = 0; j < c; j++) {
while (true){
Integer temp = ar.get();
System.out.println("temp="+temp);
//public final boolean compareAndSet(V expect, V update)
if (ar.compareAndSet(temp,temp+1)){
break;
}
}
}
latch.countDown();
}).start();
}
latch.await();
System.out.println(ar.get());
}
public static void main(String[] args) throws InterruptedException{
test();
//temp=0
//temp=0
//temp=1
//temp=2
//temp=1
//temp=3
//temp=3
//temp=4
//temp=4
//temp=5
//temp=5
//temp=6
//temp=7
//temp=8
//9
}
}

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger; /**
* 原子量实现的计数器
*/
public class AtomicCounter {
private AtomicInteger value = new AtomicInteger();
public int getValue(){
return value.get();
}
//+1
public int increase(){
// return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
return value.incrementAndGet();
}
//+delta
public int increase(int delta){
// return unsafe.getAndAddInt(this, valueOffset, delta) + delta;
return value.addAndGet(delta);
}
//-1
public int decrease(){
// return unsafe.getAndAddInt(this, valueOffset, -1) - 1;
return value.decrementAndGet();
}
//-delta
public int decrease(int delta){
// return unsafe.getAndAddInt(this, valueOffset, delta) + delta;
return value.addAndGet(-delta);
}
public static void main(String[] args){
final AtomicCounter counter = new AtomicCounter();
ExecutorService threadPool = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
threadPool.execute(()->{
System.out.println(counter.increase(2));
});
} threadPool.shutdown();
}
}

import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicLong; /**
* 原子量实现的银行取款
*/
public class AtomicAccount {
private AtomicLong balance; public AtomicAccount(long money) {
balance = new AtomicLong(money);
System.out.println("Total Money:"+balance);
}
//存钱
public void deposit(long money){
balance.addAndGet(money);
}
//取钱
public void withdraw(long money){
for (;;){
long oldValue = balance.get();
if (oldValue<money){
System.out.println(Thread.currentThread().getName()+" 余额不足!"+" 余额:"+balance);
break;
}
try {
Thread.sleep(new Random().nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
if (balance.compareAndSet(oldValue,oldValue-money)){
System.out.println(Thread.currentThread().getName()+" 取款:"+money + " 余额:"+balance);
break;
}
System.out.println(Thread.currentThread().getName() + " 遇到并发,再次尝试取款!");
}
}
public static void main(String[] args){
final AtomicAccount account = new AtomicAccount(1000);
ExecutorService threadPool = Executors.newCachedThreadPool();
int i = 0;
while (i++<13){
threadPool.execute(()->{
account.withdraw(100);
});
}
threadPool.shutdown();
}
//Total Money:1000
//pool-1-thread-13 取款:100 余额:900
//pool-1-thread-6 遇到并发,再次尝试取款!
//pool-1-thread-7 遇到并发,再次尝试取款!
//pool-1-thread-5 遇到并发,再次尝试取款!
//pool-1-thread-10 遇到并发,再次尝试取款!
//pool-1-thread-2 遇到并发,再次尝试取款!
//pool-1-thread-3 遇到并发,再次尝试取款!
//pool-1-thread-1 遇到并发,再次尝试取款!
//pool-1-thread-4 遇到并发,再次尝试取款!
//pool-1-thread-8 遇到并发,再次尝试取款!
//pool-1-thread-9 遇到并发,再次尝试取款!
//pool-1-thread-11 遇到并发,再次尝试取款!
//pool-1-thread-12 遇到并发,再次尝试取款!
//pool-1-thread-8 取款:100 余额:800
//pool-1-thread-1 遇到并发,再次尝试取款!
//pool-1-thread-5 遇到并发,再次尝试取款!
//pool-1-thread-6 遇到并发,再次尝试取款!
//pool-1-thread-5 取款:100 余额:700
//pool-1-thread-11 遇到并发,再次尝试取款!
//pool-1-thread-4 遇到并发,再次尝试取款!
//pool-1-thread-7 遇到并发,再次尝试取款!
//pool-1-thread-4 取款:100 余额:600
//pool-1-thread-10 遇到并发,再次尝试取款!
//pool-1-thread-2 遇到并发,再次尝试取款!
//pool-1-thread-6 遇到并发,再次尝试取款!
//pool-1-thread-3 遇到并发,再次尝试取款!
//pool-1-thread-3 取款:100 余额:500
//pool-1-thread-9 遇到并发,再次尝试取款!
//pool-1-thread-10 遇到并发,再次尝试取款!
//pool-1-thread-9 取款:100 余额:400
//pool-1-thread-11 遇到并发,再次尝试取款!
//pool-1-thread-2 遇到并发,再次尝试取款!
//pool-1-thread-11 取款:100 余额:300
//pool-1-thread-1 遇到并发,再次尝试取款!
//pool-1-thread-12 遇到并发,再次尝试取款!
//pool-1-thread-7 遇到并发,再次尝试取款!
//pool-1-thread-2 遇到并发,再次尝试取款!
//pool-1-thread-7 取款:100 余额:200
//pool-1-thread-2 遇到并发,再次尝试取款!
//pool-1-thread-6 遇到并发,再次尝试取款!
//pool-1-thread-10 遇到并发,再次尝试取款!
//pool-1-thread-1 遇到并发,再次尝试取款!
//pool-1-thread-12 遇到并发,再次尝试取款!
//pool-1-thread-2 取款:100 余额:100
//pool-1-thread-12 遇到并发,再次尝试取款!
//pool-1-thread-12 取款:100 余额:0
//pool-1-thread-6 遇到并发,再次尝试取款!
//pool-1-thread-6 余额不足! 余额:0
//pool-1-thread-10 遇到并发,再次尝试取款!
//pool-1-thread-10 余额不足! 余额:0
//pool-1-thread-1 遇到并发,再次尝试取款!
//pool-1-thread-1 余额不足! 余额:0
}

最新文章

  1. readelf与动态库
  2. 快手 KSCAD 5.0 矢量图形设计软件
  3. JSP简单标签开发
  4. 从原理上理解NodeJS的适用场景
  5. winform c#绑定combobox下拉框 年度代码。
  6. ArcGIS制图之Sub Points点抽稀
  7. Paragraph Vector在Gensim和Tensorflow上的编写以及应用
  8. System.Web.Optimization对脚本和样式表的压缩操作
  9. Introduction to boundary integral equations in BEM
  10. Centos7 通过yum命令安装jdk1.8
  11. [Sqoop]将Hive数据表导出到Mysql
  12. mui-webview-子页面调用父页面的js方法
  13. poj 2449 Remmarguts&#39; Date(K短路,A*算法)
  14. js &amp; listen mouse click
  15. swift - 之TabBarController的用法
  16. string学习
  17. 面向对象的JavaScript --- 原型模式和基于原型继承的JavaScript对象系统
  18. FROM_UNIXTIME(unix_timestamp), FROM_UNIXTIME(unix_timestamp,format)
  19. 64_l4
  20. 从零开始--Spring项目整合(1)使用maven框架搭建项目

热门文章

  1. IDEA设置Ctrl+滚轮调整字体大小(转载)
  2. CSS页面乱码 GB2312、UTF-8格式问题解决方案
  3. HDU4089 Activation(概率DP+处理环迭代式子)
  4. loj#2838 「JOISC 2018 Day 3」比太郎的聚会
  5. 【Visual Studio】 使用EF、 Linq2Sql快速创建数据交互层(一)
  6. Wildfly安装以及集成idea(mac)
  7. hdu6228Tree
  8. 【小刘的linux学习笔记 】——01认识操作系统
  9. 关于Object.create()与原型链的面试题?
  10. SpringBoot 接口并行高效聚合