欢迎来真孝善网,为您提供真孝善正能量书籍故事!

深入解析Java多线程编程技巧与策略

时间:11-20 神话故事 提交错误

各位老铁们好,相信很多人对深入解析Java多线程编程技巧与策略都不是特别的了解,因此呢,今天就来为大家分享下关于深入解析Java多线程编程技巧与策略以及的问题知识,还望可以帮助大家,解决大家的一些困惑,下面一起来看看吧!

无锁执行器-CAS

中科院

CAS的全称是Compare And Swap,意思是比较和交换。其算法核心思想如下

执行函数:CAS(V,E,N)

它包含3 个参数。 V代表要更新的变量。 E代表期望值。 N 代表新值。如果V值等于E值,则将V的值设置为N。如果V值和E值不同,则意味着其他线程已经进行了更新,当前线程不执行任何操作。通俗的理解是CAS操作需要我们提供一个期望值。当期望值与当前线程的变量值相同时,表示没有线程修改该值。当前线程可以修改它,即执行CAS操作,但如果期望值与当前线程不匹配,则说明该值已被其他线程修改。此时不执行更新操作,但可以选择重新读取变量并尝试再次修改变量,也可以放弃操作。原理图如下

由于CAS操作是乐观主义者,它总是认为自己能够成功完成操作。当多个线程同时使用CAS操作一个变量时,只有一个会获胜并更新成功,其余的都会失败,但失败的线程不会被Suspend,只会被告知失败并允许重试。当然,失败的线程也是允许放弃操作的,这一点从图中也可以看出。基于这个原理,即使CAS操作没有锁,它仍然知道其他线程对共享资源操作的影响,并执行相应的处理措施。同时,从这一点也可以看出,由于无锁操作没有锁,所以不可能出现死锁,这意味着无锁操作本质上是免疫死锁的。

CPU对CAS指令的支持

也许我们会有这样的疑问。假设有多个线程进行CAS操作,并且CAS的步骤很多,是否有可能判断出V和E相同后,在即将赋值的时候,线程切换,值发生了改变。数据不一致的原因是什么?答案是否定的,因为CAS 是一个系统原语。原语属于操作系统术语的范畴。它由多条指令组成,用于完成某种功能的一个过程,原语的执行必须是连续的。执行过程中不允许中断,这意味着CAS是CPU的原子指令,不会造成所谓的数据不一致问题。

鲜为人知的指针: 不安全类

Unsafe 类存在于sun.misc 包中。它的内部方法操作可以像C指针一样直接操作内存。单从名字就可以知道这个类是不安全的。毕竟Unsafe具有与C类似的指针操作,因此Unsafe类始终不应该首先使用。 Java官方不建议直接使用Unsafe类。据说Oracle正计划从Java 9中删除Unsafe类,但是我们还是有必要了解这个类,因为Java中CAS操作的执行依赖于它。关于Unsafe类的方法,需要注意的是Unsafe类中的所有方法都是native修饰的,也就是说Unsafe类中的方法直接调用操作系统的底层资源来执行相应的任务。 Unsafe类的主要功能点如下:

内存管理,Unsafe类中有直接操作内存的方法

//分配指定大小的内存

公共本机长分配内存(长字节);

//根据给定的内存地址地址设置重新分配指定大小的内存

公共本机长重新分配内存(长地址,长字节);

//用于释放allocateMemory和reallocateMemory申请的内存

public native void freeMemory(长地址);

//将指定对象给定偏移处的内存块中的所有字节设置为固定值

public native void setMemory(Object o, 长偏移量, 长字节, 字节值);

//设置给定内存地址的值

公共本机无效putAddress(长地址,长x);

//获取指定内存地址的值

公共本机长getAddress(长地址);

//设置给定内存地址的long值

公共本机无效putLong(长地址,长x);

//获取指定内存地址的long值

公共本机长getLong(长地址);

//设置或获取指定内存的字节值

公共本机字节getByte(长地址);

公共本机无效putByte(长地址,字节x);

//其他基本数据类型(long、char、float、double、short等)的操作与putByte、getByte相同

//操作系统的内存页大小

公共本机int pageSize();

提供对实例对象的新访问。

//传入一个对象的类并创建实例对象,但构造函数不会被调用。公共本机对象allocateInstance(Class cls) 抛出InstantiationException;

对类和实例对象和变量的操作,主要方法如下

//获取实例对象中字段f的偏移量

公共本机长objectFieldOffset(字段f);

//静态属性的偏移量,用于读写对应Class对象中的静态属性

公共本机长静态FieldOffset(字段f);

//返回值为f.getDeclaringClass()

公共本机对象staticFieldBase(Field f);

//获取给定对象偏移处的int值。所谓偏移量可以简单理解为指向变量的指针的内存地址。

//可以通过偏移量获取对象的变量并进行各种操作

公共本机int getInt(Object o, long offset);

//设置给定对象上偏移量的int值

公共本机无效putInt(对象o,长偏移量,int x);

//获取给定对象偏移处引用类型的值

公共本机对象getObject(Object o, long offset);

//在给定的对象偏移处设置引用类型的值

公共本机无效putObject(对象o,长偏移量,对象x);

//其他基本数据类型(long、char、byte、float、double)的操作与getInthe和putInt相同

//使用易失性语义设置给定对象的int值,即设置后立即更新到内存,并且对其他线程可见。

公共本机无效putIntVolatile(Object o, long offset, int x);

//获取给定对象的指定偏移量offset的int值。使用易失性语义,您始终可以获得最新的int 值。

公共本机int getIntVolatile(Object o, long offset);

