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
}

25.Java锁的深度化的更多相关文章

  1. Java锁的深度化--重入锁、读写锁、乐观锁、悲观锁

    Java锁 锁一般来说用作资源控制,限制资源访问,防止在并发环境下造成数据错误 锁作为并发共享数据,保证一致性的工具,在JAVA平台有多种实现(如 synchronized(重量级) 和 Reentr ...

  2. 【学习】005 线程池原理分析&锁的深度化

    线程池 什么是线程池 Java中的线程池是运用场景最多的并发框架,几乎所有需要异步或并发执行任务的程序 都可以使用线程池.在开发过程中,合理地使用线程池能够带来3个好处. 第一:降低资源消耗.通过重复 ...

  3. java并发包&线程池原理分析&锁的深度化

          java并发包&线程池原理分析&锁的深度化 并发包 同步容器类 Vector与ArrayList区别 1.ArrayList是最常用的List实现类,内部是通过数组实现的, ...

  4. JAVA多线程(三) 线程池和锁的深度化

    github演示代码地址:https://github.com/showkawa/springBoot_2017/tree/master/spb-demo/spb-brian-query-servic ...

  5. JAVA CAS原理深度分析 volatile,偏向锁,轻量级锁

    JAVA CAS原理深度分析 http://blog.csdn.net/hsuxu/article/details/9467651 偏向锁,轻量级锁 https://blog.csdn.net/zqz ...

  6. Java锁-Synchronized深层剖析

    Java锁-Synchronized深层剖析 前言 Java锁的问题,可以说是每个JavaCoder绕不开的一道坎.如果只是粗浅地了解Synchronized等锁的简单应用,那么就没什么谈的了,也不建 ...

  7. 如何用70行Java代码实现深度神经网络算法

    http://www.tuicool.com/articles/MfYjQfV 如何用70行Java代码实现深度神经网络算法 时间 2016-02-18 10:46:17  ITeye 原文  htt ...

  8. JAVA 锁之 Synchronied

    ■ Java 锁 1. 锁的内存语义 锁可以让临界区互斥执行,还可以让释放锁的线程向同一个锁的线程发送消息 锁的释放要遵循 Happens-before 原则(锁规则:解锁必然发生在随后的加锁之前) ...

  9. java锁的种类以及辨析(转载)

    java锁的种类以及辨析(一):自旋锁 锁作为并发共享数据,保证一致性的工具,在JAVA平台有多种实现(如 synchronized 和 ReentrantLock等等 ) .这些已经写好提供的锁为我 ...

随机推荐

  1. prim 模板

    #include<cstdio> #include<vector> #include<cstring> #include<set> #define ma ...

  2. Latex数学公式中的矩阵

    目录 矩阵的括号形式 array环境 上三角矩阵 分块矩阵 行内矩阵 矩阵的括号形式 使用matrix.pmatrix.bmatrix.Bmatrix.vmatrix或者Vmatrix环境: $$ \ ...

  3. docker 部署ftp

    1.搜索ftp镜像 docker search vsftpd 2.拉取ftp镜像 docker pull fauria/vsftpd 3.启动ftpdocker docker run -d -v /h ...

  4. java 集合框架 List相关接口

    AbstractCollection 此类提供 Collection 接口的骨干实现,以最大限度地减少了实现此接口所需的工作. 还有两个抽象方法,具体的迭代器,具体的Collection 的大小 pu ...

  5. UML 类图快速入门

    UML 图形 官方定义 UML 类图(Class Diagram) UML 时序图(Sequence Diagram) 领域 UML 类图和实现 UML 类图 领域 UML 类图 实现 UML 类图 ...

  6. org.dom4j 解析XML

    org.dom4j 解析xml java 代码 1 import java.io.File; import java.io.FileOutputStream; import java.io.FileW ...

  7. JDBC常用接口、类介绍

    JDBC常用接口.类介绍 JDBC提供对独立于数据库统一的API,用以执行SQL命令.API常用的类.接口如下: DriverManager 管理JDBC驱动的服务类,主要通过它获取Connectio ...

  8. Win7崩溃程序目录

    很烦,占用系统空间,毫无用处 C:\Users\你的用户名\AppData\Local\CrashDumps C:\Users\你的用户名\AppData\Local\Microsoft\Window ...

  9. tensorflow学习框架(炼数成金网络版学习记录)

    chapter1 #变量 import tensorflow as tf x = tf.Variable([1,2]) a = tf.constant([3,3]) #增加一个减法op sub = t ...

  10. JavaScript LoopQueue

    function Queue() { var items = []; this.enqueue = function(element) { items.push(element) } this.deq ...