單例模式,顧名思義就是一個類只有一個實例。
單例主要的好處就是,1:能夠解決資源訪問衝突的問題。2:減小資源浪費。java
1:餓漢式redis
在類加載的時候就實力化對象,不支持延遲加載。編程
public class HungryDemo { private AtomicInteger id = new AtomicInteger(0); public static HungryDemo instance = new HungryDemo(); private HungryDemo() { } public static HungryDemo getInstance() { return instance; } public int getId() { return id.incrementAndGet(); } public static void main(String[] args) { for (int i = 0; i < 10; i++) { new Thread(() -> { try { Thread.sleep(new Random().nextInt(10000)); } catch (InterruptedException e) { e.printStackTrace(); } HungryDemo instance = HungryDemo.getInstance(); System.out.println(instance + "==>" + instance.getId()); }).start(); } } }
2:懶漢式安全
支持延遲加載,在使用的時候才真正加載。併發
public class LazyDemo { private AtomicInteger id = new AtomicInteger(0); public static LazyDemo instance; private LazyDemo() { } public static synchronized LazyDemo getInstance() { if (Objects.isNull(instance)) { instance = new LazyDemo(); } return instance; } public int getId() { return id.incrementAndGet(); } public static void main(String[] args) { for (int i = 0; i < 10; i++) { new Thread(() -> { try { Thread.sleep(new Random().nextInt(10000)); } catch (InterruptedException e) { e.printStackTrace(); } LazyDemo instance = LazyDemo.getInstance(); System.out.println(instance + "==>" + instance.getId()); }).start(); } } }
餓漢和懶漢的區別就是一個支持延遲加載,一個不支持。懶漢模式還要加鎖,防止併發問題。從這一點來看,懶漢模式性能是不如餓漢模式,餓漢模式在類加載時就進行實力化,也能夠提早將實力化過程當中發生的資源不足等問題提早暴露,而不是等到業務訪問後才發現沒法進行初始化,引起線上事故。
但這兩種都有各自的不足,因此如今有第三種方式。dom
3:雙重檢測jvm
public class DoubleCheckDemo { private AtomicInteger id = new AtomicInteger(0); public static DoubleCheckDemo instance; private DoubleCheckDemo() { } public static DoubleCheckDemo getInstance() { if (Objects.isNull(instance)) { synchronized (DoubleCheckDemo.class) { System.out.println("加鎖操做"); if (Objects.isNull(instance)) { instance = new DoubleCheckDemo(); } } } return instance; } public int getId() { return id.incrementAndGet(); } public static void main(String[] args) { for (int i = 0; i < 10; i++) { new Thread(() -> { try { Thread.sleep(new Random().nextInt(10000)); } catch (InterruptedException e) { e.printStackTrace(); } DoubleCheckDemo instance = DoubleCheckDemo.getInstance(); System.out.println(instance + "==>" + instance.getId()); }).start(); } } }
雙重檢測解決了懶漢模式的併發性能問題,同時支持懶加載。分佈式
4:靜態內部類函數
靜態內部類的實現比雙重檢測要更加簡單,同時也能作到懶加載。工具
public class InnerClassDemo { private AtomicInteger id = new AtomicInteger(0); private InnerClassDemo() { } private static class InnerClassDemoHolder { private static final InnerClassDemo instance = new InnerClassDemo(); } public static InnerClassDemo getInstance() { return InnerClassDemoHolder.instance; } public int getId() { return id.incrementAndGet(); } public static void main(String[] args) { for (int i = 0; i < 10; i++) { new Thread(() -> { try { Thread.sleep(new Random().nextInt(10000)); } catch (InterruptedException e) { e.printStackTrace(); } InnerClassDemo instance = InnerClassDemo.getInstance(); System.out.println(instance + "==>" + instance.getId()); }).start(); } } }
InnerClassDemoHolder
是靜態內部類,一開始並不會加載,等到調用getInstance()
方法時纔會被加載,這是一種無鎖的實現,線程安全由jvm來保證。
5:枚舉
採用枚舉的方式來實現是最簡單的方式。
public enum EnumDemo { INSTANCE; private AtomicInteger id = new AtomicInteger(0); public int getId() { return id.incrementAndGet(); } } public class EnumDemoTest { public static void main(String[] args) { for (int i = 0; i < 10; i++) { new Thread(() -> { try { Thread.sleep(new Random().nextInt(10000)); } catch (InterruptedException e) { e.printStackTrace(); } EnumDemo instance = EnumDemo.INSTANCE; System.out.println(instance + "==>" + instance.getId()); }).start(); } } }
若是須要在線程內實現單例,可使用ThreadLocal併發工具類。
若是須要在集羣環境下實現單例,能夠藉助外部共享存儲,例如redis。當進行實例建立的時候,從外部存儲加載對象,若是沒有則建立後存儲回去,這裏須要分佈式鎖的接入,實現起來比較麻煩。
簡單工廠能夠看做是將對象的建立抽取出來獨立的一種實現,這種實現方式比較簡單,目的就是將建立對象的負責業務代碼從業務中剝離出來,實現複用。
缺點是邏輯過於集中,不利於後續擴展。
工廠方法模式是簡單工廠的進一步抽象。使用面向對象的多態性,保持了簡單工廠的的優勢。將不一樣的構建邏輯分開到不一樣的實現中,避免了修改單個邏輯影響全部的構建邏輯。
抽象工廠提供一個抽象,而不是具體實現。符合面向抽象編程思想,增長工廠也不影響具體業務代碼。
比較典型的實現是lombok的builder方法。
public class BuilderDemo { private String name; private int age; private String phone; private String email; private BuilderDemo(BuilderDemoBuilder builder) { this.name = builder.name; this.age = builder.age; this.phone = builder.phone; this.email = builder.email; } public static class BuilderDemoBuilder { private String name; private int age; private String phone; private String email; public BuilderDemo build() { return new BuilderDemo(this); } public BuilderDemoBuilder setName(String name) { if (Objects.isNull(name)) { throw new IllegalArgumentException("name must not null"); } this.name = name; return this; } public BuilderDemoBuilder setAge(int age) { this.age = age; return this; } public BuilderDemoBuilder setPhone(String phone) { if (Objects.isNull(name)) { throw new IllegalArgumentException("phone must not null"); } this.phone = phone; return this; } public BuilderDemoBuilder setEmail(String email) { this.email = email; return this; } } public static void main(String[] args) { BuilderDemo build = new BuilderDemoBuilder().setAge(18).setEmail("email").setName("name").setPhone("13812340987").build(); System.out.println(build.name); } }
基於已有對象原型建立對象。
實現方式分爲深拷貝和淺拷貝。深拷貝須要遞歸負責,建立一個徹底獨立的對象。淺拷貝只是拷貝引用地址,適合不會變的對象。