//其他基本数据类型(long、char、byte、float、double)的操作与putIntVolatile、getIntVolatile相同,引用类型putObjectVolatile也是如此。

//与putIntVolatile相同,但要求操作的字段必须是volatile修饰的

公共本机无效putOrderedInt(对象o,长偏移量,int x);

下面我们用一个简单的Demo来演示上面的一些方法,加深我们对Unsafe类的理解。

公共类UnSafeDemo {

公共静态无效主(字符串[] args)抛出NoSuchFieldException,IllegalAccessException,InstantiationException {

//通过反射获取theUnsafe对应的Field对象Field field=Unsafe.class.getDeclaredField("theUnsafe");

//设置Field可访问field.setAccessible(true);

//通过Field获取Field对应的具体对象。传入null,因为Field是静态的Unsafe unsafe=(Unsafe) field.get(null);

System.out.println(不安全);

//直接通过allocateInstance创建对象User user=(User) unsafe.allocateInstance(User.class);

类userClass=user.getClass();

字段名称=userClass.getDeclaredField("name");

字段年龄=userClass.getDeclaredField("age");

字段id=userClass.getDeclaredField("id");

//获取实例变量name和age在对象内存中的偏移量并设置值unsafe.putInt(user,unsafe.objectFieldOffset(age),18);

unsafe.putObject(用户,unsafe.objectFieldOffset(name),"android TV");

//这里返回User.class,Object staticBase=unsafe.staticFieldBase(id);

System.out.println("staticBase:"+staticBase);

//获取静态变量id的偏移量staticOffset long staticOffset=unsafe.staticFieldOffset(userClass.getDeclaredField("id"));

//获取静态变量的值System.out.println("设置前的ID:"+unsafe.getObject(staticBase,staticOffset));

//设置值unsafe.putObject(staticBase,staticOffset,"SSSSSSSS");

//获取静态变量的值System.out.println("设置前的ID:"+unsafe.getObject(staticBase,staticOffset));

//输出USER System.out.println("输出USER:"+user.toString());

长数据=1000;

字节大小=1; //单位字节//调用allocateMemory分配内存并获取内存地址memoryAddress long memoryAddress=unsafe.allocateMemory(size);

//直接将数据写入内存unsafe.putAddress(memoryAddress, data);

//获取指定内存地址处的数据long addrData=unsafe.getAddress(memoryAddress);

System.out.println("addrData:"+addrData);

/**

* 输出结果:

sun.misc.Unsafe@6f94fa3e

staticBase:class geym.conc.ch4.atomic.User

设置前的ID:USER_ID

ID:SSSSSSSS 设置前

输出USER:User{name="android TV",age=18",id=SSSSSSSS"}

地址数据:1000

*/}

}

