当前位置: 首页 > news >正文

河南县wap网站建设公司品牌网络推广方式

河南县wap网站建设公司,品牌网络推广方式,911制品厂麻花,阿里巴巴出口贸易平台一、背景 最近编码过程中遇到了一个非常奇怪的问题#xff0c;基于单例对象的同步代码块似乎失效了#xff0c;百思不得其姐。 下面给出模拟过程和最终的结论。 二、场景描述和模拟 2.1 现象描述 Database实现单例#xff0c;在 init 方法中使用同步代码块来保证 data不…一、背景 最近编码过程中遇到了一个非常奇怪的问题基于单例对象的同步代码块似乎失效了百思不得其姐。 下面给出模拟过程和最终的结论。 二、场景描述和模拟 2.1 现象描述 Database实现单例在 init 方法中使用同步代码块来保证 data不会被重复赋值因此打印语句不应该重复打印。 public class Database {private static final Database dbObject new Database();private volatile String data;private Database() {}public static Database getInstance() {return dbObject;}public void init() {synchronized (this) {if (data null) {data test;System.out.println(同步代码块中赋值。 );}}} }在构造 MyClass 的时候会自动获取 Database 单例并执行 init 方法。 public class MyClass {private Database database;public MyClass() {database Database.getInstance();database.init();}public Database getDatabase() {return database;} } 在业务代码中会自动创建 MyClass 对象因此会多次获取 Database 单例并执行 init 方法。 由于是单例 synchronized(this)就可以保证 init 中的打印语句不会多次执行但是从日志看最终执行了两次。 2.2 场景模拟 最终发现实际上项目中自定义了类加载器导致的。 自定义该类加载器的目的是为了避免类冲突保证该框架使用的某个 Jar 包固定在特定版本又不影响用户使用其他版本。 package org.example.classloader;import java.io.File; import java.io.FileInputStream; import java.io.IOException;public class MyClassLoader extends ClassLoader {Overridepublic String getName() {return MyClassLoader;}// 类文件的根目录private String rootDir;// 构造方法传入类文件的根目录public MyClassLoader(String rootDir) {this.rootDir rootDir;}// 重写 loadClass 方法打破双亲加载机制Overrideprotected Class? loadClass(String name, boolean resolve) throws ClassNotFoundException {// 自己先加载Class? clazz null;try {clazz findClass(name);} catch (ClassNotFoundException e) {// 自己加载器加载失败不做处理}// 如果自己加载器加载成功直接返回if (clazz ! null) {return clazz;}// 如果自己加载器加载失败调用父加载器的 findClass 方法加载类return super.loadClass(name, resolve);}// 重写 findClass 方法实现自己的类加载逻辑Overrideprotected Class? findClass(String name) throws ClassNotFoundException {// 根据类名获取类文件的路径String classPath rootDir File.separator name.replace(., File.separator) .class;// 读取类文件的字节码byte[] classBytes getClassBytes(classPath);// 如果字节码为空抛出异常if (classBytes null) {throw new ClassNotFoundException(Cannot find class: name);}// 调用 defineClass 方法将字节码转换为 Class 对象return defineClass(name, classBytes, 0, classBytes.length);}// 读取类文件的字节码private byte[] getClassBytes(String classPath) {// 创建文件对象File file new File(classPath);// 如果文件不存在返回空if (!file.exists()) {return null;}// 创建字节数组长度为文件大小byte[] bytes new byte[(int) file.length()];// 创建文件输入流try (FileInputStream fis new FileInputStream(file)) {// 读取文件内容到字节数组fis.read(bytes);} catch (IOException e) {// 发生异常返回空return null;}// 返回字节数组return bytes;} }模拟代码如下 import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method;public class ClassLoaderDemo {public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {// 第一次执行MyClass myClass new MyClass();System.out.println(第1次加载 myClass.getDatabase());// 第二次执行MyClassLoader myClassLoader new MyClassLoader(~/IdeaProjects/test/target/classes/);Class? myClazz myClassLoader.loadClass(org.example.classloader.MyClass, false);Object obj myClazz.newInstance();Method getDatabase myClazz.getMethod(getDatabase);System.out.println(第2次加载 getDatabase.invoke(obj));} }为了更好地排查问题我们在打印语句中打印类加载器 public class Database {private static final Database dbObject new Database();private volatile String data;private Database() {}public static Database getInstance() {return dbObject;}public void init() {synchronized (this) {if (this.data null) {data test;System.out.println(同步代码块中赋值。类加载器 this.getClass().getClassLoader().getName());}}} }实际没有那么明显比如第一个MyClass部分在 Spring 初始化方法中自动创建。第二个 MyClass则是在运行时从 jar 包中动态加载时自动创建的。 控制台输出 同步代码块中赋值。类加载器app 第1次加载org.example.classloader.Database3f99bd52 同步代码块中赋值。类加载器MyClassLoader 第2次加载org.example.classloader.Database19469ea2我们发现我们实际上分别使用了两个类加载器加载同一个类而其中一个类加载器违背了双亲加载机制导致两个类并不相同。 因此原因就找到了我们分别使用了两个类加载器去加载同一个类虽然采用单例的机制实际上并非同一个对象并不能保证同步代码块正确运行。 最终评估第 2 部分不需要让自定义类加载器来加载将该部分逻辑从自定义类加载器的条件中移除问题就解决了。 假如上面的例子我们修改父类优先加载 Overrideprotected Class? loadClass(String name, boolean resolve) throws ClassNotFoundException {// 先委托父类加载器加载类Class? clazz null;try {clazz super.loadClass(name, resolve);} catch (ClassNotFoundException e) {// 父类加载器加载失败不做处理}// 如果父类加载器加载成功直接返回if (clazz ! null) {return clazz;}// 如果父类加载器加载失败调用自己的 findClass 方法加载类return findClass(name);}发现单例“生效” init 也不会打印两次。 同步代码块中赋值。类加载器app 第1次加载org.example.classloader.Database3f99bd52 第2次加载org.example.classloader.Database3f99bd52三、相关知识 3.1 类加载机制 3.1.1 双亲加载机制 Java类加载器有以下几种 引导类加载器Bootstrap ClassLoader它是用原生代码实现的不继承自java.lang.ClassLoader负责加载Java的核心库如java.lang.*以及jre/lib文件夹下的jar包和class文件。扩展类加载器ExtClassLoader它继承自java.lang.ClassLoader负责加载Java的扩展库如jre/lib/ext文件夹下的jar包和class文件。应用类加载器AppClassLoader它也继承自java.lang.ClassLoader负责加载用户的类路径classpath下的jar包和class文件。自定义类加载器User-Defined ClassLoader它们是由开发人员自定义的类加载器继承自java.lang.ClassLoader可以实现一些特殊的需求如动态加载热部署加密解密等。 这些类加载器之间的关系是一个父子层次结构除了引导类加载器外每个类加载器都有一个父类加载器。当一个类加载器收到一个类加载请求时它会先委托给它的父类加载器如果父类加载器无法加载它才会尝试自己加载。这样可以保证核心类库的优先加载避免被恶意替换。 本文所列的场景就是违背双亲加载机制的一个案例。 3.1.2 双亲类加载机制的目的 可以避免类的重复加载确保一个类的全局唯一性。因为双亲委派机制是向上委托加载的所以当父类加载器已经加载了该类时就没有必要子类加载器再加载一次。可以保护程序安全防止核心API被随意篡改。因为 Java 的核心API都是通过引导类加载器进行加载的如果别人通过定义同样路径的类比如 java.lang.Integer类加载器通过向上委派会发现引导类加载器已经加载了jdk 的Integer类而不会加载自定义的 Integer类。这样就阻止了对核心API的恶意修改。 3.1.3 遵循双亲加载机制的自定义类加载器的示例 如果想自定义遵循双亲加载机制的类加载器需要以下三个步骤 继承 java.lang.ClassLoader类实现一个自己的类加载器。重写 findClass方法实现自己的类查找逻辑。例如从指定的路径或者网络上加载类的字节码然后调用 defineClass方法将字节码转换为 Class 对象。重写loadClass方法遵循类加载的顺序或方式。例如优先使用父加载器加载如果加载不到再交使用本类加载器加载。 具体代码参考上文中的 MyClassLoader loadClass 部分如下 Overrideprotected Class? loadClass(String name, boolean resolve) throws ClassNotFoundException {// 先委托父类加载器加载类Class? clazz null;try {clazz super.loadClass(name, resolve);} catch (ClassNotFoundException e) {// 父类加载器加载失败不做处理}// 如果父类加载器加载成功直接返回if (clazz ! null) {return clazz;}// 如果父类加载器加载失败调用自己的 findClass 方法加载类return findClass(name);}3.2 违背双亲加载机制 3.2.1 违背双亲加载机制的场景 违背双亲加载机制的情况有以下几种 为了避免类冲突每个web应用项目中都有自己的类加载器可以加载自己的类库而不受其他项目的影响。例如Tomcat中的 WebAppClassLoader 就会优先加载自己的类如果加载不到再交给父类加载器走双亲委派机制。为了实现一些特殊的需求如动态加载热部署加密解密等可以自定义类加载器覆盖 loadClass方法改变类加载的顺序或方式。例如OSGi 框架就是通过自定义类加载器实现了模块化和动态更新的功能。为了支持一些服务提供者接口SPI如JDBCJNDI等可以使用线程上下文类加载器Thread Context ClassLoader让启动类加载器加载的类可以使用应用类加载器加载的类。例如java.sql.DriverManager类是由启动类加载器加载的但是它需要加载不同厂商提供的 java.sql.Driver接口的实现类这些实现类是由应用类加载器加载的所以 DriverManager类就使用了线程上下文类加载器打破了双亲委派机制。 本文的例子的场景就是为了避免类冲突而自定义类加载器。 3.2.2 违背双亲加载机制的类加载器 如果想自定义违背双亲加载机制的类加载器需要以下三个步骤 继承 java.lang.ClassLoader类实现一个自己的类加载器。重写 findClass方法实现自己的类查找逻辑。例如从指定的路径或者网络上加载类的字节码然后调用 defineClass方法将字节码转换为 Class 对象。重写loadClass方法改变类加载的顺序或方式。例如优先加载自己的类如果加载不到再交给父类加载器走双亲委派机制。 具体代码参考上文中的 MyClassLoader loadClass 部分如下 Overrideprotected Class? loadClass(String name, boolean resolve) throws ClassNotFoundException {// 自己先加载Class? clazz null;try {clazz findClass(name);} catch (ClassNotFoundException e) {// 自己加载器加载失败不做处理}// 如果自己加载器加载成功直接返回if (clazz ! null) {return clazz;}// 如果自己加载器加载失败调用父加载器的 findClass 方法加载类return super.loadClass(name, resolve);}四、总结 大家在维护一些存在自定义类加载器的框架时一定要特别小心。当发生一些奇奇怪怪的问题时要主动往这个方向考虑。 另外就像我一直说过的“每一个坑都是彻底掌握某个知识的绝佳机会”当我们日常开发中遇到一些坑的时候一定要主动掌握相关原理甚至总结分享。这样对某个知识点的理解和掌握就更加透彻。 创作不易如果本文对你有帮助欢迎点赞、收藏加关注你的支持和鼓励是我创作的最大动力。
http://www.hkea.cn/news/14539359/

