翻译|李睿评论|SunShujuan人们需要了解如何在Java程序中使用惰性实例化和热切实例化。那么,哪种方法更好呢?这取决于场景。当实例化Java对象在资源使用方面成本很高时,用户不希望每次使用它们时都必须实例化它们。拥有一个可以跨系统共享的现成对象实例对性能来说要好得多。在这种情况下,惰性实例化策略非常有效。惰性实例化也有一些缺点,在某些系统中,急切实例化更好。在急切实例化中,对象被实例化一次,通常在应用程序启动后立即实例化。这两种方法是不同的。在某些情况下,某种方法效果最好。本文将介绍这两种实例化Java对象的方法。首先查看代码示例,然后通过Java代码挑战测试您学到的知识。此外,还讨论了惰性实例化与急切实例化的优缺点。1.懒实例化的简单方法首先了解创建单个实例并跨系统共享的简单方法:publicstaticHeroesDBheroesDB;//#AprivateSingletonNaiveApproach(){}//#BpublicHeroesDBgetHeroesDB(){//#Cif(heroesDB==null){//#DheroesDB=newHeroesDB();//#E}返回heroesDB;//#F}staticclassHeroesDB{}}以下是代码中发生的事情:开始(#A),声明一个静态内部类HeroesDB。将变量声明为静态变量,以便它可以在应用程序中共享。下一步(#B),创建私有构造函数以避免从类外部直接实例化。因此,必须使用getHeroes()方法来获取实例。在下一行(#C)中,您会看到有效地从HeroesDB返回实例的方法。接下来(#D),检查heroesDB实例是否为空。如果为空,将创建一个新实例。否则什么都不做。最后(#F),返回heroesDB对象实例。这种方法适用于小型应用程序。但是,在用户较多的大型多线程应用中,很容易发生数据冲突。在这种情况下,对象可能会被实例化多次,甚至在检查null时也是如此。下面将进一步探讨发生这种情况的原因。2.理解竞争条件竞争条件是两个或多个线程并发竞争同一个变量的情况,这会导致意想不到的结果。在大型多线程应用程序中,许多进程并行并发运行。在这种类型的应用程序中,一个线程可能会在另一个线程正在实例化一个空对象的同时询问一个对象是否为null。在这种情况下,存在竞争条件,可能导致重复实例。这可以通过使用同步关键字来解决:publicclassSingletonSynchronizedApproach{publicstaticHeroesDBheroesDB;privateSingletonSynchronizedApproach(){}publicsynchronizedHeroesDBgetHeroesDB(){if(heroesDB==null){heroesDB=newHeroesDB();}返回英雄数据库;}staticclassHeroesDB{}}这段代码解决了getHeroesDB()中线程冲突的问题。但是,整个方法正在同步。这会影响性能,因为一次只有一个线程可以访问整个方法。让我们看看如何解决这个问题。3.优化多线程惰性实例化getHeroesDB()方法中同步策略点,需要在该方法中创建一个同步块。下面是一个例子:publicclassThreadSafeSynchronized{publicstaticvolatileHeroesDBheroesDB;publicstaticHeroesDBgetHeroesDB(){if(heroesDB==null){synchronized(ThreadSafeSynchronized.class){if(heroesDB==null){heroesDB=newHeroesDB();}}}返回heroesDB;}staticclassHeroesDB{}}左右滑动查看完整代码这段代码中,对象的创建只有在实例为空时才会同步。否则,将返回对象实例。另请注意,由于使用了静态方法,ThreadSafeSynchronized类是同步的。然后再次检查以确保heroesDB实例仍然是空的,因为另一个线程可能已经实例化了它。如果您不仔细检查,您可能会遇到多个实例。另一个重要问题是变量heroesDB不稳定。这意味着变量的值没有被缓存。当线程更改此变量时,它将始终具有最新更新的值。4.何时使用eagerinstantiation对于可能永远不会使用的昂贵对象,最好使用lazyinstantiation。但是,如果您知道每次应用程序启动时都会使用您正在处理的对象,并且如果创建对象在使用的系统资源方面成本很高,那么最好使用预实例化。假设您必须创建一个非常昂贵的对象,例如人们总是需要的数据库连接。等待对象被使用可能会降低应用程序的速度。在这种情况下,急切的实例化更有意义。5.实现急切实例化的简单方法实现急切实例化的简单方法如下:静态HeroesDBgetHeroesDB(){返回heroesDB;}staticclassHeroesDB{privateHeroesDB(){System.out.println("急切地实例化heroesDB...");}@OverridepublicStringtoString(){返回“HeroesDB实例”;}}publicstaticvoidmain(String[]args){System.out。println(HeroesDatabaseSimpleEager.getHeroesDB());}}这段代码的输出是:InstantiatingheroesDBeagerly...HeroesDBinstanceisanullcheck。当HeroesDB被声明为HeroesDatabaseSimpleEager中的一个实例变量时,它就会被实例化。因此,每次访问HeroesDatabaseSimpleEager类时,都会从HeroesDB中获取一个实例。toString()方法也被覆盖以简化HeroesDB实例的输出。现在看看使用枚举实现急切实例化的更健壮的方法。6.使用枚举创建急切实例化使用枚举是创建急切实例化对象的更健壮的方法。虽然只有在访问枚举时才会创建实例,但请注意,在下面的代码中,对象创建时没有空检查:publicenumHeroesDatabaseEnum{INSTANCE;整数值;publicintgetValue(){返回值;}publicvoidsetValue(intvalue){this.value=value;}publicstaticvoidmain(String[]args){System.out.println(HeroesDatabaseEnum.INSTANCE);}此代码的输出将是:Creatinginstance...INSTANCE此代码是线程安全的。它保证只创建一个实例,并将对象序列化,这意味着它可以更容易地传输。另一个细节是枚举有一个隐式私有构造函数,它确保不会不必要地创建多个实例。由于其简单性和效率,枚举被认为是使用急切实例化的最佳方法之一。7.惰性实例化vs.急切实例化当一个对象并不总是需要被实例化时,最好使用惰性实例化。当已知对象总是需要实例化时,急切实例化会更好。以下是每种方法的优缺点:(1)惰性实例化的优点:对象只会在需要的时候被实例化。缺点:需要同步才能在多线程环境中工作。由于if检查和同步,性能会变慢。当需要对象时,应用程序可能会出现明显的惰性。(2)Eager实例化的优点:大多数情况下,对象会在应用程序启动时被实例化。使用该对象时没有延迟,因为它已经被实例化了。它在多线程环境中运行良好。缺点:使用这种方法可能会不必要地实例化对象。8.LazyHomer啤酒创作挑战在下面的Java代码挑战中,您将看到在多线程环境中发生的惰性实例化。需要注意的是ThreadPool可以直接使用Thread类,但是最好使用Java并发API。根据本文所学,运行以下代码时最有可能发生什么?导入java.util.concurrent.ExecutorService;导入java.util.concurrent.Executors;导入java.util.concurrent.TimeUnit;公共类LazyHomerBeerCreationChallenge{publicstaticinti=0;公共静态啤酒啤酒;staticvoidcreateBeer(){if(beer==null){try{Thread.sleep(200);啤酒=新啤酒();我++;}catch(InterruptedExceptione){e.printStackTrace();}}}publicstaticvoidmain(String[]args)throwsInterruptedException{ExecutorServiceexecutor=Executors.newFixedThreadPool(2);executor.submit(LazyHomerChallenge::createBeer);executor.submit(LazyHomerChallenge::createBeer);executor.awaitTermination(2,TimeUnit.SECONDS);执行器.shutdown();System.out.println(i);}publicstaticclassBeer{}}下面是这个挑战的选项。查看代码并选择其中之一:1.A)12.B)03.C)24.D)InterruptedException被抛出9.发生了什么?Lazyinstantiation解释这个代码挑战的关键概念是,当两个线程访问同一个进程时,Parallelism就会发生。所以既然有线程。在实例化beer之前休眠,很可能创建两个beer实例。线程不会并发运行的可能性非常小,这取决于JVM实现。但是因为线程,你很可能会得到两个Thread.sleep方法。现在再次查看代码,注意到使用线程池创建了两个线程,然后createBear方法在这些线程上运行。因此,此代码挑战的正确答案是:C,或值为2。10.结论惰性和急切实例化是优化昂贵对象性能的重要概念。以下是关于这些设计策略要记住的要点:惰性实例化需要在实例化之前进行空检查。在多线程环境中同步延迟实例化的对象。Eager实例化不需要对对象进行空检查。使用枚举是一种有效且简单的紧急实例化方法。原文链接:https://img.ydisp.cn/news/20230215/slqngok3bkp
