一、重入锁

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

  • ReentrantLock

public class TL005_Reentrant extends Thread {
ReentrantLock lock = new ReentrantLock(); public void get() {
lock.lock();
System.out.println(Thread.currentThread().getId());
set();
lock.unlock();
} public void set() {
lock.lock();
System.out.println(Thread.currentThread().getId());
lock.unlock();
} @Override
public void run() {
get();
} public static void main(String[] args) {
TL005_Reentrant ss = new TL005_Reentrant();
new Thread(ss).start();
new Thread(ss).start();
new Thread(ss).start();
} }
  • synchronized
public class Test_Reentrant implements Runnable {
public synchronized void get() {
System.out.println("name:" + Thread.currentThread().getName() + " get();");
set();
} public synchronized void set() {
System.out.println("name:" + Thread.currentThread().getName() + " set();");
} @Override public void run() {
get();
} public static void main(String[] args) {
Test_Reentrant ss = new Test_Reentrant();
new Thread(ss).start();
new Thread(ss).start();
new Thread(ss).start();
new Thread(ss).start();
}
}

二、读写锁

  • 相比Java中的锁Lock实现,读写锁更复杂一些。假设你的程序中涉及到对一些共享资源的读和写操作,且写操作没有读操作那么频繁。在没有写操作的时候,两个线程同时读一个资源没有任何问题,所以应该允许多个线程能在同时读取共享资源。但是如果有一个线程想去写这些共享资源,就不应该再有其它线程对该资源进行读或写(也就是说:读-读能共存,读-写不能共存,写-写不能共存)。这就需要一个读/写锁来解决这个问题。Java5在java.util.concurrent包中已经包含了读写锁。

  • 示例如下:

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock; public class Cache {
static Map<String, Object> map = new HashMap<String, Object>();
static ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
static Lock r = rwl.readLock();
static Lock w = rwl.writeLock(); // 获取一个key对应的value
public static final Object get(String key) {
r.lock();
try {
System.out.println("正在做读的操作,key:" + key + " 开始");
Thread.sleep(100);
Object object = map.get(key);
System.out.println("正在做读的操作,key:" + key + " 结束");
System.out.println();
return object;
} catch (InterruptedException e) { } finally {
r.unlock();
}
return key;
} // 设置key对应的value,并返回旧有的value
public static final Object put(String key, Object value) {
w.lock();
try { System.out.println("正在做写的操作,key:" + key + ",value:" + value + "开始.");
Thread.sleep(100);
Object object = map.put(key, value);
System.out.println("正在做写的操作,key:" + key + ",value:" + value + "结束.");
System.out.println();
return object;
} catch (InterruptedException e) { } finally {
w.unlock();
}
return value;
} // 清空所有的内容
public static final void clear() {
w.lock();
try {
map.clear();
} finally {
w.unlock();
}
} public static void main(String[] args) {
new Thread(new Runnable() { @Override
public void run() {
for (int i = 0; i < 10; i++) {
Cache.put(i + "", i + "");
} }
}).start();
new Thread(new Runnable() { @Override
public void run() {
for (int i = 0; i < 10; i++) {
Cache.get(i + "");
} }
}).start();
}
}

三、悲观锁、乐观锁

3.1 悲观锁

  • 总是假设最坏的情况,每次取数据时都认为其他线程会修改,所以都会加锁(读锁、写锁、行锁等),当其他线程想要访问数据时,都需要阻塞挂起。可以依靠数据库实现,如行锁、读锁和写锁等,都是在操作之前加锁,在Java中,synchronized的思想也是悲观锁。

3.2 乐观锁

  • 总是认为不会产生并发问题,每次去取数据的时候总认为不会有其他线程对数据进行修改,因此不会上锁,但是在更新时会判断其他线程在这之前有没有对数据进行修改,一般会使用版本号机制或CAS操作实现。
  • version方式:一般是在数据表中加上一个数据版本号version字段,表示数据被修改的次数,当数据被修改时,version值会加一。当线程A要更新数据值时,在读取数据的同时也会读取version值,在提交更新时,若刚才读取到的version值为当前数据库中的version值相等时才更新,否则重试更新操作,直到更新成功。
  • 核心SQL语句