类用户{

公共用户(){

System.out.println("调用用户构造函数");

}

私有字符串名称;

私有整数年龄;

私有静态字符串id="USER_ID";

@覆盖

公共字符串toString() {

返回“用户{”+

"名称="" + 名称+ """ +

", 年龄=" + 年龄+""" +

", id=" + id +""" +

"}";

}

}

虽然Unsafe类中有一个getUnsafe()方法,但是这个方法只有高级的Bootstrap类加载器才可以使用,普通用户调用时会抛出异常。因此,我们在Demo中使用了反射技术来获取Unsafe实例对象并关联起来。操作。

公共静态不安全getUnsafe() {

类cc=sun.reflect.Reflection.getCallerClass(2);

if (cc.getClassLoader() !=null)

抛出新的SecurityException("不安全");

返回不安全;

}

数组运算

//获取数组第一个元素的偏移地址public native int arrayBaseOffset(Class arrayClass); //数组中某个元素占用的内存空间。 ArrayBaseOffset与arrayIndexScale配合使用来定位数组中每个元素在内存中的位置。公共本机int arrayIndexScale(Class arrayClass);

CAS操作相关

CAS是一些CPU直接支持的指令,也就是我们前面分析的无锁操作。在Java中,无锁操作CAS是基于以下三种方法实现的。后面我会解释Atomic系列的内部方法是基于以下方法实现的。

//第一个参数o是给定的对象,offset是对象内存的偏移量。使用此偏移量可以快速定位字段并设置或获取字段的值。 //expected表示期望值,x表示要设置的值。以下三个方法都是通过CAS原子指令进行操作。公共最终本机布尔比较AndSwapObject(对象o,长偏移量,预期对象,对象x);公共最终本机布尔比较CompareAndSwapInt(对象o,长偏移量,int预期,int x);公共最终本机布尔比较CompareAndSwapLong(对象o,长偏移量,长预期,长x);

这里我们还需要在Unsafe类中引入JDK 1.8中的几个新方法。他们的实现都是基于上面的CAS方法,如下

//1.8中新增,给定对象o,根据内存偏移指向的字段将其增加delta。 //这是一个CAS操作过程。直到设置成功并返回旧值后才能退出循环。 public Final int getAndAddInt (Object o, long offset, int delta) {

整数v;

做{

//获取内存中的最新值v=getIntVolatile(o, offset);

//通过CAS进行操作} while (!compareAndSwapInt(o, offset, v, v + delta));

返回v;

}//1.8新增,方法作用与上面相同,只不过这里操作的是long类型数据public final long getAndAddLong(Object o, long offset, long delta) {

长v;

做{

v=getLongVolatile(o, 偏移量);

while (!compareAndSwapLong(o, offset, v, v + delta));

返回v;

}

//1.8中新增,给定对象o,根据字段获取的内存偏移量将其设置为新值newValue。 //这是一个CAS操作过程。直到设置成功并返回旧值后才能退出循环。公共最终int getAndSetInt(Object o, long offset, int newValue) {

整数v;

做{

v=getIntVolatile(o, 偏移量);

while (!compareAndSwapInt(o, offset, v, newValue));

返回v;

}//1.8新增,同上,操作为long类型public final long getAndSetLong(Object o, long offset, long newValue) {

长v;

做{

v=getLongVolatile(o, 偏移量);

while (!compareAndSwapLong(o, offset, v, newValue));

返回v;

}

//1.8新增,同上,操作引用类型数据public Final Object getAndSetObject(Object o, long offset, Object newValue) {

对象v;

做{

v=getObjectVolatile(o, 偏移量);

while (!compareAndSwapObject(o, offset, v, newValue));

返回v;

}

我们会在后面对Atomic系列的分析中看到上面的方法。

暂停和恢复

挂起线程是通过park方法实现的。调用park后,线程将阻塞,直到发生超时或中断等情况。 unpark 可以终止挂起的线程并使其恢复正常。 Java的线程挂起操作被封装在LockSupport类中。 LockSupport 类中有多种版本的pack 方法。其底层实现最终使用了Unsafe.park()方法和Unsafe.unpark()方法。

//线程调用该方法,线程将阻塞,直到超时或中断条件发生。 public native void park(boolean isAbsolute, long time); //终止挂起的线程并恢复正常。 java.util.concurrent包中的挂起操作都是在LockSupport类中实现的,底层就是使用这两个方法。 public native void unpark(对象线程);

记忆障碍

这主要包括loadFence、storeFence、fullFence等方法。这些方法是Java 8中新引入的,用于定义内存屏障并避免代码重新排序。它们与Java内存模型有关。有兴趣的话可以看看博主的另一篇文章。博文对Java内存模型(JMM)和volatile 关键字进行了全面的了解,这里不再展开。

//该方法之前的所有读操作必须在负载屏障之前完成public native void loadFence(); //该方法之前的所有写操作必须在store Barrier之前完成public native void storeFence(); //该方法之前的所有读写操作必须在full Barrier之前完成。这个内存屏障相当于上面两个public native void fullFence(); 的组合功能;

其他操作

//获取持有的锁,不再推荐使用@Deprecatedpublic native void monitorEnter(Object var1); //释放锁,不再推荐使用@Deprecatedpublic native void MonitorExit(Object var1); //尝试获取锁,不再推荐使用@Deprecatedpublic native boolean tryMonitorEnter(Object var1);//获取本地内存中的页数。该值始终是2 的幂public native int pageSize(); //告诉虚拟机定义一个没有安全检查的类。默认情况下,这个类加载器和保护域来自调用者类public native Class DefineClass(String name, byte[] b, int off, int len, ClassLoader loader , ProtectionDomain ProtectionDomain); //加载一个匿名类public native Class DefineAnonymousClass(Class hostClass, byte[] data, Object[] cpPatches); //判断一个类是否需要加载public native boolean shouldBeInitialized(Class c);//确保该类必须加载public native void EnsureClassInitialized(Class c)

并发包中的原子操作类(Atomic系列)

通过前面的分析,我们已经基本了解了无锁CAS的原理,也对Java中的指针类Unsafe类有了比较全面的了解。接下来我们进一步分析CAS在Java中的应用,即concurrent包中的原子操作类(Atomic operation class)。系列),从JDK 1.5开始,提供了java.util.concurrent.atomic包。该包提供了很多基于CAS的原子操作类,使用方便,性能较高。主要分为以下四种类型。

基本类型的原子更新

原子更新的基本类型主要包括3类:

AtomicBoolean:原子更新布尔类型

AtomicInteger:原子更新整数

