文章最前: 我是Octopus,这个名字来源于我的中文名--章鱼;我热爱编程、热爱算法、热爱开源。所有源码在我的个人github ;这博客是记录我学习的点点滴滴,如果您对 Python、Java、AI、算法有兴趣,可以关注我的动态,一起学习,共同进步。
相关文章:
- Command命令设计模式
- Singleton单例设计模式
- Decorator装饰设计模式
- Template模板模式
- Strategy策略设计模式
- Proxy代理设计模式
- Factory工厂类设计模式
- 设计模式的6大基本原则
文章目录:
2.懒汉式:
单例设计模式确保一个类只有一个实例,并提供一个全局访问点。所谓单例,指的就是单实例,有且仅有一个类实例,这个单例不应该由人来控制,而应该由代码来限制,强制单例。单例有其独有的使用场景,一般是对于那些业务逻辑上限定不能多例只能单例的情况,例如:类似于计数器之类的存在,一般都需要使用一个实例来进行记录,若多例计数则会不准确。
1.饿汉式:
public class Singleton {
private static Singleton singleton = new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return singleton;
}
}
饿汉式单例 (线程安全的单例设计模式):
它是在类加载的时候就立即初始化,并且创建单例对象;
优点:没有加任何的锁、执行效率比较高,在用户体验上来说,比懒汉式更好;
缺点:类加载的时候就初始化,不管你用还是不用,我都占着空间,浪费了内存资源;
绝对线程安全,在线程还没出现以前就是实例化了,不可能存在访问安全问题;
2.懒汉式:
public class Singleton {
private static Singleton singleton;
private Singleton() {}
public static Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
但是它是线程不安全的单例设计模式,要把它改为线程安全的单例设计模式,需要加上同步关键字(synchronized)。
线程安全的懒汉式2:
public class Singleton {
private static Singleton singleton;
private Singleton() {}
public static synchronized Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
这种线程安全的懒汉式,要加锁,但是效率太低了,每次都需要加锁。
一种效率高且线程安全的懒汉式3:
public class Singleton {
private volatile static Singleton singleton;
private Singleton() {}
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
其中使用volatile关键字,在高并发的场景下,当一个线程实例化了之后,volatile关键字 ,将该线程实例化,让其他线程可以看到,提供程序的效率。
一个利用内部类使用的线程安全的懒汉式4:
public class LazyThree {
private boolean initialized = false;
// 默认使用LazyThree的时候,会先初始化内部类
// 如果没使用的话,内部类是不加载的
private LazyThree(){
synchronized (LazyThree.class){
if(initialized == false){
initialized = !initialized;
}else{
throw new RuntimeException("单例已被侵犯");
}
}
}
//每一个关键字都不是多余的
//static 是为了使单例的空间共享
//保证这个方法不会被重写,重载
public static final LazyThree getInstance(){
// 在返回结果以前,一定会先加载内部类
return LazyHolder.LAZY;
}
//默认不加载
private static class LazyHolder{
private static final LazyThree LAZY = new LazyThree();
}
}
懒汉式单例(使用内部类的方式)5:
特点:在外部类被调用的时候内部类才会被加载
内部类一定是要在方法调用之前初始化,巧妙地避免了线程安全问题;这种形式兼顾饿汉式的内存浪费,也兼顾synchronized性能问题;完美地屏蔽了这两个缺点;史上最牛B的单例模式的实现方式。
测试线程安全性问题:
public class ThreadSafeTest {
public static void main(String[] args) {
int count = 200;
//发令枪,我就能想到运动员
CountDownLatch latch = new CountDownLatch(count);
long start = System.currentTimeMillis();
for (int i = 0; i < count; i++) {
new Thread() {
@Override
public void run() {
try {
try {
// 阻塞
// count = 0 就会释放所有的共享锁
// 万箭齐发
latch.await();
} catch (Exception e) {
e.printStackTrace();
}
//必然会调用,可能会有很多线程同时去访问getInstance()
Object obj = LazyOne.getInstance();
System.out.println(System.currentTimeMillis() + ":" + obj);
} catch (Exception e) {
e.printStackTrace();
}
}
}.start(); //每循环一次,就启动一个线程,具有一定的随机性
//每次启动一个线程,count --
latch.countDown();
}
long end = System.currentTimeMillis();
System.out.println("总耗时:" + (end - start));
// CountDownLatch 并不是这样子用,实际应用场景中不要学老师这样投机取巧
// Color.INSTANCE.getInstance();
}
}
3.注册式设计模式(Spring中就是利用这种方式):
//Spring中的做法,就是用这种注册式单例
public class BeanFactory {
private BeanFactory() {
}
//线程安全
private static Map<String, Object> ioc = new ConcurrentHashMap<String, Object>();
public static Object getBean(String className) {
if (!ioc.containsKey(className)) {
Object obj = null;
try {
obj = Class.forName(className).newInstance();
ioc.put(className, obj);
} catch (Exception e) {
e.printStackTrace();
}
return obj;
} else {
return ioc.get(className);
}
}
}
4.枚举类单例模式:
public enum RegiterEnum {
INSTANCE,BLACK,WHITE;
public void getInstance(){}
}
测试枚举单例模式:
public class ColorTest {
public static void main(String[] args) {
System.out.println(Color.RED);
}
}
5.序列化与反序列化单例模式:
//反序列化时导致单例破坏
public class Seriable implements Serializable {
public final static Seriable INSTANCE = new Seriable();
private Seriable(){}
public static Seriable getInstance(){
return INSTANCE;
}
private Object readResolve(){
return INSTANCE;
}
}
测试序列化与反序列化模式:
public class SeriableTest {
public static void main(String[] args) {
Seriable s1 = null;
Seriable s2 = Seriable.getInstance();
FileOutputStream fos = null;
try {
fos = new FileOutputStream("Seriable.obj");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(s2);
oos.flush();
oos.close();
FileInputStream fis = new FileInputStream("Seriable.obj");
ObjectInputStream ois = new ObjectInputStream(fis);
s1 = (Seriable)ois.readObject();
ois.close();
System.out.println(s1);
System.out.println(s2);
System.out.println(s1 == s2);
} catch (Exception e) {
e.printStackTrace();
}
}
}
序列化:
就是说把内存中的状态通过转换成字节码的形式
从而转换一个IO流,写入到其他地方(可以是磁盘、网络IO)
内存中状态给永久保存下来了
反序列化:
讲已经持久化的字节码内容,转换为IO流
通过IO流的读取,进而将读取的内容转换为Java对象
在转换过程中会重新创建对象new
jdk中的Runtime类其实也是一种单例模式;Spring单例是指一个Spring Bean容器(ApplicationContext)中仅有一个实例。