update table set x=x+1, version=version+1 where id=#{id} and version=#{version};

3.3 CAS操作方式

  • CAS:Compare and Swap,即比较再交换。
  • 即compare and swap 或者 compare and set,涉及到三个操作数,数据所在的内存值,预期值,新值。当需要更新时,判断当前内存值与之前取到的值是否相等,若相等,则用新值更新,若失败则重试,一般情况下是一个自旋操作,即不断的重试。
  • jdk5增加了并发包java.util.concurrent.*,其下面的类使用CAS算法实现了区别于synchronouse同步锁的一种乐观锁。JDK 5之前Java语言是靠synchronized关键字保证同步的,这是一种独占锁,也是是悲观锁。

3.4 CAS算法理解

  • (1)与锁相比,使用CAS会使程序看起来更加复杂一些。但由于其非阻塞性,它对死锁问题天生免疫,并且,线程间的相互影响也远远比基于锁的方式要小。更为重要的是,使用无锁的方式完全没有锁竞争带来的系统开销,也没有线程间频繁调度带来的开销,因此,它要比基于锁的方式拥有更优越的性能。
  • (2)无锁的好处:
    • 第一,在高并发的情况下,它比有锁的程序拥有更好的性能;
    • 第二,它天生就是死锁免疫的。
    • 就凭借这两个优势,就值得我们冒险尝试使用无锁的并发。
  • (3)CAS算法的过程是这样:它包含三个参数CAS(V,E,N): V表示要更新的变量,E表示预期值,N表示新值。仅当V值等于E值时,才会将V的值设为N,如果V值和E值不同,则说明已经有其他线程做了更新,则当前线程什么都不做。最后,CAS返回当前V的真实值。
  • (4)CAS操作是抱着乐观的态度进行的,它总是认为自己可以成功完成操作。当多个线程同时使用CAS操作一个变量时,只有一个会胜出,并成功更新,其余均会失败。失败的线程不会被挂起,仅是被告知失败,并且允许再次尝试,当然也允许失败的线程放弃操作。基于这样的原理,CAS操作即使没有锁,也可以发现其他线程对当前线程的干扰,并进行恰当的处理。
  • (5)简单地说,CAS需要你额外给出一个期望值,也就是你认为这个变量现在应该是什么样子的。如果变量不是你想象的那样,那说明它已经被别人修改过了。你就重新读取,再次尝试修改就好了。
  • (6)在硬件层面,大部分的现代处理器都已经支持原子化的CAS指令。在JDK 5.0以后,虚拟机便可以使用这个指令来实现并发操作和并发数据结构,并且,这种操作在虚拟机中可以说是无处不在。

3.5 CAS(乐观锁算法)

  • CAS比较与交换的伪代码可以表示为:
do{
备份旧数据;
基于旧数据构造新数据;
}while(!CAS( 内存地址,备份的旧数据,新数据 ))
  • CPU去更新一个值,但如果想改的值不再是原来的值,操作就失败,因为很明显,有其它操作先改变了这个值。
  • 就是指当两者进行比较时,如果相等,则证明共享数据没有被修改,替换成新值,然后继续往下运行;如果不相等,说明共享数据已经被修改,放弃已经所做的操作,然后重新执行刚才的操作。容易看出 CAS 操作是基于共享数据不会被修改的假设,采用了类似于数据库的 commit-retry 的模式。当同步冲突出现的机会很少时,这种假设能带来较大的性能提升。
public final int getAndAddInt(Object o, long offset, int delta){
int v;
do {
v = getIntVolatile(o, offset);
} while (!compareAndSwapInt(o, offset, v, v + delta));
return v;
} /**
* Atomically increments by one the current value.
*
* @return the updated value
*/
public final int incrementAndGet() {
for (;;) {
//获取当前值
int current = get();
//设置期望值
int next = current + 1;
//调用Native方法compareAndSet,执行CAS操作
if (compareAndSet(current, next))
//成功后才会返回期望值,否则无线循环
return next;
}
}