相关文章:

  • 5网站建站做盗版电影网站吗
  • 美食网站源代码郑州集团网站建设哪家好
  • 网站推广实施计划wordpress学习教程
  • 江苏网站建设费用他达拉非说明书
  • 小学生家长网站建设需求wordpress实例网址
  • 设计素材网站新媒体营销策略
  • 网站经常做封面的那些番号建网站定制
  • 网站后台上传图片显示运行错误为什么沈阳制作网站企业
  • 安徽省美好乡村建设网站电商网站设计模板
  • 可以做软件的网站有哪些ui界面图标
  • 网站开发语言排行中国商标买卖网站
  • 大连网站快速排名龙南黄页全部电话
  • 天津网站建设noakj乐清网络科技有限公司
  • 郑州 网站建设 东区wordpress主题vieu
  • 如何提高网站点击率怎么做网站前台如何刷新
  • 网站开发建设流程南昌模板建站代理
  • 网站建设的岗位要求深圳网站免费制作
  • 南昌商城网站建设公司广告设计公司实习周记
  • 网站建设设计报告青岛微网站制作
  • wordpress主题详情更改版权如何做360网站优化
  • 网站建设法规网站备案信息查询系统
  • 学校网站建设制作方案互联网保险发展现状分析
  • 做分色找工作网站网站建设出错1004
  • 网站空间权限十堰网站建设是什么
  • 加强学校网站建设的要求有什么平台可以推广
  • 网站正在建设中的网页怎么做网页制作怎么建站点
  • 怎么查询网站是否收录开发app软件需要多少钱
  • 电子商务网站的功能上海市区网站设计制作公司
  • html网站模板源码谷歌网站的主要内容
  • 凡科网免费做网站免费舆情网站下载大全最新版