omicLong:原子更新长整型 这3个类的实现原理和使用方式几乎是一样的,这里我们以AtomicInteger为例进行分析,AtomicInteger主要是针对int类型的数据执行原子操作,它提供了原子自增方法、原子自减方法以及原子赋值方法等,鉴于AtomicInteger的源码不多,我们直接看源码 public classAtomicIntegerextendsNumberimplementsjava.io.Serializable{ private static final long serialVersionUID = 6214790243416807050L;     // 获取指针类Unsafe    private static final Unsafe unsafe = Unsafe.getUnsafe();     //下述变量value在AtomicInteger实例对象内的内存偏移量    private static final long valueOffset;     static {         try {           //通过unsafe类的objectFieldOffset()方法,获取value变量在对象内存中的偏移          //通过该偏移量valueOffset,unsafe类的内部方法可以获取到变量value对其进行取值或赋值操作            valueOffset = unsafe.objectFieldOffset                 (AtomicInteger.class.getDeclaredField("value"));         } catch (Exception ex) { throw new Error(ex); }     }   //当前AtomicInteger封装的int变量value    private volatile int value;     public AtomicInteger(int initialValue) {         value = initialValue;     }     public AtomicInteger() {     }   //获取当前最新值,    public final int get() {         return value;     }     //设置当前值,具备volatile效果,方法用final修饰是为了更进一步的保证线程安全。    public final void set(int newValue) {         value = newValue;     }     //最终会设置成newValue,使用该方法后可能导致其他线程在之后的一小段时间内可以获取到旧值,有点类似于延迟加载    public final void lazySet(int newValue) {         unsafe.putOrderedInt(this, valueOffset, newValue);     }   //设置新值并获取旧值,底层调用的是CAS操作即unsafe.compareAndSwapInt()方法    public final int getAndSet(int newValue) {         return unsafe.getAndSetInt(this, valueOffset, newValue);     }   //如果当前值为expect,则设置为update(当前值指的是value变量)    public final boolean compareAndSet(int expect, int update) {         return unsafe.compareAndSwapInt(this, valueOffset, expect, update);     }     //当前值加1返回旧值,底层CAS操作    public final int getAndIncrement() {         return unsafe.getAndAddInt(this, valueOffset, 1);     }     //当前值减1,返回旧值,底层CAS操作    public final int getAndDecrement() {         return unsafe.getAndAddInt(this, valueOffset, -1);     }   //当前值增加delta,返回旧值,底层CAS操作    public final int getAndAdd(int delta) {         return unsafe.getAndAddInt(this, valueOffset, delta);     }     //当前值加1,返回新值,底层CAS操作    public final int incrementAndGet() {         return unsafe.getAndAddInt(this, valueOffset, 1) + 1;     }     //当前值减1,返回新值,底层CAS操作    public final int decrementAndGet() {         return unsafe.getAndAddInt(this, valueOffset, -1) - 1;     }   //当前值增加delta,返回新值,底层CAS操作    public final int addAndGet(int delta) {         return unsafe.getAndAddInt(this, valueOffset, delta) + delta;     }   //省略一些不常用的方法....} 通过上述的分析,可以发现AtomicInteger原子类的内部几乎是基于前面分析过Unsafe类中的CAS相关操作的方法实现的,这也同时证明AtomicInteger是基于无锁实现的,这里重点分析自增操作实现过程,其他方法自增实现原理一样。 //当前值加1,返回新值,底层CAS操作public final int incrementAndGet() {     return unsafe.getAndAddInt(this, valueOffset, 1) + 1; } 我们发现AtomicInteger类中所有自增或自减的方法都间接调用Unsafe类中的getAndAddInt()方法实现了CAS操作,从而保证了线程安全,关于getAndAddInt其实前面已分析过,它是Unsafe类中1.8新增的方法,源码如下 //Unsafe类中的getAndAddInt方法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;     } 可看出getAndAddInt通过一个while循环不断的重试更新要设置的值,直到成功为止,调用的是Unsafe类中的compareAndSwapInt方法,是一个CAS操作方法。这里需要注意的是,上述源码分析是基于JDK1.8的,如果是1.8之前的方法,AtomicInteger源码实现有所不同,是基于for死循环的,如下 //JDK 1.7的源码,由for的死循环实现,并且直接在AtomicInteger实现该方法,//JDK1.8后,该方法实现已移动到Unsafe类中,直接调用getAndAddInt方法即可public final int incrementAndGet() {     for (;;) {         int current = get();         int next = current + 1;         if (compareAndSet(current, next))             return next;     } } ok~,下面简单看个Demo,感受一下AtomicInteger使用方式 public classAtomicIntegerDemo{ //创建AtomicInteger,用于自增操作 static AtomicInteger i=new AtomicInteger();     public static classAddThreadimplementsRunnable{        public void run(){           for(int k=0;k<10000;k++)               i.incrementAndGet();         }     }     public static void main(String[] args) throws InterruptedException {         Thread[] ts=new Thread[10];         //开启10条线程同时执行i的自增操作        for(int k=0;k<10;k++){             ts[k]=new Thread(new AddThread());         }         //启动线程        for(int k=0;k<10;k++){ts[k].start();}         for(int k=0;k<10;k++){ts[k].join();}         System.out.println(i);//输出结果:100000    } } 在Demo中,使用原子类型AtomicInteger替换普通int类型执行自增的原子操作,保证了线程安全。至于AtomicBoolean和AtomicLong的使用方式以及实现原理是一样,大家可以自行查阅源码。 原子更新引用 原子更新引用类型可以同时更新引用类型,这里主要分析一下AtomicReference原子类,即原子更新引用类型。先看看其使用方式,如下 public class AtomicReferenceDemo2 {     public static AtomicReference atomicUserRef = new AtomicReference();     public static void main(String[] args) {         User user = new User("zejian", 18);         atomicUserRef.set(user);         User updateUser = new User("Shine", 25);         atomicUserRef.compareAndSet(user, updateUser);         //执行结果:User{name="Shine", age=25}              System.out.println(atomicUserRef.get().toString());      }     static class User {         public String name;         private int age;         public User(String name, int age) {             this.name = name;             this.age = age;         }         public String getName() {             return name;         }         @Override         public String toString() {             return "User{" +                     "name="" + name + """ +                     ", age=" + age +                     "}";         }     } } 那么AtomicReference原子类内部是如何实现CAS操作的呢? private static final Unsafe unsafe = Unsafe.getUnsafe();     private static final long valueOffset;     static {         try {             valueOffset = unsafe.objectFieldOffset                 (AtomicReference.class.getDeclaredField("value"));         } catch (Exception ex) { throw new Error(ex); }     }     //内部变量value,Unsafe类通过valueOffset内存偏移量即可获取该变量    private volatile V value;//CAS方法,间接调用unsafe.compareAndSwapObject(),它是一个//实现了CAS操作的native方法public final boolean compareAndSet(V expect, V update) {         return unsafe.compareAndSwapObject(this, valueOffset, expect, update); }//设置并获取旧值public final V getAndSet(V newValue) {         return (V)unsafe.getAndSetObject(this, valueOffset, newValue);     }     //省略其他代码......}//Unsafe类中的getAndSetObject方法,实际调用还是CAS操作public final Object getAndSetObject(Object o, long offset, Object newValue) {       Object v;       do {           v = getObjectVolatile(o, offset);       } while (!compareAndSwapObject(o, offset, v, newValue));       return v;   } 从源码看来,AtomicReference与AtomicInteger的实现原理基本是一样的,最终执行的还是Unsafe类,关于AtomicReference的其他方法也是一样的,如下 红框内的方法是Java8新增的,可以基于Lambda表达式对传递进来的期望值或要更新的值进行其他操作后再进行CAS操作,说白了就是对期望值或要更新的值进行额外修改后再执行CAS更新,在所有的Atomic原子类中几乎都存在这几个方法。 原子更新数组 原子更新数组指的是通过原子的方式更新数组里的某个元素,主要有以下3个类 AtomicIntegerArray:原子更新整数数组里的元素 AtomicLongArray:原子更新长整数数组里的元素 AtomicReferenceArray:原子更新引用类型数组里的元素 这里以AtomicIntegerArray为例进行分析,其余两个使用方式和实现原理基本一样,简单案例如下, public classAtomicIntegerArrayDemo{ static AtomicIntegerArray arr = new AtomicIntegerArray(10);     public static classAddThreadimplementsRunnable{        public void run(){           for(int k=0;k<10000;k++)               //执行数组中元素自增操作,参数为index,即数组下标              arr.getAndIncrement(k%arr.length());         }     }     public static void main(String[] args) throws InterruptedException {         Thread[] ts=new Thread[10];         //创建10条线程        for(int k=0;k<10;k++){             ts[k]=new Thread(new AddThread());         }         //启动10条线程        for(int k=0;k<10;k++){ts[k].start();}         for(int k=0;k<10;k++){ts[k].join();}         //执行结果        //[10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000]        System.out.println(arr);     } } 启动10条线程对数组中的元素进行自增操作,执行结果符合预期。使用方式比较简单,接着看看AtomicIntegerArray内部是如何实现,先看看部分源码 public classAtomicIntegerArrayimplementsjava.io.Serializable{ //获取unsafe类的实例对象 private static final Unsafe unsafe = Unsafe.getUnsafe();     //获取数组的第一个元素内存起始地址    private static final int base = unsafe.arrayBaseOffset(int[].class);     private static final int shift;     //内部数组    private final int[] array;     static {         //获取数组中一个元素占据的内存空间        int scale = unsafe.arrayIndexScale(int[].class);         //判断是否为2的次幂,一般为2的次幂否则抛异常        if ((scale & (scale - 1)) != 0)             throw new Error("data type scale not a power of two");         //        shift = 31 - Integer.numberOfLeadingZeros(scale);     }     private long checkedByteOffset(int i) {         if (i< 0 || i >= array.length)             throw new IndexOutOfBoundsException("index " + i);         return byteOffset(i);     }     //计算数组中每个元素的的内存地址    private static long byteOffset(int i) {         return ((long) i<< shift) + base;     }     //省略其他代码......} 通过前面对Unsafe类的分析,我们知道arrayBaseOffset方法可以获取数组的第一个元素起始地址,而arrayIndexScale方法可以获取每个数组元素占用的内存空间,由于这里是Int类型,而Java中一个int类型占用4个字节,也就是scale的值为4,那么如何根据数组下标值计算每个元素的内存地址呢?显然应该是 每个数组元素的内存地址=起始地址+元素下标 * 每个元素所占用的内存空间 与该方法原理相同 //计算数组中每个元素的的内存地址 private static long byteOffset(int i) { return ((long) i<< shift) + base; } 这是为什么,首先来计算出shift的值 shift = 31 - Integer.numberOfLeadingZeros(scale); 其中Integer.numberOfLeadingZeros(scale)是计算出scale的前导零个数(必须是连续的),scale=4,转成二进制为  00000000 00000000 00000000 00000100  即前导零数为29,也就是shift=2,然后利用shift来定位数组中的内存位置,在数组不越界时,计算出前3个数组元素内存地址 //第一个数组元素,index=0 , 其中base为起始地址,4代表int类型占用的字节数 address = base + 0 * 4 即address= base + 0<< 2//第二个数组元素,index=1address = base + 1 * 4 即address= base + 1<< 2//第三个数组元素,index=2address = base + 2 * 4 即address= base + 2<< 2//........ 显然shift=2,替换去就是 address= base + i<< shift 这就是 byteOffset(int i) 方法的计算原理。因此byteOffset(int)方法可以根据数组下标计算出每个元素的内存地址。至于其他方法就比较简单了,都是间接调用Unsafe类的CAS原子操作方法,如下简单看其中几个常用方法 //执行自增操作,返回旧值,i是指数组元素下标public final int getAndIncrement(int i) {       return getAndAdd(i, 1);

}//指定下标元素执行自增操作,并返回新值public final int incrementAndGet(int i) {     return getAndAdd(i, 1) + 1; }//指定下标元素执行自减操作,并返回新值public final int decrementAndGet(int i) {     return getAndAdd(i, -1) - 1; }//间接调用unsafe.getAndAddInt()方法public final int getAndAdd(int i, int delta) {     return unsafe.getAndAddInt(array, checkedByteOffset(i), delta); }//Unsafe类中的getAndAddInt方法,执行CAS操作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;     } 于AtomicLongArray和AtomicReferenceArray原子类,使用方式和实现原理基本一样。 原子更新属性 如果我们只需要某个类里的某个字段,也就是说让普通的变量也享受原子操作,可以使用原子更新字段类,如在某些时候由于项目前期考虑不周全,项目需求又发生变化,使得某个类中的变量需要执行多线程操作,由于该变量多处使用,改动起来比较麻烦,而且原来使用的地方无需使用线程安全,只要求新场景需要使用时,可以借助原子更新器处理这种场景,Atomic并发包提供了以下三个类: AtomicIntegerFieldUpdater:原子更新整型的字段的更新器。 AtomicLongFieldUpdater:原子更新长整型字段的更新器。 AtomicReferenceFieldUpdater:原子更新引用类型里的字段。 请注意原子更新器的使用存在比较苛刻的条件如下 操作的字段不能是static类型。 操作的字段不能是final类型的,因为final根本没法修改。 字段必须是volatile修饰的,也就是数据本身是读一致的。 属性必须对当前的Updater所在的区域是可见的,如果不是当前类内部进行原子更新器操作不能使用private,protected子类操作父类时修饰符必须是protect权限及以上,如果在同一个package下则必须是default权限及以上,也就是说无论何时都应该保证操作类与被操作类间的可见性。 下面看看AtomicIntegerFieldUpdater和AtomicReferenceFieldUpdater的简单使用方式 public classAtomicIntegerFieldUpdaterDemo{ public static classCandidate{ int id;         volatile int score;     }     public static classGame{        int id;         volatile String name;         public Game(int id, String name) {             this.id = id;             this.name = name;         }         @Override        public String toString() {             return "Game{" +                     "id=" + id +                     ", name="" + name + """ +                     "}";         }     }     static AtomicIntegerFieldUpdater atIntegerUpdater         = AtomicIntegerFieldUpdater.newUpdater(Candidate.class, "score");     static AtomicReferenceFieldUpdater atRefUpdate =             AtomicReferenceFieldUpdater.newUpdater(Game.class,String.class,"name");     //用于验证分数是否正确    public static AtomicInteger allScore=new AtomicInteger(0);     public static void main(String[] args) throws InterruptedException {         final Candidate stu=new Candidate();         Thread[] t=new Thread[10000];         //开启10000个线程        for(int i = 0 ; i< 10000 ; i++) {             t[i]=new Thread() {                 public void run() {                     if(Math.random()>0.4){                         atIntegerUpdater.incrementAndGet(stu);                         allScore.incrementAndGet();                     }                 }             };             t[i].start();         }         for(int i = 0 ; i< 10000 ; i++) {  t[i].join();}         System.out.println("最终分数score="+stu.score);         System.out.println("校验分数allScore="+allScore);         //AtomicReferenceFieldUpdater 简单的使用        Game game = new Game(2,"zh");         atRefUpdate.compareAndSet(game,game.name,"JAVA-HHH");         System.out.println(game.toString());         /**         * 输出结果:         * 最终分数score=5976           校验分数allScore=5976           Game{id=2, name="JAVA-HHH"}         */    } } 我们使用AtomicIntegerFieldUpdater更新候选人(Candidate)的分数score,开启了10000条线程投票,当随机值大于0.4时算一票,分数自增一次,其中allScore用于验证分数是否正确(其实用于验证AtomicIntegerFieldUpdater更新的字段是否线程安全),当allScore与score相同时,则说明投票结果无误,也代表AtomicIntegerFieldUpdater能正确更新字段score的值,是线程安全的。对于AtomicReferenceFieldUpdater,我们在代码中简单演示了其使用方式,注意在AtomicReferenceFieldUpdater注明泛型时需要两个泛型参数,一个是修改的类类型,一个修改字段的类型。至于AtomicLongFieldUpdater则与AtomicIntegerFieldUpdater类似,不再介绍。接着简单了解一下AtomicIntegerFieldUpdater的实现原理,实际就是反射和Unsafe类结合,AtomicIntegerFieldUpdater是个抽象类,实际实现类为AtomicIntegerFieldUpdaterImpl public abstract class AtomicIntegerFieldUpdater {     public staticAtomicIntegerFieldUpdaternewUpdater(Classtclass,                                                               String fieldName) {         //实际实现类AtomicIntegerFieldUpdaterImpl                                                  return new AtomicIntegerFieldUpdaterImpl             (tclass, fieldName, Reflection.getCallerClass());     } } 看看AtomicIntegerFieldUpdaterImpl private static classAtomicIntegerFieldUpdaterImplextendsAtomicIntegerFieldUpdater { private static final Unsafe unsafe = Unsafe.getUnsafe();         private final long offset;//内存偏移量        private final Class tclass;         private final Class cclass;         AtomicIntegerFieldUpdaterImpl(final Class tclass,                                       final String fieldName,                                       final Class caller) {             final Field field;//要修改的字段            final int modifiers;//字段修饰符            try {                 field = AccessController.doPrivileged(                     new PrivilegedExceptionAction() {                         public Field run() throws NoSuchFieldException {                             return tclass.getDeclaredField(fieldName);//反射获取字段对象                        }                     });                     //获取字段修饰符                modifiers = field.getModifiers();             //对字段的访问权限进行检查,不在访问范围内抛异常                sun.reflect.misc.ReflectUtil.ensureMemberAccess(                     caller, tclass, null, modifiers);                 ClassLoader cl = tclass.getClassLoader();                 ClassLoader ccl = caller.getClassLoader();                 if ((ccl != null) && (ccl != cl) &&                     ((cl == null) || !isAncestor(cl, ccl))) {               sun.reflect.misc.ReflectUtil.checkPackageAccess(tclass);                 }             } catch (PrivilegedActionException pae) {                 throw new RuntimeException(pae.getException());             } catch (Exception ex) {                 throw new RuntimeException(ex);             }             Class fieldt = field.getType();             //判断是否为int类型            if (fieldt != int.class)                 throw new IllegalArgumentException("Must be integer type");             //判断是否被volatile修饰            if (!Modifier.isVolatile(modifiers))                 throw new IllegalArgumentException("Must be volatile type");             this.cclass = (Modifier.isProtected(modifiers) &&                           caller != tclass) ? caller : null;             this.tclass = tclass;             //获取该字段的在对象内存的偏移量,通过内存偏移量可以获取或者修改该字段的值            offset = unsafe.objectFieldOffset(field);         }         } 从AtomicIntegerFieldUpdaterImpl的构造器也可以看出更新器为什么会有这么多限制条件了,当然最终其CAS操作肯定是通过unsafe完成的,简单看一个方法 public int incrementAndGet(T obj) {         int prev, next;         do {             prev = get(obj);             next = prev + 1;             //CAS操作        } while (!compareAndSet(obj, prev, next));         return next; }//最终调用的还是unsafe.compareAndSwapInt()方法public boolean compareAndSet(T obj, int expect, int update) {             if (obj == null || obj.getClass() != tclass || cclass != null) fullCheck(obj);             return unsafe.compareAndSwapInt(obj, offset, expect, update);         } CAS的ABA问题及其解决方案 假设这样一种场景,当第一个线程执行CAS(V,E,U)操作,在获取到当前变量V,准备修改为新值U前,另外两个线程已连续修改了两次变量V的值,使得该值又恢复为旧值,这样的话,我们就无法正确判断这个变量是否已被修改过,如下图 这就是典型的CAS的ABA问题,一般情况这种情况发现的概率比较小,可能发生了也不会造成什么问题,比如说我们对某个做加减法,不关心数字的过程,那么发生ABA问题也没啥关系。但是在某些情况下还是需要防止的,那么该如何解决呢?在Java中解决ABA问题,我们可以使用以下两个原子类 AtomicStampedReference AtomicStampedReference原子类是一个带有时间戳的对象引用,在每次修改后,AtomicStampedReference不仅会设置新值而且还会记录更改的时间。当AtomicStampedReference设置对象值时,对象值以及时间戳都必须满足期望值才能写入成功,这也就解决了反复读写时,无法预知值是否已被修改的窘境,测试demo如下 /** * Created by zejian on 2017/7/2. * Blog : http://blog.csdn.net/javazejian [原文地址,请尊重原创] */public classABADemo{    static AtomicInteger atIn = new AtomicInteger(100);     //初始化时需要传入一个初始值和初始时间    static AtomicStampedReference atomicStampedR =             new AtomicStampedReference(200,0);     static Thread t1 = new Thread(new Runnable() {         @Override        public void run() {             //更新为200            atIn.compareAndSet(100, 200);             //更新为100            atIn.compareAndSet(200, 100);         }     });     static Thread t2 = new Thread(new Runnable() {         @Override        public void run() {             try {                 TimeUnit.SECONDS.sleep(1);             } catch (InterruptedException e) {                 e.printStackTrace();             }             boolean flag=atIn.compareAndSet(100,500);             System.out.println("flag:"+flag+",newValue:"+atIn);         }     });     static Thread t3 = new Thread(new Runnable() {         @Override        public void run() {             int time=atomicStampedR.getStamp();             //更新为200            atomicStampedR.compareAndSet(100, 200,time,time+1);             //更新为100            int time2=atomicStampedR.getStamp();             atomicStampedR.compareAndSet(200, 100,time2,time2+1);         }     });     static Thread t4 = new Thread(new Runnable() {         @Override        public void run() {             int time = atomicStampedR.getStamp();             System.out.println("sleep 前 t4 time:"+time);             try {                 TimeUnit.SECONDS.sleep(1);             } catch (InterruptedException e) {                 e.printStackTrace();             }             boolean flag=atomicStampedR.compareAndSet(100,500,time,time+1);             System.out.println("flag:"+flag+",newValue:"+atomicStampedR.getReference());         }     });     public static  void  main(String[] args) throws InterruptedException {         t1.start();         t2.start();         t1.join();         t2.join();         t3.start();         t4.start();         /**         * 输出结果:         flag:true,newValue:500         sleep 前 t4 time:0         flag:false,newValue:200         */    } } 对比输出结果可知,AtomicStampedReference类确实解决了ABA的问题,下面我们简单看看其内部实现原理 public class AtomicStampedReference {     //通过Pair内部类存储数据和时间戳    private static class Pair {         final T reference;         final int stamp;         private Pair(T reference, int stamp) {             this.reference = reference;             this.stamp = stamp;         }         static Pair of(T reference, int stamp) {             return new Pair(reference, stamp);         }     }     //存储数值和时间的内部类    private volatile Pair pair;     //构造器,创建时需传入初始值和时间初始值    public AtomicStampedReference(V initialRef, int initialStamp) {         pair = Pair.of(initialRef, initialStamp);     } } 接着看看其compareAndSet方法的实现: public boolean compareAndSet(V expectedReference,                                 V  newReference,                                 int expectedStamp,                                 int newStamp) {         Pair current = pair;         return            expectedReference == current.reference &&             expectedStamp == current.stamp &&             ((newReference == current.reference &&               newStamp == current.stamp) ||             casPair(current, Pair.of(newReference, newStamp)));     } 同时对当前数据和当前时间进行比较,只有两者都相等是才会执行casPair()方法,单从该方法的名称就可知是一个CAS方法,最终调用的还是Unsafe类中的compareAndSwapObject方法 private boolean casPair(Pair cmp, Pair val) {         return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val);     } 到这我们就很清晰AtomicStampedReference的内部实现思想了,通过一个键值对Pair存储数据和时间戳,在更新时对数据和时间戳进行比较,只有两者都符合预期才会调用Unsafe的compareAndSwapObject方法执行数值和时间戳替换,也就避免了ABA的问题。 AtomicMarkableReference类 AtomicMarkableReference与AtomicStampedReference不同的是,AtomicMarkableReference维护的是一个boolean值的标识,也就是说至于true和false两种切换状态,经过博主测试,这种方式并不能完全防止ABA问题的发生,只能减少ABA问题发生的概率。 public classABADemo{ static AtomicMarkableReference atMarkRef =               new AtomicMarkableReference(100,false); static Thread t5 = new Thread(new Runnable() {         @Override        public void run() {             boolean mark=atMarkRef.isMarked();             System.out.println("mark:"+mark);             //更新为200            System.out.println("t5 result:"+atMarkRef.compareAndSet(atMarkRef.getReference(), 200,mark,!mark));         }     });     static Thread t6 = new Thread(new Runnable() {         @Override        public void run() {             boolean mark2=atMarkRef.isMarked();             System.out.println("mark2:"+mark2);             System.out.println("t6 result:"+atMarkRef.compareAndSet(atMarkRef.getReference(), 100,mark2,!mark2));         }     });     static Thread t7 = new Thread(new Runnable() {         @Override        public void run() {             boolean mark=atMarkRef.isMarked();             System.out.println("sleep 前 t7 mark:"+mark);             try {                 TimeUnit.SECONDS.sleep(1);             } catch (InterruptedException e) {                 e.printStackTrace();             }             boolean flag=atMarkRef.compareAndSet(100,500,mark,!mark);             System.out.println("flag:"+flag+",newValue:"+atMarkRef.getReference());         }     });     public static  void  main(String[] args) throws InterruptedException {                t5.start();t5.join();         t6.start();t6.join();         t7.start();         /**         * 输出结果:         mark:false         t5 result:true         mark2:true         t6 result:true         sleep 前 t5 mark:false         flag:true,newValue:500 ---->成功了.....说明还是发生ABA问题         */    } } AtomicMarkableReference的实现原理与AtomicStampedReference类似,这里不再介绍。到此,我们也明白了如果要完全杜绝ABA问题的发生,我们应该使用AtomicStampedReference原子类更新对象,而对于AtomicMarkableReference来说只能减少ABA问题的发生概率,并不能杜绝。 再谈自旋锁 自旋锁是一种假设在不久将来,当前的线程可以获得锁,因此虚拟机会让当前想要获取锁的线程做几个空循环(这也是称为自旋的原因),在经过若干次循环后,如果得到锁,就顺利进入临界区。如果还不能获得锁,那就会将线程在操作系统层面挂起,这种方式确实也是可以提升效率的。但问题是当线程越来越多竞争很激烈时,占用CPU的时间变长会导致性能急剧下降,因此Java虚拟机内部一般对于自旋锁有一定的次数限制,可能是50或者100次循环后就放弃,直接挂起线程,让出CPU资源。如下通过AtomicReference可实现简单的自旋锁。 public classSpinLock{ private AtomicReference sign =new AtomicReference<>();   public void lock(){     Thread current = Thread.currentThread();     while(!sign .compareAndSet(null, current)){     }   }   public void unlock (){     Thread current = Thread.currentThread();     sign .compareAndSet(current, null);   } }

