标签:sync tom 就会 模式 单例 zed rgs 对象引用 编写
一、背景单例模式是最常见的设计模式之一,也是整个设计模式中最简单的模式之一。
单例模式需确保这个类只有一个实例,而且自行实例化并向整个系统提供这个实例;这个类也称为单例类,提供全局访问的方法。
单例模式有三大要点:
实现单例模式有多种写法,这里我们只列举其中最常用的三种实现方式,且考虑到网站登录高并发场景下,将重点关注多线程环境下的安全问题。
/**
* 单例模式的应用--登录线程
*
* @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个线程并发登录过程中,获取到了同一个对象引用地址,即该单例模式是有效的。
我们先看下未使用线程同步技术的例子:
/**
* 懒汉式单例模式--未应用线程同步技术
*
* @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;
}
我们使用线程同步技术对懒汉式模式进行改进:
/**
* 懒汉式单例模式
*
* @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个线程并发登录过程中,该单例模式是有效的。
标签:sync tom 就会 模式 单例 zed rgs 对象引用 编写
原文地址:https://blog.51cto.com/14230003/2512603