zoukankan      html  css  js  c++  java
  • Java高并发-无锁

    一、无锁类的原理

    1.1 CAS

    CAS算法的过程是这样:它包含3个参数CAS(V,E,N)。V表示要更新的变量,E表示预期值,N表示新值。仅当V值等于E值时,才会将V的值设为N,如果V值和E值不同,则说明已经有其他线程做了更新,则当前线程什么都不做。最后,CAS返回当前V的真实值 。CAS操作是抱着乐观的态度进行的,它总是认为自己可以成功完成操作。当多个线程同时使用CAS操作一个变量时,只有一个会胜出,并成功更新,其余均会失败。失败的线程不会被挂起,仅是被告知失败,并且允许再次尝试,当然也允许失败的线程放弃操作。基于这样的原理,CAS操作即使没有锁,也可以发现其他线程对当前线程的干扰,并进行恰当的处理。

    1.2 CPU指令

    二、无锁类的使用

    2.1 AtomicInteger

    概述

    java.util.concurrent.atomic
    public class AtomicInteger extends Number implements java.io.Serializable
    

    主要接口

    public final int get() // 获取当前值
    public final void set(int newValue) // 设置当前值
    public final int getAndSet(int newValue) // 设置新值,并返回旧值
    public final boolean compareAndSet(int expect, int u) // 如果当前值为expect,则设置为u
    public final int getAndIncrement() // 当前值加1,返回旧值,类似于i++
    public final int getAndDecrement() // 当前值减1,返回旧值,类似于i--
    public final int getAndAdd(int delta) // 当前值增加delta,返回旧值
    public final int incrementAndGet() // 当前值加1,返回新值,类似于++i
    public final int decrementAndGet() // 当前值减1,返回新值,类似于--i
    public final int addAndGet(int delta) // 当前值增加delta,返回新值
    

    主要接口的实现

    // 内部定义了一个value,AtomicInteger只是对它的封装
    private volatile int value;
    

    compareAndSet

    valueOffset是偏移量
    

    举例

    2.2 Unsafe

    概述

    非安全的操作,比如:根据偏移量设置值
    park()  把线程停下来
    底层的CAS操作
    非公开API,在不同版本的JDK中,可能有较大差异
    

    主要接口

    // 获得给定对象偏移量上的int值
    public native int getInt(java.lang.Object arg0, long arg1);
    // 设置给定对象像偏移量上的int值
    public native void putInt(java.lang.Object arg0, long arg1, int arg2);
    // 获得字段在对象中的偏移量
    public native long objectFieldOffset(Field f);
    // 设置给定对象的int值,使用volatile语义
    public native void putIntVolatile(Object o, long offset, int x);
    // 获得给定对象的int值,使用volatile语义
    public native void getIntVolatile(Object o, long offset);
    // 和putIntVolatile()一样,但是它要求被操作字段就是volatile类型的
    public native void putOrderedInt(Object o, long offset, int x);
    

    2.3 AtomicReference

    概述

    对引用进行修改
    是一个模板类,抽象化了数据类型
    

    主要接口

    get()
    set(V)
    compareAndSet()
    getAndSet(V)
    

    举例

    2.4 AtomicStampedReference

    概述

    解决ABA问题
    一个变量初始值为A
    线程1读取变量            00:00
    线程2读取变量            00:03
    线程2将变量修改为B       00:05
    线程3读取变量            00:06
    线程3将变量修改为A       00:08
    线程1根据变量做计算       00:10
    线程1将变量修改为C       00:12  定稿前变量值是A,其实该变量已经经历了ABA的变化 
    

    主要接口

    // 比较设置 参数依次为:期望值 写入新值 期望时间戳 新时间戳
    public boolean compareAndSet(V   expectedReference,
                                 V   newReference,
                                 int expectedStamp,
                                 int newStamp)
    // 获得当前对象引用
    public V getReference()
    // 获得当前时间戳
    public int getStamp()
    // 设置当前对象引用和时间戳
    public void set(V newReference, int newStamp)
    

    举例

    public class AtomicStampedReferenceDemo {
    	static AtomicStampedReference<Integer> money = new AtomicStampedReference<Integer>(19, 0);
    
    	public static void main(String[] args) {
    		// 模拟3个充钱线程,余额小余20时充钱且只充一次
    		for (int i = 0; i < 3; i++) {
    			final int timestamp = money.getStamp();
    			new Thread() {
    				public void run() {
    					while (true) {
    						Integer m = money.getReference();
    						if (m < 20) {
    							if (money.compareAndSet(m, m + 20, timestamp, timestamp + 1)) {
    								System.out.println("余额小于20元,充值成功,余额:" + money.getReference());
    							}
    						} else {
    							// 余额大于20,无需充值
    							break;
    						}
    					}
    				}
    			}.start();
    		}
    		// 模拟一个消费线程
    		new Thread() {
    			public void run() {
    				// 消费10次,余额大于10元时才能消费
    				for (int i = 0; i < 10; i++) {
    					while (true) {
    						int timestamp = money.getStamp();
    						Integer m = money.getReference();
    						if (m > 10) {
    							if (money.compareAndSet(m, m - 10, timestamp, timestamp + 1)) {
    								System.out.println("成功消费10元,余额:" + money.getReference());
    								break;
    							}
    						} else {
    							System.out.println("没有足够的余额");
    							break;
    						}
    					}
    					try {
    						Thread.sleep(100);
    					} catch (InterruptedException e) {
    					}
    				}
    			}
    		}.start();
    	}
    }
    

    2.5 AtomicIntegerArray

    概述

    支持无锁的数组
    

    主要接口

    // 获得数组第i个下标的元素
    public final int get(int i)
    // 获得数组的长度
    public final int length()
    // 将数组第i个下标设置为newValue,并返回旧的值
    public final int getAndSet(int i, int newValue)
    // 进行CAS操作,如果第i个下标的元素等于expect,则设置为update,设置成功返回true
    public final boolean compareAndSet(int i, int expect, int update)
    // 将第i个下标的元素加1
    public final int getAndIncrement(int i)
    // 将第i个下标的元素减1
    public final int getAndDecrement(int i)
    // 将第i个下标的元素增加delta(delta可以是负数)
    public final int getAndAdd(int i, int delta)
    

    举例

    2.6 AtomicIntegerFieldUpdater

    概述

    让普通变量也享受原子操作
    

    主要接口

    // 工厂方法
    AtomicIntegerFieldUpdater.newUpdater()
    incrementAndGet()
    

    说明

    1. Updater只能修改它可见范围内的变量。因为Updater使用反射得到这个变量。如果变量不可见,就会出错。比如如果score声明为private,就是不可行的。
    2. 为了确保变量被正确读取,它必须是volatile类型的。如果我们原有代码中未声明这个类型,那么简单声明一个就行,这不会引起什么问题。
    3. 由于CAS操作会通过对象实例中的偏移量直接进行赋值,因此它不支持static字段(Unsafe.objectFieldOffset()不支持静态变量)
    

    举例

    三、无锁算法

    3.1 Vector实现

    add方法

    说明

    数组实现
    modCount++ 记录被修改的次数
    ensureCapacityHelper(elementCount+1) 做容量检查
    

    扩容

    3.2 无锁的Vector实现

  • 相关阅读:
    cat > file << EOF 的用法
    jemter参数化是如何取值的?(数据分配)
    使用elasticdump迁移es数据
    阿里云SLB的http强制转https
    Centos7主机安装Cockpit管理其他主机
    CentOS 7配置Chrony服务进行时间同步
    Nginx负载均衡设置max_fails和fail_timeout
    openresty(nginx) 配置 stream 转发
    rabbitmq的内存节点和磁盘节点
    nginx实现vue的web页面项目集群负载
  • 原文地址:https://www.cnblogs.com/okokabcd/p/8730427.html
Copyright © 2011-2022 走看看