用户评论

迁心

Java并发编程真是个让人头疼的话题!

    有17位网友表示赞同!

月下独酌

想做一个高性能的java项目,并发编程是必须掌握的技术。

    有6位网友表示赞同!

无望的后半生

看了点文档,感觉线程管理还是蛮复杂的啊...

    有16位网友表示赞同!

゛指尖的阳光丶

有没有大佬分享一下高效的协程学习资料?

    有7位网友表示赞同!

走过海棠暮

最近在学java的多道题中发现不少都是关于并发编程的,看来很重要!

    有12位网友表示赞同!

蔚蓝的天空〃没有我的翅膀

Java并发编程这门课我差点没考过,太难了...

    有14位网友表示赞同!

娇眉恨

听说synchronized关键字在并发编程中很常见?

    有10位网友表示赞同!

青瓷清茶倾城歌

学习Java并发编程的关键是什么?

    有13位网友表示赞同!

半梦半醒半疯癫

想明白线程池是怎么工作的还是蛮费劲的...

    有11位网友表示赞同!

我绝版了i

并发编程调试起来真的好难,总是不太知道哪里出了问题!

    有12位网友表示赞同!

我怕疼别碰我伤口

有没有什么好的书籍推荐学习Java并发编程?

    有20位网友表示赞同!

