单例模式是最多见的设计模式之一,也是整个设计模式中最简单的模式之一。设计模式
单例模式需确保这个类只有一个实例,并且自行实例化并向整个系统提供这个实例;这个类也称为单例类,提供全局访问的方法。安全
单例模式有三大要点:服务器
实现单例模式有多种写法,这里咱们只列举其中最经常使用的三种实现方式,且考虑到网站登陆高并发场景下,将重点关注多线程环境下的安全问题。多线程
/** * 单例模式的应用--登陆线程 * * @author zhuhuix * @date 2020-06-01 */ public class Login implements Runnable { // 登陆名称 private String loginName; public String getLoginName() { return loginName; } public void setLoginName(String loginName) { this.loginName = loginName; } @Override public void run() { // TODO // 登陆成功后调用单例对象进行计数 } }
/** * 单例模式--主程序 * * @author zhuhuix * @date 2020-06-01 */ public class App { public final static int num = 10; public static void main(String[] args) throws InterruptedException { Thread[] threads = new Thread[num]; for (int i = 0; i < num; i++) { Login login = new Login(); login.setLoginName("" + String.format("%2s", (i + 1)) + "号用户"); threads[i] = new Thread(login); threads[i].start(); } for (int i = 0; i < threads.length; i++) { threads[i].join(); } // TODO // 调用单例对象输出登陆人数统计 }
/** * 饿汉式单例模式 * * @author zhuhuix * @date 2020-06-01 */ public class SimpleSingleton implements Serializable { // 单例对象 private static final SimpleSingleton APP_INSTANCE = new SimpleSingleton(); // 计数器 private AtomicLong count = new AtomicLong(0); // 单例模式必须保证默认构造方法为私有类型 private SimpleSingleton() { } public static SimpleSingleton getInstance() { return APP_INSTANCE; } public AtomicLong getCount() { return count; } public void setCount() { count.addAndGet(1); } }
咱们将饿汉模式的单例对象加入进登陆线程及主程序中进行测试:并发
/** * 单例模式的应用--登陆线程 * * @author zhuhuix * @date 2020-06-01 */ public class Login implements Runnable { // 登陆名称 private String loginName; public String getLoginName() { return loginName; } public void setLoginName(String loginName) { this.loginName = loginName; } @Override public void run() { // 饿汉式单例 SimpleSingleton simpleSingleton= SimpleSingleton.getInstance(); simpleSingleton.setCount(); System.out.println(getLoginName()+"登陆成功:"+simpleSingleton.toString()); } } /** * 单例模式--主程序 * * @author zhuhuix * @date 2020-06-01 */ public class App { public final static int num = 10; public static void main(String[] args) throws InterruptedException { Thread[] threads = new Thread[num]; for (int i = 0; i < num; i++) { Login login = new Login(); login.setLoginName("" + String.format("%2s", (i + 1)) + "号用户"); threads[i] = new Thread(login); threads[i].start(); } for (int i = 0; i < threads.length; i++) { threads[i].join(); } System.out.println("网站共有"+SimpleSingleton.getInstance().getCount()+"个用户登陆"); } }
输出以下:
10个线程并发登陆过程当中,获取到了同一个对象引用地址,即该单例模式是有效的。ide
咱们先看下未使用线程同步技术的例子:高并发
/** * 懒汉式单例模式--未应用线程同步技术 * * @author zhuhuix * @date 2020-06-01 */ public class LazySingleton { // 单例对象 private static LazySingleton APP_INSTANCE; // 计数器 private AtomicLong count = new AtomicLong(0); // 单例模式必须保证默认构造方法为私有类型 private LazySingleton() { } public static LazySingleton getInstance() { if (APP_INSTANCE == null) { APP_INSTANCE = new LazySingleton(); } return APP_INSTANCE; } public AtomicLong getCount() { return count; } public void setCount() { count.addAndGet(1); } }
/** * 单例模式的应用--登陆线程 * * @author zhuhuix * @date 2020-06-01 */ public class Login implements Runnable { .... @Override public void run() { // 饿汉式单例 LazySingleton lazySingleton =LazySingleton.getInstance(); lazySingleton.setCount(); System.out.println(getLoginName()+"登陆成功:"+lazySingleton); } } /** * 单例模式--主程序- * * @author zhuhuix * @date 2020-06-01 */ public class App { public final static int num = 10; public static void main(String[] args) throws InterruptedException { Thread[] threads = new Thread[num]; for (int i = 0; i < num; i++) { Login login = new Login(); login.setLoginName("" + String.format("%2s", (i + 1)) + "号用户"); threads[i] = new Thread(login); threads[i].start(); } for (int i = 0; i < threads.length; i++) { threads[i].join(); } System.out.println("网站共有" + LazySingleton.getInstance().getCount() + "个用户登陆"); } }
输出结果:
10个线程并发登陆过程当中,获取到了四个对象引用地址,该单例模式失效了。测试
对代码进行分析:网站
// 未使用线程同步 public static LazySingleton getInstance() { // 在多个线程并发时,可能会有多个线程同时进入 if 语句,致使产生多个实例 if (APP_INSTANCE == null) { APP_INSTANCE = new LazySingleton(); } return APP_INSTANCE; }
咱们使用线程同步技术对懒汉式模式进行改进:ui
/** * 懒汉式单例模式 * * @author zhuhuix * @date 2020-06-01 */ public class LazySingleton { // 单例对象 ,加入volatile关键字进行修饰 private static volatile LazySingleton APP_INSTANCE; // 计数器 private AtomicLong count = new AtomicLong(0); // 单例模式必须保证默认构造方法为私有类型 private LazySingleton() { } public static LazySingleton getInstance() { if (APP_INSTANCE == null) { // 对类进行加锁,并进行双重检查 synchronized (LazySingleton.class) { if (APP_INSTANCE == null) { APP_INSTANCE = new LazySingleton(); } } } return APP_INSTANCE; } public AtomicLong getCount() { return count; } public void setCount() { count.addAndGet(1); } }
再测试运行:
10个线程并发登陆过程当中,获取到了同一个对象引用地址,即该单例模式有效了。
《Effective Java》 推荐使用枚举的方式解决单例模式。这种方式解决了最主要的;线程安全、自由串行化、单一实例。
/** * 利用枚举类实现单例模式 * * @author zhuhuix * @date 2020-06-01 */ public enum EnumSingleton implements Serializable { // 单例对象 APP_INSTANCE; // 计数器 private AtomicLong count = new AtomicLong(0); // 单例模式必须保证默认构造方法为私有类型 private EnumSingleton() { } public AtomicLong getCount() { return count; } public void setCount() { count.addAndGet(1); } }
/** * 单例模式的应用--登陆线程 * * @author zhuhuix * @date 2020-06-01 */ public class Login implements Runnable { ... @Override public void run() { EnumSingleton enumSingleton = EnumSingleton.APP_INSTANCE; enumSingleton.setCount(); System.out.println(getLoginName()+"登陆成功:"+enumSingleton.toString()); } } /** * 单例模式--主程序 * * @author zhuhuix * @date 2020-06-01 */ public class App { public final static int num = 10; public static void main(String[] args) throws InterruptedException { Thread[] threads = new Thread[num]; for (int i = 0; i < num; i++) { Login login = new Login(); login.setLoginName("" + String.format("%2s", (i + 1)) + "号用户"); threads[i] = new Thread(login); threads[i].start(); } for (int i = 0; i < threads.length; i++) { threads[i].join(); } System.out.println("网站共有"+EnumSingleton.APP_INSTANCE.getCount()+"个用户登陆"); } }
输出以下:
10个线程并发登陆过程当中,该单例模式是有效的。