ホームページ > Java > &#&チュートリアル > Java のパフォーマンス関連の設計パターンについて話しましょう

Java のパフォーマンス関連の設計パターンについて話しましょう

WBOY
リリース: 2022-06-08 11:56:41
転載
2336 人が閲覧しました

この記事では、java に関する関連知識を提供します。主にパフォーマンスに関連する設計パターンを紹介します。ほとんどの設計パターンはコードを整理する方法にすぎず、パフォーマンスに関連するのは設計パターンの一部のみです。 、プロキシ モード、シングルトン モード、フライウェイト モード、プロトタイプ モードなどが含まれます。一緒に見てみましょう。皆さんの参考になれば幸いです。

Java のパフォーマンス関連の設計パターンについて話しましょう

推奨学習: 「java ビデオ チュートリアル

コードの構造は、コードの全体的なパフォーマンスに重要な影響を与えます。応用。優れた構造を持つコードは、多くの潜在的なパフォーマンスの問題を回避し、コードのスケーラビリティに大きな役割を果たします。明確な構造と明確な層を持つコードは、システム内のボトルネックを見つけて特別な最適化を実行するのにも役立ちます。

デザイン パターンは一般的な開発テクニックを要約したもので、プログラマーがより専門的かつ便利な方法で問題を伝達できるようにします。

実際、ほとんどのデザイン パターンはプログラムのパフォーマンスを向上させるものではなく、コードを整理する単なる方法です。この記事では、プロキシ モード、シングルトン モード、フライウェイト モード、プロトタイプ モードなど、パフォーマンスに関連するいくつかの設計パターンを 1 つずつ例を挙げて説明します。

プロキシ モード

プロキシ モード (プロキシ) では、プロキシ クラスを通じてオブジェクトへのアクセスを制御できます。

Java で動的プロキシを実装するには、主に 2 つのモードがあります。1 つは JDK を使用する方法、もう 1 つは CGLib を使用する方法です。このうち、JDK メソッドはインターフェイス指向であり、主な関連クラスは InvocationHandler と Proxy、CGLib は通常のクラスをプロキシでき、主な関連クラスは MethodInterceptor と Enhancer です。

このナレッジポイントに関するインタビューの頻度は非常に高いです。

CGLib

package cn.wja.proxy.cglibproxy;import org.springframework.cglib.proxy.MethodInterceptor;import org.springframework.cglib.proxy.MethodProxy;import java.lang.reflect.Method;public class CglibInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        return methodProxy.invokeSuper(o, objects);
    }}
ログイン後にコピー
package cn.wja.proxy.cglibproxy;import cn.wja.proxy.jdkproxy.Target;import cn.wja.proxy.jdkproxy.TargetImpl;import org.springframework.cglib.proxy.Enhancer;public class CglibFactory {

    public static Target newInstance() {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(TargetImpl.class);
        enhancer.setCallback(new CglibInterceptor());
        return (Target) enhancer.create();
    }

    public static void main(String[] args) {
        Target target = newInstance();
        System.out.println(target.targetMetod(4));
    }}
ログイン後にコピー

JDK

package cn.wja.proxy.jdkproxy;public interface Target {
    int targetMethod(int i);}
ログイン後にコピー
package cn.wja.proxy.jdkproxy;public class TargetImpl implements Target {
    @Override
    public int targetMethod(int i) {
        return i * i;
    }}
ログイン後にコピー
package cn.wja.proxy.jdkproxy;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;public class JdkInvocationHandler implements InvocationHandler {
    private Target target;

    public JdkInvocationHandler(Target target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //before
        Object object = method.invoke(target, args);
        //after
        return object;
    }}
ログイン後にコピー
package cn.wja.proxy.jdkproxy;import java.lang.reflect.Proxy;public class JdkFactory {
    public static Target newInstance(Target target) {
        Object object = Proxy.newProxyInstance(JdkInvocationHandler.class.getClassLoader(),
                new Class>[]{Target.class},
                new JdkInvocationHandler(target));
        return Target.class.cast(object);
    }

    public static void main(String[] args) {
        Target t = new TargetImpl();
        Target target = newInstance(t);
        System.out.println(target.targetMethod(4));
    }}
ログイン後にコピー

以下は、JDK モードと CGLib モードのプロキシ速度の JMH テスト結果です:

#ProxyBenchmark.cglibthrpt1078499.580±1771.148ops/msProxyBenchmark.jdkthrpt1088948.858±814.360ops/ ms
ベンチマーク モード Cnt スコア エラー 単位
現在使用している JDK のバージョンは 1.8 ですが、CGLib の速度はそれほど速くないことがわかります (10 倍高速であるとの噂)。が鈍くなるにもほどがあります。

プロキシの作成速度を見てみましょう。結果は次のとおりです。プロキシ クラスの初期化に関して、JDK のスループットは CGLib の 2 倍であることがわかります。

