饿汉式
核心是私有化构造方法、内部static final初始一个对象、对外提供获取实例的方法getInstance
/**
* @author zhangmy
* @date 2022/6/7 8:21
* @description
* 饿汉式
* 类加载到内存后,就实例化一个单例,JVM保证线程安全(线程安全是指JVM每个class只会加载一次)
* 简单实用,推荐使用
* 唯一缺点:不管用到与否,类装载时就完成实例化
*/
public class Manager01 {
private static final Manager01 INSTANCE = new Manager01();
private Manager01(){}
public static Manager01 getInstance() {
return INSTANCE;
}
}
饿汉式即不管是否使用,先初始化再说,那我们是否可以做到用到的时候再初始化呢,当然是可以的
懒汉式
package com.imysh.zmy.mca.designpattern.singleton;
/**
* 懒汉式
* 达到按需初始化的目的,但是带来线程不安全的问题
* @author zhangmy
* @date 2022/6/21 7:55
* @description
*/
public class Manager02 {
private static Manager02 INSTANCE;
private Manager02(){}
public static Manager02 getInstance() {
if (INSTANCE == null) {
// 这段try是为了测试多线程下 线程不安全的问题
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
INSTANCE = new Manager02();
}
return INSTANCE;
}
public static void main(String[] args) {
for (int i = 0; i < 100 ; i++) {
new Thread(() -> {
System.out.println(Manager02.getInstance().hashCode());
} ).start();
}
}
}
此种方式虽然达到按需初始化的目的,但是带来线程不安全的问题,就像示例中所示,如果线程sleep1毫秒,那么在多线的情况下肯定会存在线程不安全的问题,我们调用main方法看下hash值
很明显,并非是同一个对象,那么我们是否可以做优化,比如使用加锁的方式
package com.imysh.zmy.mca.designpattern.singleton;
/**
* 懒汉式 - 加锁
* 解决懒汉式线程不安全的问题,但是 同时带来效率降低的问题
* @author zhangmy
* @date 2022/6/21 7:55
* @description
*/
public class Manager03 {
private static Manager03 INSTANCE;
private Manager03(){}
/**
* 此处 synchronized 锁定的是Manager03.class对象
* @return
*/
public static synchronized Manager03 getInstance() {
if (INSTANCE == null) {
// 这段try是为了测试多线程下 线程不安全的问题
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
INSTANCE = new Manager03();
}
return INSTANCE;
}
public static void main(String[] args) {
for (int i = 0; i < 100 ; i++) {
new Thread(() -> {
System.out.println(Manager03.getInstance().hashCode());
} ).start();
}
}
}
加上锁之后的确能解决线程安全的问题,但是很明显,只要在getInstance获取实例,都会校验,都会上锁,那么在多线程获取instance的时候明显效率肯定是降低的,那我们再尝试做个优化
package com.imysh.zmy.mca.designpattern.singleton;
/**
* 懒汉式 - 部分加锁
* 这种其实也没有解决线程不安全的问题
* @author zhangmy
* @date 2022/6/21 7:55
* @description
*/
public class Manager04 {
private static Manager04 INSTANCE;
private Manager04(){}
/**
* 此处 synchronized 锁定的是Manager03.class对象
* @return
*/
public static Manager04 getInstance() {
if (INSTANCE == null) {
// 只针对特定的地方加锁
synchronized (Manager04.class) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
INSTANCE = new Manager04();
}
}
return INSTANCE;
}
public static void main(String[] args) {
for (int i = 0; i < 100 ; i++) {
new Thread(() -> {
System.out.println(Manager04.getInstance().hashCode());
} ).start();
}
}
}
这里呢我们只在创建实例的时候加锁,再已初始化的时候并不会发生锁竞争导致效率降低的问题,但是仔细观察,这种方式其实也没有解决线程安全的问题,在有两个线程都到锁那里进行竞争的时候都会初始化示例,调main方法试试
我们进一步优化
package com.imysh.zmy.mca.designpattern.singleton;
/**
* 懒汉式 - 部分加锁 + 双重判断
* 既解决线程安全,又相对提高效率
* @author zhangmy
* @date 2022/6/21 7:55
* @description
*/
public class Manager05 {
// volatile关键字 JIT优化
private static volatile Manager05 INSTANCE;
private Manager05(){}
/**
* 此处 synchronized 锁定的是Manager03.class对象
* @return
*/
public static Manager05 getInstance() {
if (INSTANCE == null) {
// 只针对特定的地方加锁
synchronized (Manager05.class) {
// 再判断一次
if (INSTANCE == null) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
INSTANCE = new Manager05();
}
}
}
return INSTANCE;
}
public static void main(String[] args) {
for (int i = 0; i < 100 ; i++) {
new Thread(() -> {
System.out.println(Manager05.getInstance().hashCode());
} ).start();
}
}
}
这种双重判断的方式,就可以保证既解决线程安全,又相对提高效率,这也是懒汉式完美的写法,当然还有其他方式实现单例
静态内部类方式
package com.imysh.zmy.mca.designpattern.singleton;
/**
* 静态内部类方式 -- 完美的方式
* JVM保证线程安全(外部类只会加载一次,且内部类是用到的时候才会加载),即加载外部类时不会加载内部类
*
* @author zhangmy
* @date 2022/6/21 7:55
* @description
*/
public class Manager06 {
private Manager06(){}
/**
* 使用静态内部类去初始化外部类
*/
private static class Manager06Holder {
private static final Manager06 INSTANCE = new Manager06();
}
private static Manager06 getInstance() {
return Manager06Holder.INSTANCE;
}
}
这也是比较的写法,代码也看上去简介,同时JVM保证线程安全(外部类只会加载一次,且内部类是用到的时候才会加载),即加载外部类时不会加载内部类。还有没有更加简介且完美的方式呢,当然有
枚举单例
package com.imysh.zmy.mca.designpattern.singleton;
/**
* 枚举单例 -- 完美的写法
* 不仅可以解决线程同步,还可以防止反序列化
*
* @author zhangmy
* @date 2022/6/21 8:25
* @description
*/
public enum Manager07 {
INSTANCE;
public static void main(String[] args) {
for (int i = 0; i < 100 ; i++) {
System.out.println(Manager07.INSTANCE.hashCode());
}
}
}
枚举单例应该是最简单且最完美的方式了,但是看上去就不那么直观
总结
日常工作中,其实我们使用饿汉式就可以了,毕竟如果不需要使用,就没必要写这个类了吧
package com.imysh.zmy.mca.designpattern.singleton;
/**
* @author zhangmy
* @date 2022/6/7 8:21
* @description
* 饿汉式
* 类加载到内存后,就实例化一个单例,JVM保证线程安全(线程安全是指JVM每个class只会加载一次)
* 简单实用,推荐使用
* 唯一缺点:不管用到与否,类装载时就完成实例化
*/
public class Manager01 {
private static final Manager01 INSTANCE = new Manager01();
private Manager01(){}
public static Manager01 getInstance() {
return INSTANCE;
}
}
第二种懒汉式的完美写法也是可以
package com.imysh.zmy.mca.designpattern.singleton;
/**
* 懒汉式 - 部分加锁 + 双重判断
* 既解决线程安全,又相对提高效率
* @author zhangmy
* @date 2022/6/21 7:55
* @description
*/
public class Manager05 {
// volatile关键字 JIT优化
private static volatile Manager05 INSTANCE;
private Manager05(){}
/**
* 此处 synchronized 锁定的是Manager03.class对象
* @return
*/
public static Manager05 getInstance() {
if (INSTANCE == null) {
// 只针对特定的地方加锁
synchronized (Manager05.class) {
// 再判断一次
if (INSTANCE == null) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
INSTANCE = new Manager05();
}
}
}
return INSTANCE;
}
public static void main(String[] args) {
for (int i = 0; i < 100 ; i++) {
new Thread(() -> {
System.out.println(Manager05.getInstance().hashCode());
} ).start();
}
}
}
第三种就是枚举方式
package com.imysh.zmy.mca.designpattern.singleton;
/**
* 枚举单例 -- 完美的写法
* 不仅可以解决线程同步,还可以防止反序列化
*
* @author zhangmy
* @date 2022/6/21 8:25
* @description
*/
public enum Manager07 {
INSTANCE;
public static void main(String[] args) {
for (int i = 0; i < 100 ; i++) {
System.out.println(Manager07.INSTANCE.hashCode());
}
}
}
以上三种都是工作中可行的方式,可根据自己的需要和习惯采用何种方式
评论区