亚洲最大看欧美片,亚洲图揄拍自拍另类图片,欧美精品v国产精品v呦,日本在线精品视频免费

  • 站長(zhǎng)資訊網(wǎng)
    最全最豐富的資訊網(wǎng)站

    一起來(lái)聊聊與Java中性能相關(guān)的設(shè)計(jì)模式

    本篇文章給大家?guī)?lái)了關(guān)于java的相關(guān)知識(shí),其中主要介紹了關(guān)于與性能相關(guān)的設(shè)計(jì)模式,大多數(shù)設(shè)計(jì)模式只是代碼的一種組織方式,只有部分設(shè)計(jì)模式與性能相關(guān),包括代理模式、單例模式、享元模式、原型模式等,下面一起來(lái)看一下,希望對(duì)大家有幫助。

    一起來(lái)聊聊與Java中性能相關(guān)的設(shè)計(jì)模式

    推薦學(xué)習(xí):《java視頻教程》

    代碼的結(jié)構(gòu)對(duì)應(yīng)用的整體性能,有著重要的影響。結(jié)構(gòu)優(yōu)秀的代碼,可以避免很多潛在的性能問(wèn)題,在代碼的擴(kuò)展性上也有巨大的作用;結(jié)構(gòu)清晰、層次分明的代碼,也有助于幫你找到系統(tǒng)的瓶頸點(diǎn),進(jìn)行專項(xiàng)優(yōu)化。

    設(shè)計(jì)模式就是對(duì)常用開(kāi)發(fā)技巧進(jìn)行的總結(jié),它使得程序員之間交流問(wèn)題,有了更專業(yè)、便捷的方式。

    事實(shí)上,大多數(shù)設(shè)計(jì)模式并不能增加程序的性能,它只是代碼的一種組織方式。本文,我們將一一舉例講解和性能相關(guān)的幾個(gè)設(shè)計(jì)模式,包括代理模式、單例模式、享元模式、原型模式等。

    代理模式

    代理模式(Proxy)可以通過(guò)一個(gè)代理類,來(lái)控制對(duì)一個(gè)對(duì)象的訪問(wèn)。

    Java 中實(shí)現(xiàn)動(dòng)態(tài)代理主要有兩種模式:一種是使用 JDK,另外一種是使用 CGLib。 其中,JDK 方式是面向接口的,主要的相關(guān)類是 InvocationHandler 和 Proxy;CGLib 可以代理普通類,主要的相關(guān)類是 MethodInterceptor 和 Enhancer。

    這個(gè)知識(shí)點(diǎn)面試頻率非常高。

    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 測(cè)試結(jié)果:

    Benchmark Mode Cnt Score Error Units
    ProxyBenchmark.cglib thrpt 10 78499.580 ±1771.148 ops/ms
    ProxyBenchmark.jdk thrpt 10 88948.858 ±814.360 ops/ms

    我現(xiàn)在用的 JDK 版本是 1.8,可以看到,CGLib 的速度并沒(méi)有傳得那么快(有傳言高出10 倍),相比較而言,它的速度甚至略有下降。
    我們?cè)賮?lái)看下代理的創(chuàng)建速度,結(jié)果如下所示??梢钥吹?,在代理類初始化方面,JDK 的吞吐量要高出 CGLib 一倍。

    Benchmark Mode Cnt Score Error Units
    ProxyCreateBenchmark.cglib thrpt 10 7281.487 ± 1339.779 ops/ms
    ProxyCreateBenchmark.jdk thrpt 10 15612.467 ± 268.362 ops/ms

    Spring動(dòng)態(tài)代理

    Spring 廣泛使用了代理模式,它使用 CGLIB 對(duì) Java 的字節(jié)碼進(jìn)行了增強(qiáng)。在復(fù)雜的項(xiàng)目中,會(huì)有非常多的 AOP 代碼,比如權(quán)限、日志等切面。在方便了編碼的同時(shí),AOP 也給不熟悉項(xiàng)目代碼的同學(xué)帶來(lái)了很多困擾。

    下面我將分析一個(gè)使用 arthas 找到動(dòng)態(tài)代理慢邏輯的具體原因,這種方式在復(fù)雜項(xiàng)目中,非常有效,你不需要熟悉項(xiàng)目的代碼,就可以定位到性能瓶頸點(diǎn)。

    首先,我們創(chuàng)建一個(gè)最簡(jiǎn)單的 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();         }     }}

    創(chuàng)建一個(gè)啟動(dòng)類,當(dāng)訪問(wèn) /aop 鏈接時(shí),將會(huì)輸出 Bean 的類名稱,以及它的耗時(shí)。

    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;     }}

    訪問(wèn)結(jié)果如下,可以看到 AOP 代理已經(jīng)生效,內(nèi)存里的 Bean 對(duì)象,已經(jīng)變成了EnhancerBySpringCGLIB 類型,調(diào)用方法 method,耗時(shí)達(dá)到了1005ms。

    一起來(lái)聊聊與Java中性能相關(guān)的設(shè)計(jì)模式
    下面使用 arthas 分析這個(gè)執(zhí)行過(guò)程,找出耗時(shí)最高的 AOP 方法。啟動(dòng) arthas 后,可以從列表中看到我們的應(yīng)用程序,在這里,輸入 1 進(jìn)入分析界面。

    一起來(lái)聊聊與Java中性能相關(guān)的設(shè)計(jì)模式
    在終端輸入 trace 命令,然后訪問(wèn) /aop 接口,終端將打印出一些 debug 信息,可以發(fā)現(xiàn)耗時(shí)操作就是 Spring 的代理類。

    trace cn.wja.spring.ABean method

    一起來(lái)聊聊與Java中性能相關(guān)的設(shè)計(jì)模式

    單例模式

    Spring 在創(chuàng)建組件的時(shí)候,可以通過(guò) scope 注解指定它的作用域,用來(lái)標(biāo)示這是一個(gè)prototype(多例)還是 singleton(單例)。

    當(dāng)指定為單例時(shí)(默認(rèn)行為),在 Spring 容器中,組件有且只有一份,當(dāng)你注入相關(guān)組件的時(shí)候,獲取的組件實(shí)例也是同一份。

    如果是普通的單例類,我們通常將單例的構(gòu)造方法設(shè)置成私有的,單例有懶漢加載和餓漢加載模式。

    餓漢模式

    了解 JVM 類加載機(jī)制的同學(xué)都知道,一個(gè)類從加載到初始化,要經(jīng)歷 5 個(gè)步驟:加載、驗(yàn)證、準(zhǔn)備、解析、初始化。
    一起來(lái)聊聊與Java中性能相關(guān)的設(shè)計(jì)模式
    其中,static 字段和 static 代碼塊,是屬于類的,在類加載的初始化階段就已經(jīng)被執(zhí)行。它在字節(jié)碼中對(duì)應(yīng)的是 方法,屬于類的(構(gòu)造方法)。因?yàn)轭惖某跏蓟挥幸淮?,所以它就能夠保證這個(gè)加載動(dòng)作是線程安全的。

    根據(jù)以上原理,只要把單例的初始化動(dòng)作,放在方法里,就能夠?qū)崿F(xiàn)餓漢模式。

    private static Singleton instace = new Singleton();

    理論上來(lái)說(shuō),餓漢模式它會(huì)造成資源的浪費(fèi),可能生成一些永遠(yuǎn)不會(huì)用到的對(duì)象,因此很多教程不建議用。但實(shí)際上來(lái)說(shuō),這存粹是脫褲子放屁,如果你真的永遠(yuǎn)用不到這個(gè)對(duì)象,你為何要?jiǎng)?chuàng)建這個(gè)類,寫一個(gè)單例模式? 我覺(jué)得對(duì)于普通項(xiàng)目來(lái)說(shuō),餓漢模式就完全足夠了。

    飽漢模式

    而對(duì)象初始化就不一樣了。通常,我們?cè)?new 一個(gè)新對(duì)象的時(shí)候,都會(huì)調(diào)用它的構(gòu)造方法,就是,用來(lái)初始化對(duì)象的屬性。由于在同一時(shí)刻,多個(gè)線程可以同時(shí)調(diào)用函數(shù),我們就需要使用 synchronized 關(guān)鍵字對(duì)生成過(guò)程進(jìn)行同步。

    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 的關(guān)鍵代碼,我們介紹一下四個(gè)關(guān)鍵點(diǎn):

    • 第一次檢查,當(dāng) instance 為 null 的時(shí)候,進(jìn)入對(duì)象實(shí)例化邏輯,否則直接返回。
    • 加同步鎖,這里是類鎖。
    • 第二次檢查才是關(guān)鍵。如果不加這次判空動(dòng)作,可能會(huì)有多個(gè)線程進(jìn)入同步代碼塊,進(jìn)而生成多個(gè)實(shí)例。
    • 最后一個(gè)關(guān)鍵點(diǎn)是 volatile 關(guān)鍵字。在一些低版本的 Java 里,由于指令重排的緣故,可能會(huì)導(dǎo)致單例被 new 出來(lái)后,還沒(méi)來(lái)得及執(zhí)行構(gòu)造函數(shù),就被其他線程使用。 這個(gè)關(guān)鍵字,可以阻止字節(jié)碼指令的重排序,在寫 double check 代碼時(shí),習(xí)慣性會(huì)加上 volatile。

    可以看到,double check 的寫法繁雜,注意點(diǎn)很多,它現(xiàn)在其實(shí)是一種反模式,已經(jīng)不推薦使用了,我也不推薦你用在自己的代碼里。但它能夠考察面試者對(duì)并發(fā)的理解,所以這個(gè)問(wèn)題經(jīng)常被問(wèn)到。

    推薦使用 enum 實(shí)現(xiàn)懶加載的單例,《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框架那就更簡(jiǎn)單了:

    package cn.wja.singleton;import org.springframework.context.annotation.Scope;import org.springframework.stereotype.Component;@Component@Scope("singleton")public class SpringBean {     //具體內(nèi)容}

    享元模式

    享元模式(Flyweight)專門針對(duì)性能優(yōu)化的設(shè)計(jì)模式,它通過(guò)共享技術(shù),最大限度地復(fù)用對(duì)象。享元模式一般會(huì)使用唯一的標(biāo)識(shí)碼進(jìn)行判斷,然后返回對(duì)應(yīng)的對(duì)象,使用 HashMap 一類的集合存儲(chǔ)非常合適。

    上面的描述,我們非常熟悉,因?yàn)楸緦诘闹暗牟┪闹?,我們就能看到很多享元模式的身影,比如博?淺談Java中的池化技術(shù) 里的池化對(duì)象和博文 如何處理Java中的大對(duì)象 里的對(duì)象復(fù)用等。

    案例:Integer

    在Java中,我們常見(jiàn)的Integer,為了提升效率,在創(chuàng)建[1,127]范圍內(nèi)的對(duì)象時(shí)也用了享元模式。通過(guò)下面的測(cè)試代碼可以驗(yàn)證。

    @Testpublic void myTest() throws Exception{     Integer a=1;     Integer b=1;     System.out.println(a == b ? "a b同一個(gè)對(duì)象" : "a b不是同一個(gè)對(duì)象");      Integer c=128;     Integer d=128;     System.out.println(c == d ? "c d同一個(gè)對(duì)象" : "c d不是同一個(gè)對(duì)象");}

    一起來(lái)聊聊與Java中性能相關(guān)的設(shè)計(jì)模式

    多視角看問(wèn)題

    設(shè)計(jì)模式對(duì)這我們平常的編碼進(jìn)行了抽象,從不同的角度去解釋設(shè)計(jì)模式,都會(huì)找到設(shè)計(jì)思想的一些共通點(diǎn)。比如,單例模式就是享元模式的一種特殊情況,它通過(guò)共享單個(gè)實(shí)例,達(dá)到對(duì)象的復(fù)用。

    值得一提的是,同樣的代碼,不同的解釋,會(huì)產(chǎn)生不同的效果。比如下面這段代碼:

    Map<String,Strategy> strategys = new HashMap<>(); strategys.put("a",new AStrategy()); strategys.put("b",new BStrategy());

    如果我們從對(duì)象復(fù)用的角度來(lái)說(shuō),它就是享元模式;如果我們從對(duì)象的功能角度來(lái)說(shuō),那它就是策略模式。所以大家在討論設(shè)計(jì)模式的時(shí)候,一定要注意上下文語(yǔ)境的這些差別。

    原型模式

    原型模式(Prototype)比較類似于復(fù)制粘貼的思想,它可以首先創(chuàng)建一個(gè)實(shí)例,然后通過(guò)這個(gè)實(shí)例進(jìn)行新對(duì)象的創(chuàng)建。在 Java 中,最典型的就是 Object 類的 clone 方法。

    但編碼中這個(gè)方法很少用,我們上面在代理模式提到的 prototype,并不是通過(guò) clone 實(shí)現(xiàn)的,而是使用了更復(fù)雜的反射技術(shù)。

    一個(gè)比較重要的原因就是 clone 如果只拷貝當(dāng)前層次的對(duì)象,實(shí)現(xiàn)的只是淺拷貝。在現(xiàn)實(shí)情況下,對(duì)象往往會(huì)非常復(fù)雜,想要實(shí)現(xiàn)深拷貝的話,需要在 clone 方法里做大量的編碼,遠(yuǎn)遠(yuǎn)不如調(diào)用 new 方法方便。

    實(shí)現(xiàn)深拷貝,還有序列化等手段,比如實(shí)現(xiàn) Serializable 接口,或者把對(duì)象轉(zhuǎn)化成 JSON。

    所以,在現(xiàn)實(shí)情況下,原型模式變成了一種思想,而不是加快對(duì)象創(chuàng)建速度的工具。

    推薦學(xué)習(xí):《java視頻教程》

    贊(0)
    分享到: 更多 (0)
    網(wǎng)站地圖   滬ICP備18035694號(hào)-2    滬公網(wǎng)安備31011702889846號(hào)