前言:
如今看官们对“java中什么是访问控制符”大约比较关注,你们都想要分析一些“java中什么是访问控制符”的相关文章。那么小编也在网上搜集了一些有关“java中什么是访问控制符””的相关内容,希望咱们能喜欢,你们快快来学习一下吧!前两篇文章我介绍了AtomicInteger和AtomicIntegerArray,现在总结一下两个类的特点。
1:AtomicInteger是对单个变量进行原子操作的。2:AtomicIntegerArray是对数组中的元素进行原子操作的,而不是数组本身。
本篇文章介绍第三类原子操作:AtomicIntegerFieldUpdater.
1:关于private/protected/public访问控制符2:AtomicIntegerFieldUpdater的一些限制3:AtomicIntegerFieldUpdater实例4:AtomicIntegerFieldUpdater源码解析一、关于private/protected/public访问控制符
由于下面要用到Java这个知识点,所以我们在复习一下关于Java的访问控制符,在Java中主要定义三个访问控制符,还有一个默认的(没有被任何访问控制符修饰default),一共四个访问控制符
1:private:私有的,只能自己能访问,其他都访问不到2:默认的(default,如果没有被任何访问控制符修饰):同一类或者同一个包中能够访问到,其他访问不到。3:protected:同一个类、子类、同一个包能放到,其他访问不到。4:public:所有类都能访问到。
他们的访问范围如图:
通过一个表格更加清晰的看一下:
这个知识点大家都非常熟悉了,理解了访问控制符,AtomicIntegerFieldUpdater的一些知识点就好理解了。
二、AtomicIntegerFieldUpdater的一些限制
接下来的文章内容如果没有特殊说明,AtomicIntegerFieldUpdater更新的字段简称为被操作的字段。
1:被操作的字段必须被volatile修饰2:被操作的字段必须是int类型,不能是包装类型,如Integer3:被操作的字段只能是实例变量,不能是类变量,就是不能被static修饰4:被操作的字段只能是可变变量,不能被final修饰,其实final和volatile不能同时存在,否则编译不通过。5:如果调用方无法直接访问到被操作字段,则会抛出异常
关于第5点就是用到了上面所说的访问控制权限:
1:如果调用方和被操作字段所在的类是同一类,那么被操作字段可以被private/defalut/protected/public修饰。2:如果调用方和被操作字段所在的类是同一个包,那么被操作字段可以被defalut/protected/public修饰3:如果调用方和被操作字段所在的类属于子类,那么被操作字段可以被protected/public修饰4:如果调用方和被操作字段所在的类不在同一个包,也不属于子类关系,那么只能被public修饰。三、AtomicIntegerFieldUpdater实例
上面提出了它的限制后,下面我们通过实例去一个一个的证明:
1:首先证明AtomicIntegerFieldUpdater可以原子更新一个对象中被volatile修饰的int类型的字段
下面的实例调用对象AtomicIntegerFieldUpdater和被操作字段所在的类在同一个包中
调用对象:AtomicIntegerFieldUpdater
package com.stqsht.juc.atomic;public class AtomicIntegerFieldUpdaterTest {//定义一个线程池 private static ThreadPoolExecutor pool; static { pool = new ThreadPoolExecutor(5, 10, 1, TimeUnit.SECONDS, new ArrayBlockingQueue<>(1000)); } public static void main(String[] args) throws InterruptedException { Obj obj = new Obj(0, 0); AtomicIntegerFieldUpdater<Obj> afu = AtomicIntegerFieldUpdater.newUpdater(Obj.class, "age"); for (int i = 0; i < 5; i++) { pool.execute(() -> { for (int j = 0; j < 10000; j++) { obj.setId(obj.getId() + 1); afu.incrementAndGet(obj); } }); } pool.awaitTermination(5, TimeUnit.SECONDS); System.out.println("id=" + obj.getId()); System.out.println("age=" + obj.getAge()); pool.shutdown(); }}
被操作字段所在的类Obj中有两个字段,一个字段没有被volatile修饰,一个字段被volatile修饰
package com.stqsht.juc.atomic;class Obj { private int id; volatile int age; public Obj(int id, int age) { this.id = id; this.age = age; }//省略getter和setter方法}
通过运行多次后,得出一个结论,每一次id的结果都不相同,而age得到的结果相同且是正确的,运行结果如下:
从上述案例可以证明,AtomicIntegerFieldUpdater可以原子的更新一个对象中的字段。
2:如果被操作的字段没有关键字volatile,则会抛出异常:Must be volatile type
class Obj { private int id; int age; public Obj(int id, int age) { this.id = id; this.age = age; }//setter和getter省略}
上面的被修饰的字段age并没有关键字volatile,运行代码结果如下:
从运行结果可以看出,如果被操作的字段没有被volatile修饰,那么它直接抛出异常,因为多线程下volatile具有可见性和有序性。
3:被操作的字段只能是int类型,不能是包装类型Integer
class Obj { private int id; volatile Integer age; public Obj(int id, int age) { this.id = id; this.age = age; }//setter和getter省略}
上面的被操作字段是一个包装类Integer,运行结果如下图:
4:被操作的字段只能是实例变量,不能是类变量,也就是被操作字段不能被static修饰
class Obj { private int id; static volatile int age; public Obj(int id, int age) { this.id = id; this.age = age; }//setter和getter省略}
运行结果如下图:
5:被操作的字段只能是可变变量,不能被final修饰,其实final和volatile不能同时存在,否则编译不通过。
6:如果调用方无法直接访问到被操作字段,则会抛出异常
6.1:如果调用方和被操作字段所在的类是同一个类,被操作字段可以被private/defalut/protected/public修饰
public class User { public static void main(String[] args) { AtomicIntegerFieldUpdater<User>ai = AtomicIntegerFieldUpdater.newUpdater(User.class,"age"); User user = new User(0); ai.incrementAndGet(user); System.out.println(user.getAge()); }//可以被private/defalut/protected/public修饰 private volatile int age; public User(int age) { this.age = age; }//省略setter和gettter方法}
大家可以亲自测试以下上面的demo,无论成员变量age被什么访问控制符修饰,都是可以的。
6.2:如果调用方和被操作字段所在的类在同一个包中,被操作字段可以被defalut/protected/public修饰,用private则会抛出异常
调用方:Caller
package com.stqsht.juc.other;import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;//调用方public class Caller { public static void main(String[] args) { User user = new User(0); AtomicIntegerFieldUpdater<User> ai = AtomicIntegerFieldUpdater.newUpdater(User.class,"age"); ai.incrementAndGet(user); System.out.println(user.getAge()); }}
被操作字段所在的类:User
package com.stqsht.juc.other;//调用方public class User {//可以被default/protected/public修饰 volatile int age; public User(int age) { this.age = age; } //省略setter和getter方法}
如果上面的例子如果被private修饰,则会抛出如图的异常:
6.3:如果调用方和被操作字段所在的类属于子类关系,被操作字段可以被protected/public修饰
调用方:com.stqsht.juc.other.Caller
package com.stqsht.juc.other;import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;import com.stqsht.juc.common.User;//调用方public class Caller extends User { public static void main(String[] args) { Caller user = new Caller(0); AtomicIntegerFieldUpdater<User> ai = AtomicIntegerFieldUpdater.newUpdater(User.class,"age"); ai.incrementAndGet(user); System.out.println(user.getAge()); } public Caller(int age) { super(age); }}
被操作字段所在的类:com.stqsht.juc.common.User
package com.stqsht.juc.common;//被操作字段所在的类,可以被protect/public修饰public class User { volatile int age; public User(int age) { this.age = age; } public int getAge() { return age; } public void setAge(int age) { this.age = age; }}
如果上面的例子被private/defalut修饰,则会抛出以下异常。
6.4:如果调用方和被操作字段所在的类不在同一个包,也不属于子类关系,那么只能被public修饰。
调用方:com.stqsht.juc.other.Caller
package com.stqsht.juc.other;import com.stqsht.juc.common.User;import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;//调用方public class Caller { public static void main(String[] args) { User user = new User(0); AtomicIntegerFieldUpdater<User> ai = AtomicIntegerFieldUpdater.newUpdater(User.class, "age"); ai.incrementAndGet(user); System.out.println(user.getAge()); }}
被操作字段所在的类:com.stqsht.juc.common.User
package com.stqsht.juc.common;public class User { protected volatile int age; public User(int age) { this.age = age; } public int getAge() { return age; } public void setAge(int age) { this.age = age; }}
如果上面的代码被private/defalut/protected修饰,则会抛出如下异常
上面列出了AtomicIntegerFieldUpdater的一些限制,我们知道了怎么使用AtomicIntegerFieldUpdaterle ,那从源码角度,它是怎么有这些限制呢,我们进入它的源码一探究竟。
四、AtomicIntegerFieldUpdater源码解析
当我们跟进源码中看到它是一个抽象类,它只有一个方法能够获取一个实例对象。
public abstract class AtomicIntegerFieldUpdater<T> { @CallerSensitive public static <U> AtomicIntegerFieldUpdater<U> newUpdater(Class<U> tclass, String fieldName) { return new AtomicIntegerFieldUpdaterImpl<U> (tclass, fieldName, Reflection.getCallerClass()); }//省略其他代码}参数1:tclass:实例的Class对象,例如上面例子User.class参数2:fieldName:被操作的字段的名称,例如上面例子成员变量age
通过一个newUpdater()方法获取一个实例对象,从这个方法中可以看出他的实现类是AtomicIntegerFieldUpdaterImpl,这个实现类在它的内部,我们一起跟进它的实现类。
private static final class AtomicIntegerFieldUpdaterImpl<T>extends AtomicIntegerFieldUpdater<T> {//原子操作封装对象private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe();private final long offset;//调用方的Class对象,如上面例子Caller.classprivate final Class<?> cclass;//被操作字段所在的类的Class对象,如上面例子User.classprivate final Class<T> tclass;
上面是子类的几个重要的成员变量,下面我们看一下它的核心构造函数
//参数一:tclass:被操作字段所在类的Class对象//参数二:被操作字段的名称//参数三:调用方的Class对象AtomicIntegerFieldUpdaterImpl(final Class<T> tclass, final String fieldName, final Class<?> caller) { final Field field; final int modifiers; try {//通过反射获取被操作字段 field = AccessController.doPrivileged( new PrivilegedExceptionAction<Field>() { 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) {//$5:如果调用方不能直接访问被操作字段,则抛出异常 throw new RuntimeException(ex); } if (field.getType() != int.class)//$2:如果不是int类型,则会抛出异常 throw new IllegalArgumentException("Must be integer type"); if (!Modifier.isVolatile(modifiers))//$1:如果没有被volatile修饰,则抛出异常 throw new IllegalArgumentException("Must be volatile type"); this.cclass = (Modifier.isProtected(modifiers) && tclass.isAssignableFrom(caller) && !isSamePackage(tclass, caller)) ? caller : tclass; this.tclass = tclass;//$3:objectFieldOffset()方法的作用:获取某个字段相对Java对象的起始地址的偏移地址 this.offset = U.objectFieldOffset(field);}
这个构造函数涵盖了AtomicIntegerFieldUpdater的所有限制,所以从源码角度也证明了上面总结的限制。这个构造函数的流程图如下:
AtomicIntegerFieldUpdater的核心方法和AtomicInteger类似,功能也是一样的:原子更新一个被volatile修饰的变量,这里就不在介绍了,如果不太明白这些方法的用法,请进入我的今日头条主页查看
接下来介绍几个特殊的方法:判断调用方和被操作字段所在的类是否在同一个包,是否是子类关系。
第一个方法:isAncestor:判断是否属于子类关系
private static boolean isAncestor(ClassLoader first, ClassLoader second) { ClassLoader acl = first; do { acl = acl.getParent(); if (second == acl) {//属于子类,返回true return true; } } while (acl != null); return false;}
第二个方法:isSamePackage:判断是否在同一个包
private static boolean isSamePackage(Class<?> class1, Class<?> class2) { return class1.getClassLoader() == class2.getClassLoader() && Objects.equals(getPackageName(class1), getPackageName(class2));}private static String getPackageName(Class<?> cls) { String cn = cls.getName(); int dot = cn.lastIndexOf('.'); return (dot != -1) ? cn.substring(0, dot) : "";}
标签: #java中什么是访问控制符