单例模式
什么是单例模式 ?
确保程序中一个类只能被实例化一次,实现这种功能就叫单例模式
单例模式的好处是什么 ?
方便控制对象
节省资源减少浪费
怎么实现单例模式 ?
构造私有化
调用静态方法返回实例
确保对象的实例只有一个
常见的单例模式有哪些 ?
饿汉式
把对象创建好,需要使用的时候直接用就行饥肠辘辘 非常着急
懒汉式
由于饿汉式容易浪费资源,比如类里 有public static 修饰的一个方法 test(),即可不创建实例就可访问到
懒 不到万不得已不创建实例,什么时候用什么时候创建
懒汉式的缺点呢 ?
线程不安全 但是可以控制 如何控制,下面会讲
代码实现饿汉式
创建 Singleton.java 类
public class Singleton1 {
public static final Singleton1 INSTANCE = new Singleton1();
//构造方法私有化
private Singleton1(){
}
//get方式获取对象
public static Singleton1 getInstance(){
return INSTANCE;
}
}
创建 TestSingleton.java 类
public class TestSingleton{
public static void main(String[] args) {
Singleton1 st1 = Singleton1.INSTANCE;
Singleton1 st2 = Singleton1.getInstance();
//return true
System.out.println(st1.hashCode() == st2.hashCode());
//return true
System.out.println(st1 == st2);
}
}
代码实现懒汉式
创建 Singleton2.java 类
public class Singleton2 {
private static Singleton2 INSTANCE;
//构造方法私有化
private Singleton2(){
}
//get方式获取对象
public static Singleton2 getInstance(){
if (INSTANCE == null) {
INSTANCE = new Singleton2();
}
return INSTANCE;
}
}
x修改 TestSingleton.java 类
Singleton2 a = Singleton2.getInstance();
Singleton2 b = Singleton2.getInstance();
//ture
System.out.println(a == b);
注意: 再有的情况下懒汉式线程不安全比如多线程
举例说明 线程不安全
修改 Singleton2.java 类
public static Singleton2 getInstance(){
if (INSTANCE == null) {
try {
Thread.sleep((int) Math.random() * 100);
} catch (InterruptedException e) {
e.printStackTrace();
}
INSTANCE = new Singleton2();
}
return INSTANCE;
}
修改 TestSingleton.java 类
Callable c = new Callable() {
@Override
public Singleton2 call() throws Exception {
return Singleton2.getInstance();
}
};
//创建两个线程池
ExecutorService es = Executors.newFixedThreadPool(2);
Future f1 = es.submit(c);
Future f2 = es.submit(c);
Singleton2 a = f1.get();
Singleton2 b = f2.get();
/**
* 有可能两种情况
* 1. 判断是否一致:false
* 2. 判断是否一致:true
*/
System.out.println("判断是否一致:" + (a == b));
为什么会这样呢 ?
因为第一个线程 if (INSTANCE == null) 进去之后触发 sleep() 线程休眠
第二个线程就紧跟着 if (INSTANCE == null) 这时 INSTANCE 还是等于null,随后进入线程休眠
这样他两个都创建了实例,一共创建两次所以导致线程不安全
怎么解决线程不安全 ?
解决 懒汉式 线程不安全的方法
使用 synchronized 同步锁
使用静态内部类
使用 synchronized 同步锁
修改 Singleton2.java 类
public static Singleton2 getInstance(){
//提高效率 已经new过后不需要再等着资源
if (INSTANCE == null) {
//可以使用同步锁 完成线程安全
synchronized (Singleton1.class) {
if (INSTANCE == null) {
try {
Thread.sleep((int) Math.random() * 100);
} catch (InterruptedException e) {
e.printStackTrace();
}
INSTANCE = new Singleton2();
}
}
}
return INSTANCE;
}
再次运行 TestSingleton.java 测试 就会返回 true
但是还是有一个问题,第一个线程已经创建好了,第二个线程还需要再次进入 synchronized块等待资源的控制权.可以在外面加上 if 判断 INSTANCE == null 即可提高效率
使用静态内部类
因使用 synchronized 同步锁,代码看起来不雅观,所以可以使用静态内部类,达到线程安全
修改 Singleton2.java 类
public class Singleton2 {
//构造私有化
private Singleton2(){
}
//在内部类被加载时,才创建
//静态内部类 不会随着外部类加载,初始化 它是一个独立的 当用这个类时才会加载初始化
//在内部类加载和初始化,所以线程是安全的
private static class Inner{
private static final Singleton2 INSTANCE = new Singleton2();
}
//get 方式获取静态内部类的INSTANCE
public static Singleton2 getInstance(){
return Inner.INSTANCE;
}
}
小结
并不能说明 懒汉式 要强于 饿汉式,可以根据项目需求,来使用其中一种模式
再次推荐 保你面试必过的 java面试题
本文就先说到这里,有问题欢迎留言讨论