3.6 CAS缺点

  • CAS存在一个很明显的问题,即ABA问题。
  • 问题:如果变量V初次读取的时候是A,并且在准备赋值的时候检查到它仍然是A,那能说明它的值没有被其他线程修改过了吗?
  • 如果在这段期间曾经被改成B,然后又改回A,那CAS操作就会误认为它从来没有被修改过。针对这种情况,java并发包中提供了一个带有标记的原子引用类AtomicStampedReference,它可以通过控制变量值的版本来保证CAS的正确性。

四、原子类

4.1 概述

  • 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关键字保证同步的,这是一种独占锁,也是是悲观锁。

  • 如果同一个变量要被多个线程访问,则可以使用该包中的类

4.2 常用原子类

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

  • 1、基本类型,使用原子的方式更新基本类型

    • AtomicInteger:整型原子类
    • AtomicLong:长整型原子类
    • AtomicBoolean :布尔型原子类
  • 2、数组类型,使用原子的方式更新数组里的某个元素

    • AtomicIntegerArray:整型数组原子类
    • AtomicLongArray:长整型数组原子类
    • AtomicReferenceArray :引用类型数组原子类
  • 3、引用类型

    • AtomicReference:引用类型原子类
    • AtomicReferenceFieldUpdater:原子更新引用类型里的字段
    • AtomicMarkableReference :原子更新带有标记位的引用类型
  • 4、对象的属性修改类型

    • AtomicIntegerFieldUpdater:原子更新整型字段的更新器
    • AtomicLongFieldUpdater:原子更新长整型字段的更新器
    • AtomicStampedReference :原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于解决原子的更新数据和数据的版本号,可以解决使用 CAS 进行原子更新时可能出现的 ABA 问题。
    • AtomicMarkableReference:原子更新带有标记的引用类型。该类将 boolean 标记与引用关联起来,也可以解决使用 CAS 进行原子更新时可能出现的 ABA 问题。
  • 以AtomicInteger为例:

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

五、分布式锁

  • 如果想在不同的jvm中保证数据同步,使用分布式锁技术。
  • 有数据库实现、缓存实现、Zookeeper分布式锁

最新文章

  1. hdu 1873 看病要排队(优先级队列)
  2. 移动端HTML5开发心得(转)
  3. linux卸载openJDK并安装sun jdk
  4. Knockout 新版应用开发教程之Observable Arrays
  5. sql数据库(资料库)的基本操作
  6. How to Avoid OOM in Android
  7. 硝烟中的scrum学习笔记 - 怎样制定Sprint计划(Plan Meeting)
  8. Mac或Linux中对Android抓包
  9. 【HDOJ】3006 The Number of set
  10. (转)jquery.validator规则
  11. 极度简约 最小 Linux 发行版 Tiny Core Linux 7.1 发布
  12. java.toString() ,(String),String.valueOf的区别
  13. Android自定义控件系列之基础篇
  14. amd屏幕亮度无法调整,无法调节亮度
  15. 『高性能模型』轻量级网络ShuffleNet_v1及v2
  16. [Leetcode 217&amp;219]寻找数组中的重复值Contains Duplicate I &amp; II
  17. aspx页面,取得Excel某列不同类型的数据为空
  18. Python3学习笔记06-字符串
  19. 前后端分离(手) -- mock.js
  20. VMware Workstation Pro12安装RedHat6.4 64位

热门文章

  1. jmeter 调用python的方法三种 (还没试)
  2. python3 @classmethod 和 @staticmethod 的区别
  3. 关于运维之故障复盘篇-Case Study
  4. CockroachDB学习笔记——[译]在CockroachDB中如何让在线模式更改成为可能
  5. EasyNetQ使用(四)【Request与Response,Send与Receive】
  6. 提示/bin/roslyn/csc.exe权限不足
  7. python、java、ruby、node等如何提取office文档中的内容?
  8. JS遍历数组,实现数组去重(重复的元素只保留一个)
  9. 信用卡分销系统源码、分销系统定制贴牌、信用卡三级分销系统源码、信用卡返佣系统OEM
  10. vue项目富文本编辑器vue-quill-editor之自定义图片上传