ベンチマークモードCntスコアエラー単位##ProxyCreateBenchmark.cglib#ops/msthrpt##15612.467± 268.362ops/ms

Spring动态代理

Spring 广泛使用了代理模式,它使用 CGLIB 对 Java 的字节码进行了增强。在复杂的项目中,会有非常多的 AOP 代码,比如权限、日志等切面。在方便了编码的同时,AOP 也给不熟悉项目代码的同学带来了很多困扰。

下面我将分析一个使用 arthas 找到动态代理慢逻辑的具体原因,这种方式在复杂项目中,非常有效,你不需要熟悉项目的代码,就可以定位到性能瓶颈点。

首先,我们创建一个最简单的 Bean。

package cn.wja.spring;import org.springframework.stereotype.Component;@Componentpublic class ABean {
    public void method() {
        System.out.println("****ABean method*******************");
    }}
ログイン後にコピー

然后,我们使用 Aspect 注解,完成切面的书写,在前置方法里,我们让线程 sleep 了 1 秒钟。

package cn.wja.spring;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Before;import org.aspectj.lang.annotation.Pointcut;import org.springframework.stereotype.Component;import java.util.concurrent.TimeUnit;@Aspect@Componentpublic class MyAspect {
    @Pointcut("execution(* cn.wja.spring.ABean.*(..)))")
    public void pointcut() {
    }

    @Before("pointcut()")
    public void before() {
        System.out.println("before");
        try {
            Thread.sleep(TimeUnit.SECONDS.toMillis(1));
        } catch (InterruptedException e) {
            throw new IllegalStateException();
        }
    }}
ログイン後にコピー

创建一个启动类,当访问 /aop 链接时,将会输出 Bean 的类名称,以及它的耗时。

package cn.wja.spring;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.scheduling.annotation.EnableAsync;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.ResponseBody;@SpringBootApplication@EnableAsync@Controllerpublic class App {
    public static void main(String[] args) {
        SpringApplication.run(App.class, args);
    }
    @Autowired
    private ABean aBean;

    @ResponseBody
    @GetMapping("/aop")
    public String aop() {
        long begin = System.currentTimeMillis();
        aBean.method();
        long cost = System.currentTimeMillis() - begin;
        String cls = aBean.getClass().toString();
        return cls + " | " + cost;
    }}
ログイン後にコピー

访问结果如下,可以看到 AOP 代理已经生效,内存里的 Bean 对象,已经变成了EnhancerBySpringCGLIB 类型,调用方法 method,耗时达到了1005ms。

Java のパフォーマンス関連の設計パターンについて話しましょう
下面使用 arthas 分析这个执行过程,找出耗时最高的 AOP 方法。启动 arthas 后,可以从列表中看到我们的应用程序,在这里,输入 1 进入分析界面。

Java のパフォーマンス関連の設計パターンについて話しましょう
在终端输入 trace 命令,然后访问 /aop 接口,终端将打印出一些 debug 信息,可以发现耗时操作就是 Spring 的代理类。

trace cn.wja.spring.ABean method
ログイン後にコピー

Java のパフォーマンス関連の設計パターンについて話しましょう

单例模式

Spring 在创建组件的时候,可以通过 scope 注解指定它的作用域,用来标示这是一个prototype(多例)还是 singleton(单例)。

当指定为单例时(默认行为),在 Spring 容器中,组件有且只有一份,当你注入相关组件的时候,获取的组件实例也是同一份。

如果是普通的单例类,我们通常将单例的构造方法设置成私有的,单例有懒汉加载和饿汉加载模式。

饿汉模式

了解 JVM 类加载机制的同学都知道,一个类从加载到初始化,要经历 5 个步骤:加载、验证、准备、解析、初始化。
Java のパフォーマンス関連の設計パターンについて話しましょう
其中,static 字段和 static 代码块,是属于类的,在类加载的初始化阶段就已经被执行。它在字节码中对应的是 方法,属于类的(构造方法)。因为类的初始化只有一次,所以它就能够保证这个加载动作是线程安全的。

根据以上原理,只要把单例的初始化动作,放在方法里,就能够实现饿汉模式。

private static Singleton instace = new Singleton();
ログイン後にコピー

理论上来说,饿汉模式它会造成资源的浪费,可能生成一些永远不会用到的对象,因此很多教程不建议用。但实际上来说,这存粹是脱裤子放屁,如果你真的永远用不到这个对象,你为何要创建这个类,写一个单例模式? 我觉得对于普通项目来说,饿汉模式就完全足够了。

饱汉模式

而对象初始化就不一样了。通常,我们在 new 一个新对象的时候,都会调用它的构造方法,就是,用来初始化对象的属性。由于在同一时刻,多个线程可以同时调用函数,我们就需要使用 synchronized 关键字对生成过程进行同步。

