从源码角度学习Java动态代理
前言
最近,看了一下关于RMI(Remote Method Invocation)相关的知识,遇到了一个动态代理的问题,然后就决定探究一下动态代理。
这里先科普一下RMI。
RMI
像我们平时写的程序,对象之间互相调用方法都是在同一个JVM中进行,而RMI可以实现一个JVM上的对象调用另一个JVM上对象的方法,即远程调用。
接口定义
定义一个远程对象接口,实现Remote接口来进行标记。
public interface UserInterface extends Remote {
void sayHello() throws RemoteException;
}
远程对象定义
定义一个远程对象类,继承UnicastRemoteObject来实现Serializable和Remote接口,并实现接口方法。
public class User extends UnicastRemoteObject implements UserInterface {
public User() throws RemoteException {}
@Override
public void sayHello() {
System.out.println("Hello World");
}
}
服务端
启动服务端,将user对象在注册表上进行注册。
public class RmiServer {
public static void main(String[] args) throws RemoteException, AlreadyBoundException, MalformedURLException {
User user = new User();
LocateRegistry.createRegistry(8888);
Naming.bind("rmi://127.0.0.1:8888/user", user);
System.out.println("rmi server is starting...");
}
}
启动服务端:
客户端
从服务端注册表获取远程对象,在服务端调用sayHello()方法。
public class RmiClient {
public static void main(String[] args) throws RemoteException, NotBoundException, MalformedURLException {
UserInterface user = (UserInterface) Naming.lookup("rmi://127.0.0.1:8888/user");
user.sayHello();
}
}
服务端运行结果:
至此,一个简单的RMI demo完成。
动态代理
提出问题
看了看RMI代码,觉得UserInterface这个接口有点多余,如果客户端使用Naming.lookup()获取的对象不强转成UserInterface,直接强转成User是不是也可以,于是试了一下,就报了以下错误:
似曾相识又有点陌生的$Proxy0,翻了翻尘封的笔记找到了是动态代理的知识点,寥寥几笔带过,所以决定梳理一下动态代理,重新整理一份笔记。
动态代理Demo
接口定义
public interface UserInterface {
void sayHello();
}
真实角色定义
public class User implements UserInterface {
@Override
public void sayHello() {
System.out.println("Hello World");
}
}
调用处理类定义
代理类调用真实角色的方法时,其实是调用与真实角色绑定的处理类对象的invoke()方法,而invoke()调用的是真实角色的方法。
这里需要实现 InvocationHandler 接口以及invoke()方法。
public class UserHandler implements InvocationHandler {
private User user;
public UserProxy(User user) {
this.user = user;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("invoking start....");
method.invoke(user);
System.out.println("invoking stop....");
return user;
}
}
执行类
public class Main {
public static void main(String[] args) {
User user = new User();
// 处理类和真实角色绑定
UserHandler userHandler = new UserHandler(user);
// 开启将代理类class文件保存到本地模式,平时可以省略
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
// 动态代理生成代理对象$Proxy0
Object o = Proxy.newProxyInstance(Main.class.getClassLoader(), new Class[]{UserInterface.class}, userHandler);
// 调用的其实是invoke()
((UserInterface)o).sayHello();
}
运行结果:
这样动态代理的基本用法就学完了,可是还有好多问题不明白。
动态代理是怎么调用的invoke()方法? 处理类UserHandler有什么作用? 为什么要将类加载器和接口类数组当作参数传入newProxyInstance?
假如让你去实现动态代理,你有什么设计思路?
猜想
动态代理,是不是和静态代理,即设计模式的代理模式有相同之处呢?
简单捋一捋代理模式实现原理:真实角色和代理角色共同实现一个接口并实现抽象方法A,代理类持有真实角色对象,代理类在A方法中调用真实角色对象的A方法。在Main中实例化代理对象,调用其A方法,间接调用了真实角色的A方法。
「实现代码」
// 接口和真实角色对象就用上面代码
// 代理类,实现UserInterface接口
public class UserProxy implements UserInterface {
// 持有真实角色对象
private User user = new User();
@Override
public void sayHello() {
System.out.println("invoking start....");
// 在代理对象的sayHello()里调用真实角色的sayHello()
user.sayHello();
System.out.println("invoking stop....");
}
}
// 运行类
public class Main {
public static void main(String[] args) {
// 实例化代理角色对象
UserInterface userProxy = new UserProxy();
// 调用了代理对象的sayHello(),其实是调用了真实角色的sayHello()
userProxy.sayHello();
}
拿开始的动态代理代码和静态代理比较,接口、真实角色都有了,区别就是多了一个UserHandler处理类,少了一个UserProxy代理类。
接着对比一下两者的处理类和代理类,发现UserHandler的invoke()和UserProxy的sayHello()这两个方法的代码都是一样的。那么,是不是新建一个UserProxy类,然后实现UserInterface接口并持有UserHandler的对象,在sayHello()方法中调用UserHandler的invoke()方法,就可以动态代理了。
「代码大概就是这样的」
// 猜想的代理类结构,动态代理生成的代理是com.sun.proxy.$Proxy0
public class UserProxy implements UserInterface{
// 持有处理类的对象
private InvocationHandler handler;
public UserProxy(InvocationHandler handler) {
this.handler = handler;
}
// 实现sayHello()方法,并调用invoke()
@Override
public void sayHello() {
try {
handler.invoke(this, UserInterface.class.getMethod("sayHello"), null);
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
}
// 执行类
public static void main(String[] args) {
User user = new User();
UserHandler userHandler = new UserHandler(user);
UserProxy proxy = new UserProxy(userHandler);
proxy.sayHello();
}
输出结果:
上面的代理类代码是写死的,而动态代理是当你调用Proxy.newProxyInstance()时,会根据你传入的参数来动态生成这个代理类代码,如果让我实现,会是以下这个流程。
根据你传入的Class[]接口数组,代理类会来实现这些接口及其方法(这里就是sayHello()),并且持有你传入的userHandler对象,使用文件流将预先设定的包名、类名、方法名等一行行代码写到本地磁盘,生成$Proxy0.java文件 使用编译器将编译成Proxy0.class 根据你传入的ClassLoader将$Proxy0.class加载到JMV中 调用Proxy.newProxyInstance()就会返回一个$Proxy0的对象,然后调用sayHello(),就执行了里面userHandler的invoke()
以上就是对动态代理的一个猜想过程,下面就通过debug看看源码是怎么实现的。
在困惑的日子里学会拥抱源码
调用流程图
这里先用PPT画一个流程图,可以跟着流程图来看后面的源码。
「从newProxyInstance()设置断点」
newProxyInstance()
newProxyInstance()代码分为上下两部分,上部分是获取类,下部分是通过反射构建Proxy0对象。
「上部分代码」
从名字看就知道getProxyClass0()是核心方法,step into
getProxyClass0()
里面调用了WeakCache对象的get()方法,这里暂停一下debug,先讲讲WeakCache类。
WeakCache
顾名思义,它是一个弱引用缓存。那什么是是弱引用呢,是不是还有强引用呢?
弱引用
WeakReference就是弱引用类,作为包装类来包装其他对象,在进行GC时,其中的包装对象会被回收,而WeakReference对象会被放到引用队列中。
举个栗子:
// 这就是强引用,只要不写str1 = null,str1指向的这个字符串不就会被垃圾回收
String str1 = new String("hello");
ReferenceQueue referenceQueue = new ReferenceQueue();
// 只要垃圾回收,这个str2里面包装的对象就会被回收,但是这个弱引用对象不会被回收,即word会被回收,但是str2指向的弱引用对象不会
// 每个弱引用关联一个ReferenceQueue,当包装的对象被回收,这个弱引用对象会被放入引用队列中
WeakReference<String> str2 = new WeakReference<>(new String("world"), referenceQueue);
// 执行gc
System.gc();
Thread.sleep(3);
// 输出被回收包装对象的弱引用对象:java.lang.ref.WeakReference@2077d4de
// 可以debug看一下,弱引用对象的referent变量指向的包装对象已经为null
System.out.println(referenceQueue.poll());
WeakCache的结构
其实整个WeakCache的都是围绕着成员变量map来工作的,构建了一个一个<K,<K,V>>格式的二级缓存,在动态代理中对应的类型是<类加载器, <接口Class, 代理Class>>,它们都使用了弱引用进行包装,这样在垃圾回收的时候就可以直接回收,减少了堆内存占用。
// 存放已回收弱引用的队列
private final ReferenceQueue<K> refQueue = new ReferenceQueue<>();
// 使用ConcurrentMap实现的二级缓存结构
private final ConcurrentMap<Object, ConcurrentMap<Object, Supplier<V>>> map = new ConcurrentHashMap<>();
// 可以不关注这个,这个是用来标识二级缓存中的value是否存在的,即Supplier是否被回收
private final ConcurrentMap<Supplier<V>, Boolean> reverseMap = new ConcurrentHashMap<>();
// 包装传入的接口class,生成二级缓存的Key
private final BiFunction<K, P, ?> subKeyFactory = new KeyFactory();
// 包装$Proxy0,生成二级缓存的Value
private final BiFunction<K, P, V> valueFactory = new ProxyClassFactory();
WeakCache的get()
回到debug,接着进入get()方法,看看map二级缓存是怎么生成KV的。
public V get(K key, P parameter) {
Objects.requireNonNull(parameter);
// 遍历refQueue,然后将缓存map中对应的失效value删除
expungeStaleEntries();
// 以ClassLoader为key,构建map的一级缓存的Key,是CacheKey对象
Object cacheKey = CacheK.valueOf(key, refQueue);
// 通过Key从map中获取一级缓存的value,即ConcurrentMap
ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);
if (valuesMap == null) {
// 如果Key不存在,就新建一个ConCurrentMap放入map,这里使用的是putIfAbsent
// 如果key已经存在了,就不覆盖并返回里面的value,不存在就返回null并放入Key
// 现在缓存map的结构就是ConCurrentMap<CacheKey, ConCurrentMap<Object, Supplier>>
ConcurrentMap<Object, Supplier<V>> oldValuesMap = map.putIfAbsent(cacheKey, valuesMap = new ConcurrentHashMap<>());
// 如果其他线程已经创建了这个Key并放入就可以复用了
if (oldValuesMap != null) {
valuesMap = oldValuesMap;
}
}
// 生成二级缓存的subKey,现在缓存map的结构就是ConCurrentMap<CacheKey, ConCurrentMap<Key1, Supplier>>
// 看后面的<生成二级缓存Key>!!!
Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
// 根据二级缓存的subKey获取value
Supplier<V> supplier = valuesMap.get(subKey);
Factory factory = null;
// !!!直到完成二级缓存Value的构建才结束,Value是弱引用的$Proxy0.class!!!
while (true) {
// 第一次循环:suppiler肯定是null,因为还没有将放入二级缓存的KV值
// 第二次循环:这里suppiler不为null了!!!进入if
if (supplier != null) {
// 第二次循环:真正生成代理对象,
// 往后翻,看<生成二级缓存Value>,核心!!!!!
// 看完后面回到这里:value就是弱引用后的$Proxy0.class
V value = supplier.get();
if (value != null) {
// 本方法及上部分的最后一行代码,跳转最后的<构建$Proxy对象>
return value;
}
}
// 第一次循环:factory肯定为null,生成二级缓存的Value
if (factory == null) {
factory = new Factory(key, parameter, subKey, valuesMap);
}
// 第一次循环:将subKey和factory作为KV放入二级缓存
if (supplier == null) {
supplier = valuesMap.putIfAbsent(subKey, factory);
if (supplier == null) {
// 第一次循环:赋值之后suppiler就不为空了,记住!!!!!
supplier = factory;
}
}
}
}
}
生成二级缓存Key
在get()中调用subKeyFactory.apply(key, parameter),根据你newProxyInstance()传入的接口Class[]的个数来生成二级缓存的Key,这里我们就传入了一个UserInterface.class,所以就返回了Key1对象。
不论是Key1、Key2还是KeyX,他们都继承了WeakReference,都是包装对象是Class的弱引用类。这里看看Key1的代码。
生成二级缓存Value
在上面的while循环中,第一次循环只是生成了一个空的Factory对象放入了二级缓存的ConcurrentMap中。
在第二次循环中,才开始通过get()方法来真正的构建value。
别回头,接着往下看。
Factory.get()生成弱引用value
「CacheValue」类是一个弱引用,是二级缓存的Value值,包装的是class,在这里就是$Proxy0.class,至于这个类如何生成的,根据下面代码注释一直看完Class文件的生成
public synchronized V get() {
// 检查是否被回收,如果被回收,会继续执行上面的while循环,重新生成Factory
Supplier<V> supplier = valuesMap.get(subKey);
if (supplier != this) {
return null;
}
// 这里的V的类型是Class
V value = null;
// 这行是核心代码,看后面<class文件的生成>,记住这里返回的是Class
value = Objects.requireNonNull(valueFactory.apply(key, parameter));
// 将Class对象包装成弱引用
CacheValue<V> cacheValue = new CacheValue<>(value);
// 回到上面<WeakCache的get()方法>V value = supplier.get();
return value;
}
}
Class文件的生成
包名类名的定义与验证
进入valueFactory.apply(key, parameter)方法,看看class文件是怎么生成的。
private static final String proxyClassNamePrefix = "$Proxy";
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
// 遍历你传入的Class[],我们只传入了UserInterface.class
for (Class<?> intf : interfaces) {
Class<?> interfaceClass = null;
// 获取接口类
interfaceClass = Class.forName(intf.getName(), false, loader);
// 这里就很明确为什么只能传入接口类,不是接口类会报错
if (!interfaceClass.isInterface()) {
throw new IllegalArgumentException(
interfaceClass.getName() + " is not an interface");
}
String proxyPkg = null;
int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
for (Class<?> intf : interfaces) {
int flags = intf.getModifiers();
// 验证接口是否是public,不是public代理类会用接口的package,因为只有在同一包内才能继承
// 我们的UserInterface是public,所以跳过
if (!Modifier.isPublic(flags)) {
accessFlags = Modifier.FINAL;
String name = intf.getName();
int n = name.lastIndexOf('.');
String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
if (proxyPkg == null) {
proxyPkg = pkg;
} else if (!pkg.equals(proxyPkg)) {
throw new IllegalArgumentException(
"non-public interfaces from different packages");
}
}
}
// 如果接口类是public,则用默认的包
if (proxyPkg == null) {
// PROXY_PACKAGE = "com.sun.proxy";
proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
}
// 原子Int,此时num = 0
long num = nextUniqueNumber.getAndIncrement();
// com.sun.proxy.$Proxy0,这里包名和类名就出现了!!!
String proxyName = proxyPkg + proxyClassNamePrefix + num;
// !!!!生成class文件,查看后面<class文件写入本地> 核心!!!!
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);
// !!!看完下面再回来看这行!!!!
// 获取了字节数组之后,获取了class的二进制流将类加载到了JVM中
// 并且返回了$Proxy0.class,返回给Factory.get()来包装
return defineClass0(loader, proxyName,proxyClassFile, 0, proxyClassFile.length);
}
}
}
defineClass0()是Proxy类自定义的类加载的native方法,会获取class文件的二进制流加载到JVM中,以获取对应的Class对象,这一块可以参考JVM类加载器。
class文件写入本地
generateProxyClass()方法会将class二进制文件写入本地目录,并返回class文件的二进制流,使用你传入的类加载器加载,「这里你知道类加载器的作用了么」。
public static byte[] generateProxyClass(final String name,
Class[] interfaces)
{
ProxyGenerator gen = new ProxyGenerator(name, interfaces);
// 生成class文件的二进制,查看后面<生成class文件二进制>
final byte[] classFile = gen.generateClassFile();
// 将class文件写入本地
if (saveGeneratedFiles) {
java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction<Void>() {
public Void run() {
try {
FileOutputStream file =
new FileOutputStream(dotToSlash(name) + ".class");
file.write(classFile);
file.close();
return null;
} catch (IOException e) {
throw new InternalError(
"I/O exception saving generated file: " + e);
}
}
});
}
// 返回$Proxy0.class字节数组,回到上面<class文件生成>
return classFile;
}
生成class文件二进制流
generateClassFile()生成class文件,并存放到字节数组,「可以顺便学一下class结构,这里也体现了你传入的class[]的作用」。
private byte[] generateClassFile() {
// 将hashcode、equals、toString是三个方法放入代理类中
addProxyMethod(hashCodeMethod, Object.class);
addProxyMethod(equalsMethod, Object.class);
addProxyMethod(toStringMethod, Object.class);
for (int i = 0; i < interfaces.length; i++) {
Method[] methods = interfaces[i].getMethods();
for (int j = 0; j < methods.length; j++) {
// 将接口类的方法放入新建的代理类中,这里就是sayHello()
addProxyMethod(methods[j], interfaces[i]);
}
}
for (List<ProxyMethod> sigmethods : proxyMethods.values()) {
checkReturnTypes(sigmethods);
}
// 给代理类增加构造方法
methods.add(generateConstructor());
for (List<ProxyMethod> sigmethods : proxyMethods.values()) {
for (ProxyMethod pm : sigmethods) {
// 将上面的四个方法都封装成Method类型成员变量
fields.add(new FieldInfo(pm.methodFieldName,
"Ljava/lang/reflect/Method;",
ACC_PRIVATE | ACC_STATIC));
// generate code for proxy method and add it
methods.add(pm.generateMethod());
}
}
// static静态块构造
methods.add(generateStaticInitializer());
cp.getClass(dotToSlash(className));
cp.getClass(superclassName);
for (int i = 0; i < interfaces.length; i++) {
cp.getClass(dotToSlash(interfaces[i].getName()));
}
cp.setReadOnly();
ByteArrayOutputStream bout = new ByteArrayOutputStream();
DataOutputStream dout = new DataOutputStream(bout);
// !!!核心点来了!这里就开始构建class文件了,以下都是class的结构,只写一部分
try {
// u4 magic,class文件的魔数,确认是否为一个能被JVM接受的class
dout.writeInt(0xCAFEBABE);
// u2 minor_version,0
dout.writeShort(CLASSFILE_MINOR_VERSION);
// u2 major_version,主版本号,Java8对应的是52;
dout.writeShort(CLASSFILE_MAJOR_VERSION);
// 常量池
cp.write(dout);
// 其他结构,可参考class文件结构
dout.writeShort(ACC_PUBLIC | ACC_FINAL | ACC_SUPER);
dout.writeShort(cp.getClass(dotToSlash(className)));
dout.writeShort(cp.getClass(superclassName));
dout.writeShort(interfaces.length);
for (int i = 0; i < interfaces.length; i++) {
dout.writeShort(cp.getClass(
dotToSlash(interfaces[i].getName())));
}
dout.writeShort(fields.size());
for (FieldInfo f : fields) {
f.write(dout);
}
dout.writeShort(methods.size());
for (MethodInfo m : methods) {
m.write(dout);
}
dout.writeShort(0);
} catch (IOException e) {
throw new InternalError("unexpected I/O Exception", e);
}
// 将class文件字节数组返回
return bout.toByteArray();
}
构建$Proxy对象
newProxyInstance()上半部分经过上面层层代码调用,获取了$Proxy0.class,接下来看下部分代码:
cl就是上面获取的Proxy0.class,h就是上面传入的userHandler,被当做构造参数来创建$Proxy0对象。然后获取这个动态代理对象,调用sayHello()方法,相当于调用了UserHandler的invoke(),「这里就是UserHandler的作用」!
$Proxy.class文件
我们开启了将代理class写到本地目录的功能,在项目下的com/sum/proxy目录下找到了$Proxy0的class文件。
「看一下反编译的class」
package com.sun.proxy;
import com.test.proxy.UserInterface;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class $Proxy0 extends Proxy implements UserInterface {
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m0;
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final void sayHello() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m3 = Class.forName("com.test.proxy.UserInterface").getMethod("sayHello");
m2 = Class.forName("java.lang.Object").getMethod("toString");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
结语
上面就是动态代理源码的调试过程,与之前的猜想的代理类的生成过程比较,动态代理是直接生成class文件,省去了java文件和编译这一块。
刚开始看可能比较绕,跟着注释及跳转指引,耐心多看两遍就明白了。动态代理涉及的知识点比较多,我自己看的时候,在WeakCache这一块纠结了一阵,其实把它当成一个两层的map对待即可,只不过里面所有的KV都被弱引用包装。
---
写的都是日常工作中的亲身实践,处于自己的角度从0写到1,保证能够真正让大家看懂。
文章会在公众号 [**入门到放弃之路**] 首发,期待你的关注。
![公众号](https://img-blog.csdnimg.cn/2020121714585714.jpg)
从源码角度学习Java动态代理的更多相关文章
- 从源码角度理解Java设计模式——装饰者模式
一.饰器者模式介绍 装饰者模式定义:在不改变原有对象的基础上附加功能,相比生成子类更灵活. 适用场景:动态的给一个对象添加或者撤销功能. 优点:可以不改变原有对象的情况下动态扩展功能,可以使扩展的多个 ...
- JVM插码之四:Java动态代理机制的对比(JDK 和CGLIB,Javassist,ASM)
一.class文件简介及加载 Java编译器编译好Java文件之后,产生.class 文件在磁盘中.这种class文件是二进制文件,内容是只有JVM虚拟机能够识别的机器码.JVM虚拟机读取字节码文件, ...
- ASM字节码框架学习之动态代理
ASM字节码操纵框架,可以直接以二进制的形式来来修改已经存在的类或者创建新的类.ASM封装了操作字节码的大部分细节,并提供了非常方便的接口来对字节码进行操作.ASM框架是全功能的,使用ASM字节码框架 ...
- 透过字节码生成审视Java动态代理运作机制
对于动态代理我想应该大家都不陌生,就是可以动态去代理实现某个接口的类来干一些我们自己想要的功能,但是在字节码层面它的表现是如何的呢?既然目前刚好在研究字节码相关的东东,有必要对其从字节码角度来审视一下 ...
- 设计模式学习——JAVA动态代理原理分析
一.JDK动态代理执行过程 上一篇我们讲了JDK动态代理的简单使用,今天我们就来研究一下它的原理. 首先我们回忆下上一篇的代码: public class Main { public static v ...
- 【一起学源码-微服务】Feign 源码二:Feign动态代理构造过程
前言 前情回顾 上一讲主要看了@EnableFeignClients中的registerBeanDefinitions()方法,这里面主要是 将EnableFeignClients注解对应的配置属性注 ...
- 从JDK源码角度看java并发的公平性
JAVA为简化开发者开发提供了很多并发的工具,包括各种同步器,有了JDK我们只要学会简单使用类API即可.但这并不意味着不需要探索其具体的实现机制,本文从JDK源码角度简单讲讲并发时线程竞争的公平性. ...
- 从JDK源码角度看java并发的原子性如何保证
JDK源码中,在研究AQS框架时,会发现很多地方都使用了CAS操作,在并发实现中CAS操作必须具备原子性,而且是硬件级别的原子性,java被隔离在硬件之上,明显力不从心,这时为了能直接操作操作系统层面 ...
- 从JDK源码角度看java并发线程的中断
线程的定义给我们提供了并发执行多个任务的方式,大多数情况下我们会让每个任务都自行执行结束,这样能保证事务的一致性,但是有时我们希望在任务执行中取消任务,使线程停止.在java中要让线程安全.快速.可靠 ...
随机推荐
- Mac中的格式转换如何用读写工具Tuxera NTFS完成
Tuxera NTFS for Mac是一款专门为Mac用户提供的NTFS驱动软件,它不仅可以进行磁盘文件的访问.编辑.传输和存储,还可以对硬盘进行维修检查以及修复. 今天小编就给大家简单介绍一下Tu ...
- 自定义 JSTLFunction
复习常用JSTL Function为什么需要自定义Function如何自定义Function,例子:1.在独立的项目中(也可以在web项目中)的类中(比如Functions)编写一个static方法: ...
- pycharm2020激活破解和汉化
一:破解补丁和程序下载:链接:https://pan.baidu.com/s/1u-aZrKMmfRBlQHtcivUt8Q 提取码:tvko 二:破解步骤: 1.安装下载的pycharm202 ...
- (1)Hello World
语出<论语·卫灵公>:子贡问为仁.子曰:"工欲善其事,必先利其器.居是邦也,事其大夫之贤者,友其士之仁者." 2020年11月终于下定决心开始 Visual C++ 的 ...
- nginx proxy_pass参数配置带不带‘/’
1. proxy_pass 配置的url后面,不加'/',那么重定向后,追加location后的路径.比如server_name: 10.9.11.225location /proxy1/{proxy ...
- 7-1 Hashing
The task of this problem is simple: insert a sequence of distinct positive integers into a hash tabl ...
- [Android systrace系列] systrace的信息从哪里来
-------------------------------------------------------------- 这篇文章的小目标: 1. systrace是怎么抓出来的 2. 这些信息的 ...
- 【NOIP2011模拟11.1】钓鱼
钓鱼 题目 Description 我们把钓鱼的过程放在坐标系里来考虑.图中蓝色的点为船,初始时它的坐标记为(Ax,y).河深为y,河宽为x.某个时刻会从左边界或右边界游出来一条鱼(左边的往右边游,右 ...
- 2017 Mid Central Regional G.Hopscotch (组合计数)
这道题有点意思,给出点(N,N),你在原点处向目标点走,每次只能向x和y两个方向走路,每次xy两个方向的步幅分别不能小于dx和dy,问走到终点的方案数,答案对1e9 + 7取模 这道题最直接的想法就是 ...
- 第10.1节 Python的模块及模块导入
一. 什么是模块 Python中的模块即单个的Python代码文件,为什么称为模块呢?这是因为在Python中,每个独立的Python文件都可以作为被其他代码导入的模块使用,导入的模块有自己的名字空间 ...