java 单例模式(八种写法)
本文通过饿汉和懒汉两种模式分析单例模式,并分析每种单例的优缺点。目录1 饿汉-Hungry(可用)2懒汉-Lazy(不推荐)3线程安全-synchronized(可用)4 双重检测-DoubleCheck(推荐)5内部类-InnerClass(推荐)6序列化-Serializable(可用)7容器单例-IOC(可用)8枚举-Enum(推荐)1 饿...
本文通过饿汉和懒汉两种模式分析单例模式,并分析每种单例的优缺点。
目录
1 饿汉-Hungry(可用)
饿汉单例模式在类加载的时候,就初始化,并且创建对象
优点:
1.没有加任何锁,执行效率比较高
2.用户体验上来说,比懒汉式更好
3.线程绝对安全,线程还没有访问的时候,就已经初始化了,不存在访问线程安全问题
缺点:
1.类加载的时候就初始化,不管你用不用,都占用内存资源
public class HungrySingleton {
private static HungrySingleton instance = new HungrySingleton();
//私有化构造函数
private HungrySingleton(){
}
public static HungrySingleton getInstance(){
return instance;
}
}
还一种写法,在静态代码块中进行初始化,因为静态代码块也是在类加载的时候执行
public class HungryStaticSingleton {
//私有化构造函数
private HungryStaticSingleton() {
}
private static HungryStaticSingleton instance;
//静态代码块
static {
instance = new HungryStaticSingleton();
}
public HungryStaticSingleton getInstance() {
return instance;
}
}
2 懒汉-Lazy(不推荐)
有线程调用getInstance方法的时候,开始进行初始化
优点:
1.开始不占用内存,只有调用了才初始化实例
缺点:
1.并发的时候,可能会出现多个实例。线程不安全,因为并发的时候,会出现创建多个实例的情况
public class LazySimpleSingleton {
private static LazySimpleSingleton instance = null;
//私有化构造函数
private LazySimpleSingleton() {
}
public static LazySimpleSingleton getInstance() {
if (instance == null) {
instance = new LazySimpleSingleton();
}
return instance;
}
}
下面我们可以用两个线程来模拟下 ,创建一个ExecutorThread实现Runnable,并重写run方法
public class ExecutorThread implements Runnable {
@Override
public void run() {
//获取实例
LazySimpleSingleton instance = LazySimpleSingleton.getInstance();
//打印当前线程名和实例名
System.out.println(Thread.currentThread().getName()+":"+instance);
}
}
再创建一个Test类,创建两个线程并启动
public class LazySimpleSingletonTest {
public static void main(String[] args) {
new Thread(new ExecutorThread()).start();
new Thread(new ExecutorThread()).start();
}
}
运行main方法,看控制台打印,从下图可以看出,有几率出现实例化两次的情况,
3 线程安全-synchronized(可用)
有线程调用getInstance方法的时候,开始进行初始化
优点:
1.开始不占用内存,只有调用了才初始化
2.线程安全,因为getInstance加上了锁,假如并发访问的话,拿到锁的线程,才能进入方法进行初始化,其他线程排队等待锁
缺点:
1.性能比较低,并发的时候,会出现其他线程等待的情况
2.每一次并发的时候,都会有锁的竞争情况
public class LazyThreadSingleton {
private static LazyThreadSingleton lazySingleTon = null;
//私有化构造函数
private LazyThreadSingleton() {
}
public static synchronized LazyThreadSingleton getInstance() {
if (lazySingleTon == null) {
lazySingleTon = new LazyThreadSingleton();
}
return lazySingleTon;
}
}
4 双重检测-DoubleCheck(推荐)
有线程调用getInstance方法的时候,开始进行初始化
优点:
1.开始不占用内存,只有调用了getInstance才初始化
2.第一次并发访问存在锁的竞争情况,后面任何一个一次并发都不会存在锁的竞争
缺点:
1.第一次并发的时候,有锁的竞争情况,存在线程等待
public class LazyDoubleCheckSingleton {
//私有化构造函数
private LazyDoubleCheckSingleton() {
}
private static LazyDoubleCheckSingleton instance = null;
public static LazyDoubleCheckSingleton getInstance() {
if (instance == null) {
synchronized (LazyDoubleCheckSingleton.class) {
if (instance == null) {
instance = new LazyDoubleCheckSingleton();
}
}
}
return instance;
}
}
5 内部类-InnerClass(推荐)
JVM加载LazyInnerClassSingleton的时候,不会去加载内部类LazyHolder,只有调用getInstance方法的时候,JVM才会去加载LazyHolder,巧妙的避开了线程安全的问题
优点:
1.开始不会占用内存
2.兼顾了synchronized的性能问题
3.巧妙的避免了线程安全的问题
public class LazyInnerClassSingleton {
//私有化构造函数
private LazyInnerClassSingleton() {
}
//保证这个方法不会被重写,重载
public static final LazyInnerClassSingleton getInstance() {
return LazyHolder.lazy;
}
//内部类,默认不加载
private static class LazyHolder {
private static LazyInnerClassSingleton lazy = new LazyInnerClassSingleton();
}
}
6 序列化-Serializable(可用)
两个要求:1.实现Seriablizable,2.增加readResolve方法
//实现Serializable
public class SeriableSingleton implements Serializable {
//私有化构造函数
private SeriableSingleton(){
}
private static SeriableSingleton instance = new SeriableSingleton();
public static SeriableSingleton getInstance(){
return instance;
}
//一定要加上这个方法,否则序列化会破坏单例
public Object readResolve(){
return instance;
}
}
下面写个Test,对此对象进行序列化和反序列化
(序列化:就是把内存中的状态通过转换成字节码的形式从而转换成一个IO流,写入到其他地方,例如:网络IO,磁盘,内存中状态给永久保存下来了)
反序列化:将已经持久化的字节码内容,转换成IO流,通过IO流的读取,将内容转换成Java对象)
public class SeriableSingletonTest {
public static void main(String[] args) {
SeriableSingleton s1 = SeriableSingleton.getInstance();
try {
//序列化
FileOutputStream fos = new FileOutputStream("SeriableSingleton.obj");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(s1);
oos.flush();
oos.close();
//反序列化
FileInputStream fis = new FileInputStream("SeriableSingleton.obj");
ObjectInputStream ois = new ObjectInputStream(fis);
SeriableSingleton s2 = (SeriableSingleton) ois.readObject();
ois.close();
System.out.println(s1);
System.out.println(s2);
System.out.println(s1==s2);
} catch (Exception e) {
e.printStackTrace();
}
}
}
查看控制台结果
7 容器单例-IOC(可用)
容器单例适用于创建非常多的实例,便于管理,就像Spring一样
public class ContainerSingleton {
//私有化构造函数
private ContainerSingleton() {
}
private static Map<String, Object> ioc = new ConcurrentHashMap<>();
public static Object getInstance(String className) {
synchronized (ioc) {
if (!ioc.containsKey(className)) {
try {
Object o = Class.forName(className).newInstance();
ioc.put(className, o);
return o;
} catch (Exception e) {
e.printStackTrace();
}
}
return ioc.get(className);
}
}
}
我们可以看下Spring中的容器式单例的实现代码:
8 枚举-Enum(推荐)
public enum EnumSingleton {
INSTANCE;
EnumSingleton(){
System.out.println("新建对象");
}
public static EnumSingleton getInstance() {
return INSTANCE;
}
}
写一个测试类,
public class EnumSingletonTest {
public static void main(String[] args) {
EnumSingleton instance1 = EnumSingleton.getInstance();
EnumSingleton instance2 = EnumSingleton.getInstance();
System.out.println(instance1==instance2);
}
}
从结果可以看出,构造函数只执行了一次
更多推荐
所有评论(0)