package cn.wja.singleton;public class DoubleCheckSingleton {
    private volatile static DoubleCheckSingleton instance = null;
    private DoubleCheckSingleton() {
    }

    public static DoubleCheckSingleton getInstance() {
        if (null == instance) {
            synchronized (DoubleCheckSingleton.class) {
                if (null == instance) {
                    instance = new DoubleCheckSingleton();
                }
            }
        }
        return instance;
    }}
ログイン後にコピー

如上面是 double check 的关键代码,我们介绍一下四个关键点:

  • 第一次检查,当 instance 为 null 的时候,进入对象实例化逻辑,否则直接返回。
  • 加同步锁,这里是类锁。
  • 第二次检查才是关键。如果不加这次判空动作,可能会有多个线程进入同步代码块,进而生成多个实例。
  • 最后一个关键点是 volatile 关键字。在一些低版本的 Java 里,由于指令重排的缘故,可能会导致单例被 new 出来后,还没来得及执行构造函数,就被其他线程使用。 这个关键字,可以阻止字节码指令的重排序,在写 double check 代码时,习惯性会加上 volatile。

可以看到,double check 的写法繁杂,注意点很多,它现在其实是一种反模式,已经不推荐使用了,我也不推荐你用在自己的代码里。但它能够考察面试者对并发的理解,所以这个问题经常被问到。

推荐使用 enum 实现懒加载的单例,《Effective Java》这本书也同样推荐了该方式。代码片段如下:

package cn.wja.singleton;public class EnumSingleton {
    private EnumSingleton() {
    }

    public static EnumSingleton getInstance() {
        return Holder.HOLDER.instance;
    }

    private enum Holder {
        HOLDER;
        private final EnumSingleton instance;
        Holder() {
            instance = new EnumSingleton();
        }
    }

    public static void main(String[] args) {
        System.out.println(getInstance());
    }}
ログイン後にコピー

如果要借助spring框架那就更简单了:

package cn.wja.singleton;import org.springframework.context.annotation.Scope;import org.springframework.stereotype.Component;@Component@Scope("singleton")public class SpringBean {
    //具体内容}
ログイン後にコピー

享元模式

享元模式(Flyweight)专门针对性能优化的设计模式,它通过共享技术,最大限度地复用对象。享元模式一般会使用唯一的标识码进行判断,然后返回对应的对象,使用 HashMap 一类的集合存储非常合适。

上面的描述,我们非常熟悉,因为本专栏的之前的博文中,我们就能看到很多享元模式的身影,比如博文 浅谈Java中的池化技术 里的池化对象和博文 如何处理Java中的大对象 里的对象复用等。

案例:Integer

在Java中,我们常见的Integer,为了提升效率,在创建[1,127]范围内的对象时也用了享元模式。通过下面的测试代码可以验证。

@Testpublic void myTest() throws Exception{
    Integer a=1;
    Integer b=1;
    System.out.println(a == b ? "a b同一个对象" : "a b不是同一个对象");

    Integer c=128;
    Integer d=128;
    System.out.println(c == d ? "c d同一个对象" : "c d不是同一个对象");}
ログイン後にコピー

Java のパフォーマンス関連の設計パターンについて話しましょう

多视角看问题

设计模式对这我们平常的编码进行了抽象,从不同的角度去解释设计模式,都会找到设计思想的一些共通点。比如,单例模式就是享元模式的一种特殊情况,它通过共享单个实例,达到对象的复用。

值得一提的是,同样的代码,不同的解释,会产生不同的效果。比如下面这段代码:

Map<string> strategys = new HashMap(); strategys.put("a",new AStrategy()); strategys.put("b",new BStrategy());</string>
ログイン後にコピー

如果我们从对象复用的角度来说,它就是享元模式;如果我们从对象的功能角度来说,那它就是策略模式。所以大家在讨论设计模式的时候,一定要注意上下文语境的这些差别。

原型模式

原型模式(Prototype)比较类似于复制粘贴的思想,它可以首先创建一个实例,然后通过这个实例进行新对象的创建。在 Java 中,最典型的就是 Object 类的 clone 方法。

但编码中这个方法很少用,我们上面在代理模式提到的 prototype,并不是通过 clone 实现的,而是使用了更复杂的反射技术。

一个比较重要的原因就是 clone 如果只拷贝当前层次的对象,实现的只是浅拷贝。在现实情况下,对象往往会非常复杂,想要实现深拷贝的话,需要在 clone 方法里做大量的编码,远远不如调用 new 方法方便。

实现深拷贝,还有序列化等手段,比如实现 Serializable 接口,或者把对象转化成 JSON。

所以,在现实情况下,原型模式变成了一种思想,而不是加快对象创建速度的工具。

推荐学习:《java视频教程

thrpt 10 7281.487 ± 1339.779 ##ProxyCreateBenchmark.jdk
10

以上がJava のパフォーマンス関連の設計パターンについて話しましょうの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

関連ラベル:
ソース:csdn.net
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート