分类:
懒汉式和饿汉式
单例应用场景:
(1)单体应用计数器
(2)应用配置、线程池
(3)数据库连接池
优点:
(1)在内存里只有一个实例,减少了内存开销
(2)可以避免对资源的多重占用
(3)设置全局访问点,严格控制访问,即:对外不能new
缺点:
没有接口,扩展困难
1)懒汉式:可延时加载实例,减小内存开销
1.1多线程不安全的懒汉式代码
单例类LazySingleton
package com.geely.design.pattern.creational.singleton;
/**
* Created by geely
*/
public class LazySingleton {
private static LazySingleton lazySingleton = null;
private LazySingleton(){
}
public static LazySingleton getInstance(){
if(lazySingleton == null){
lazySingleton = new LazySingleton();
}
return lazySingleton;
}
}
线程类T
package com.geely.design.pattern.creational.singleton;
/**
* Created by geely
*/
public class T implements Runnable {
@Override
public void run() {
LazySingleton lazySingleton = LazySingleton.getInstance();
System.out.println(Thread.currentThread().getName()+" "+lazySingleton);
}
}
测试类Test
package com.geely.design.pattern.creational.singleton;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
/**
* Created by geely
*/
public class Test {
public static void main(String[] args) {
Thread t1 = new Thread(new T());
Thread t2 = new Thread(new T());
t1.start();
t2.start();
System.out.println("program end");
}
}
线程不安全说明:
通过idea 多线程debug模拟,具体方法参考:https://www.jianshu.com/p/3832ef4932d3
当线程1和线程2走到LazySingleton类的 lazySingleton = new LazySingleton();代码块时,
(1)假设线程1先执行了new LazySingleton操作,此时并没有结束,这时线程2也执行了new操作,由于lazySingleton实例是static的共享的,当线程2和线程1执行完后,线程2创建的对象会覆盖线程1创建的对象,如果在大并发下,可能会创建多次对象,非常消耗内存。
(2)假设线程1执行了new LazySingleton操作并结束,这时线程2开始执行new操作并结束,此时会存在两个不同的对象,不符合单例定义。
1.2 通过 synchronized关键字给静态方法加锁,实现线程安全
package com.geely.design.pattern.creational.singleton;
/**
* Created by geely
*/
public class LazySingleton {
private static LazySingleton lazySingleton = null;
private LazySingleton(){
}
public synchronized static LazySingleton getInstance(){
if(lazySingleton == null){
lazySingleton = new LazySingleton();
}
return lazySingleton;
}
}
缺点:虽然解决了线程安全问题,但是synchronized 作用于整个类上,加锁和释放锁本身就会消耗性能,所以这种方式性能不好。
1.3 优化synchronized,通过 DoubleCheck双重检查 解决线程安全
单例类 LazyDoubleCheckSingleton
package com.geely.design.pattern.creational.singleton;
/**
* Created by geely
*/
public class LazyDoubleCheckSingleton {
private static LazyDoubleCheckSingleton lazyDoubleCheckSingleton = null;
private LazyDoubleCheckSingleton(){
}
public static LazyDoubleCheckSingleton getInstance(){
if(lazyDoubleCheckSingleton == null){
synchronized (LazyDoubleCheckSingleton.class){
if(lazyDoubleCheckSingleton == null){
lazyDoubleCheckSingleton = new LazyDoubleCheckSingleton();
//1.分配内存给这个对象
// //3.设置lazyDoubleCheckSingleton 指向刚分配的内存地址
//2.初始化对象
// intra-thread semantics
// ---------------//3.设置lazyDoubleCheckSingleton 指向刚分配的内存地址
}
}
}
return lazyDoubleCheckSingleton;
}
}
缺点:double check方案可以实现线程安全,且降低内存消耗。代码中三行注释代表了new对象时底层进行了三步操作,由于JVM优化算法可能会指令重排,也就是第二步和第三步执行的顺序会互换。这样可能会出现第一个线程出现了指令重排情况,先有内存地址但还没初始化对象,此时第二个线程获取到lazyDoubleCheckSingleton实例地址不为空,直接就返回实例对象,导致获取的对象是null并抛出异常。
下图是JVM执行new对象时,多线程情况下可能出现的步骤
1.4 DoubleCheck双重检查添加volatile关键字修饰静态类,禁止指令重排
private volatile static LazyDoubleCheckSingleton lazyDoubleCheckSingleton = null;
1.5 也可以使用静态内部类的初始化延迟加载解决,静态内部类有初始化锁,达到线程安全的目的
package com.geely.design.pattern.creational.singleton;
/**
* Created by geely
*/
public class StaticInnerClassSingleton {
private static class InnerClass{
private static StaticInnerClassSingleton staticInnerClassSingleton = new StaticInnerClassSingleton();
}
public static StaticInnerClassSingleton getInstance(){
return InnerClass.staticInnerClassSingleton;
}
}
2)饿汉式:JVM启动就加载实例,不能延时加载
2.1 属性直接new
package com.geely.design.pattern.creational.singleton;
import java.io.Serializable;
/**
* Created by geely
*/
public class HungrySingleton{
private final static HungrySingleton hungrySingleton = new HungrySingleton();
private HungrySingleton(){
}
public static HungrySingleton getInstance(){
return hungrySingleton;
}
}
2.2 通过静态代码块new
package com.geely.design.pattern.creational.singleton;
/**
* Created by geely
*/
public class HungrySingleton{
private final static HungrySingleton hungrySingleton;
static{
hungrySingleton = new HungrySingleton();
}
private HungrySingleton(){
}
public static HungrySingleton getInstance(){
return hungrySingleton;
}
}
其它:
1)序列化破坏单例模式
单例类 HungrySingleton
package com.geely.design.pattern.creational.singleton;
import java.io.Serializable;
/**
* Created by geely
*/
public class HungrySingleton implements Serializable{
private final static HungrySingleton hungrySingleton;
static{
hungrySingleton = new HungrySingleton();
}
private HungrySingleton(){
}
public static HungrySingleton getInstance(){
return hungrySingleton;
}
}
测试类 Test
package com.geely.design.pattern.creational.singleton;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
/**
* Created by geely
*/
public class Test {
public static void main(String[] args) throws IOException, ClassNotFoundException {
HungrySingleton instance = HungrySingleton.getInstance();
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton_file"));
oos.writeObject(instance);
File file = new File("singleton_file");
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
HungrySingleton newInstance = (HungrySingleton) ois.readObject();
System.out.println(instance);
System.out.println(newInstance);
System.out.println(instance == newInstance);
}
}
运行Test控制台输出结果
com.geely.design.pattern.creational.singleton.HungrySingleton@5c647e05
com.geely.design.pattern.creational.singleton.HungrySingleton@4c873330
false
由于:Test类中的 ois.readObject()会通过反射创建对象,所以与原对象不同
修复序列化破坏单例代码 HungrySingleton,添加 readResolve方法
package com.geely.design.pattern.creational.singleton;
import java.io.Serializable;
/**
* Created by geely
*/
public class HungrySingleton implements Serializable{
private final static HungrySingleton hungrySingleton;
static{
hungrySingleton = new HungrySingleton();
}
private HungrySingleton(){
}
public static HungrySingleton getInstance(){
return hungrySingleton;
}
private Object readResolve(){
return hungrySingleton;
}
}
运行Test控制台输出结果
com.geely.design.pattern.creational.singleton.HungrySingleton@5c647e05
com.geely.design.pattern.creational.singleton.HungrySingleton@5c647e05
true
2)单例模式反射攻击
只针对饿汉式,即类加载就创建对象。
懒汉式无法解决反射攻击,原因在于,懒汉式无法控制反射创建对象和自身创建对象的顺序,如果先反射后自身创建,则会有不同对象,如果先自身创建,再反射创建,可以在私有构造器抛异常。
单例类 HungrySingleton
package com.geely.design.pattern.creational.singleton;
import java.io.Serializable;
/**
* Created by geely
*/
public class HungrySingleton implements Serializable{
private final static HungrySingleton hungrySingleton;
static{
hungrySingleton = new HungrySingleton();
}
private HungrySingleton(){
}
public static HungrySingleton getInstance(){
return hungrySingleton;
}
private Object readResolve(){
return hungrySingleton;
}
}
Test类
public class Test {
public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Class objectClass = HungrySingleton.class;
HungrySingleton newInstance = (HungrySingleton) constructor.newInstance();
HungrySingleton instance = HungrySingleton.getInstance();
System.out.println(instance);
System.out.println(newInstance);
System.out.println(instance == newInstance);
}
}
运行输出结果,反射出来了新实例
com.geely.design.pattern.creational.singleton.HungrySingleton@15db9742
com.geely.design.pattern.creational.singleton.HungrySingleton@6d06d69c
false
修复反射破坏,在HungrySingleton 私有构造方法添加逻辑
public class HungrySingleton implements Serializable{
private final static HungrySingleton hungrySingleton;
static{
hungrySingleton = new HungrySingleton();
}
private HungrySingleton(){
if(hungrySingleton != null){
throw new RuntimeException("单例构造器禁止反射调用");
}
}
public static HungrySingleton getInstance(){
return hungrySingleton;
}
private Object readResolve(){
return hungrySingleton;
}
}
运行输出结果,控制台抛异常
xception in thread "main" java.lang.reflect.InvocationTargetException
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at com.geely.design.pattern.creational.singleton.Test.main(Test.java:71)
Caused by: java.lang.RuntimeException: 单例构造器禁止反射调用
at com.geely.design.pattern.creational.singleton.HungrySingleton.<init>(HungrySingleton.java:17)
... 5 more
3)Enum枚举单例
解决序列化和反射攻击的最佳单例实现
3.1序列化例子
枚举类 EnumInstance
public enum EnumInstance {
INSTANCE;
private Object data;
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public static EnumInstance getInstance(){
return INSTANCE;
}
}
Test类
public class Test {
public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
EnumInstance instance = EnumInstance.getInstance();
instance.setData(new Object());
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton_file"));
oos.writeObject(instance);
File file = new File("singleton_file");
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
EnumInstance newInstance = (EnumInstance) ois.readObject();
System.out.println(instance.getData());
System.out.println(newInstance.getData());
System.out.println(instance == newInstance);
}
}
控制台输出结果
java.lang.Object@119d7047
java.lang.Object@119d7047
true
3.2反射攻击例子
Test类
public class Test {
public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Class objectClass = EnumInstance.class;
Constructor constructor = objectClass.getDeclaredConstructor(String.class,int.class);
constructor.setAccessible(true); //打开私有构造方法的private权限
EnumInstance instance = (EnumInstance) constructor.newInstance("Geely",666);
System.out.println(instance);
}
}
控制台输出结果
Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
at com.geely.design.pattern.creational.singleton.Test.main(Test.java:59)
[ERROR] Command execution failed.
3.3另外枚举类中还可以写方法供外部调用
枚举类 EnumInstance
public enum EnumInstance {
INSTANCE{
// 枚举类中声明方法
protected void printTest(){
System.out.println("Geely Print Test");
}
};
// 外部调用的话需要声明
protected abstract void printTest();
private Object data;
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public static EnumInstance getInstance(){
return INSTANCE;
}
}
Test类
public class Test {
public static void main(String[] args) {
EnumInstance instance = EnumInstance.getInstance();
instance.printTest();
}
}
控制台输出
Geely Print Test
4)容器单例
容器单例类hashmap
public class ContainerSingleton {
private ContainerSingleton(){}
private static Map<String,Object> singletonMap = new HashMap<String,Object>();
public static void putInstance(String key,Object instance){
if(StringUtils.isNotBlank(key) && instance != null){
if(!singletonMap.containsKey(key)){
singletonMap.put(key,instance);
}
}
}
public static Object getInstance(String key){
return singletonMap.get(key);
}
}
线程类
public class T implements Runnable {
@Override
public void run() {
ContainerSingleton.putInstance("object",new Object());
Object instance = ContainerSingleton.getInstance("object");
System.out.println(Thread.currentThread().getName()+" "+instance);
}
}
测试类
public class Test {
public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Thread t1 = new Thread(new T());
Thread t2 = new Thread(new T());
t1.start();
t2.start();
System.out.println("program end");
}
}
运行结果:
优点:对多个单例对象进行缓存统一管理;
缺点:线程不安全,hashmap在多线程不安全,可能会出现两个不同对象,或者创建对象两次,最后一个对象把前一个对象覆盖的现象。结果复现参考:1.1多线程不安全的懒汉式代码
5)ThreadLocal线程单例
ThreadLocalInstance类
public class ThreadLocalInstance {
private static final ThreadLocal<ThreadLocalInstance> threadLocalInstanceThreadLocal
= new ThreadLocal<ThreadLocalInstance>(){
@Override
protected ThreadLocalInstance initialValue() {
return new ThreadLocalInstance();
}
};
private ThreadLocalInstance(){
}
public static ThreadLocalInstance getInstance(){
return threadLocalInstanceThreadLocal.get();
}
}
线程类
public class T implements Runnable {
@Override
public void run() {
ThreadLocalInstance instance = ThreadLocalInstance.getInstance();
System.out.println(Thread.currentThread().getName()+" "+instance);
}
}
测试类
public class Test {
public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
System.out.println("main thread"+ThreadLocalInstance.getInstance());
System.out.println("main thread"+ThreadLocalInstance.getInstance());
System.out.println("main thread"+ThreadLocalInstance.getInstance());
System.out.println("main thread"+ThreadLocalInstance.getInstance());
System.out.println("main thread"+ThreadLocalInstance.getInstance());
System.out.println("main thread"+ThreadLocalInstance.getInstance());
Thread t1 = new Thread(new T());
Thread t2 = new Thread(new T());
t1.start();
t2.start();
System.out.println("program end");
}
}
运行结果
main threadcom.geely.design.pattern.creational.singleton.ThreadLocalInstance@15db9742
main threadcom.geely.design.pattern.creational.singleton.ThreadLocalInstance@15db9742
main threadcom.geely.design.pattern.creational.singleton.ThreadLocalInstance@15db9742
main threadcom.geely.design.pattern.creational.singleton.ThreadLocalInstance@15db9742
main threadcom.geely.design.pattern.creational.singleton.ThreadLocalInstance@15db9742
main threadcom.geely.design.pattern.creational.singleton.ThreadLocalInstance@15db9742
program end
Thread-0 com.geely.design.pattern.creational.singleton.ThreadLocalInstance@26484d1e
Thread-1 com.geely.design.pattern.creational.singleton.ThreadLocalInstance@33918ac4
文章内容参考自慕课网