哽咽

最近在开发项目时遇到了并发问题,不知道该如何解决...

    有12位网友表示赞同!

裸睡の鱼

Java中如何实现一个安全的线程池?

    有20位网友表示赞同!

刺心爱人i

看了些Java并发编程的资料,感觉太复杂了,还是慢慢消化吧!

    有15位网友表示赞同!

歆久

请问有哪些常用的单例模式设计模式可以应用于Java并发?

    有19位网友表示赞同!

栀蓝

Java并发编程能提高程序效率吗?

    有19位网友表示赞同!

轨迹!

为什么说Java并发编程是现代软件开发的必备技能呢?

    有13位网友表示赞同!

ˉ夨落旳尐孩。

想了解一下Java并发编程与分布式编程的关系?

    有14位网友表示赞同!

相知相惜

学习并发编程真的需要多实践嘛?

    有13位网友表示赞同!

【深入解析Java多线程编程技巧与策略】相关文章:

1.蛤蟆讨媳妇【哈尼族民间故事】

2.米颠拜石

3.王羲之临池学书

4.清代敢于创新的“浓墨宰相”——刘墉

5.“巧取豪夺”的由来--米芾逸事

6.荒唐洁癖 惜砚如身(米芾逸事)

7.拜石为兄--米芾逸事

8.郑板桥轶事十则

9.王献之被公主抢亲后的悲惨人生

10.史上真实张三丰:在棺材中竟神奇复活