单例模式
单例模式的种类
-
饿汉式(静态常量)
-
饿汉式(静态代码块)
-
懒汉式(线程不安全)
-
懒汉式(线程安全,同步方法)
-
懒汉式(线程安全,同步代码块)
-
双重检查
-
静态内部类
-
枚举
1.饿汉式(静态常量)
优点:类装载的时候就完成了实例化,避免了线程同步问题
缺点:在类装载的时候就完成了实例化,没有达到lazy loading(懒加载,用时即加载)的效果,如果从始至终从未使用过这个实例就会造成内存浪费的问题
public class Singleton1 {
public static void main(String[] args) {
Singleton instance = Singleton.getInstance();//通过公开的getInstance获得实例
System.out.println(instance.hashCode());//1163157884
Singleton instance2 = Singleton.getInstance();//通过公开的getInstance获得实例
System.out.println(instance2.hashCode());//1163157884
System.out.println(instance==instance2);//true 返回的是同一对象
}
}
//饿汉式(静态变量)
class Singleton{
//1.构造器私有化,外部不能new
private Singleton(){
}
//2。本类内部创建对象实例
private final static Singleton instance=new Singleton();
//3. 提供一个公有的静态方法,返回实例对象
public static Singleton getInstance() {
return instance;
}
}
2.饿汉式(静态代码块)
这种方式和饿汉式(静态常量)方式类似,在类装载的时候就执行静态代码块中的代码,优缺点和上面一样
public class singleton2 {
public static void main(String[] args) {
Singleton instance1 = Singleton.getInstance();
System.out.println(instance1.hashCode());//1163157884
Singleton instance2 = Singleton.getInstance();
System.out.println(instance2.hashCode());//1163157884
System.out.println(instance1==instance2);//true
}
}
class Singleton{
private Singleton(){
}
private static Singleton instance;
static {
instance=new Singleton();
}
public static Singleton getInstance(){
return instance;
}
}
3.懒汉式(线程不安全)
优点:起到了懒加载的效果,但只能在单线程下使用
缺点:多线程下,会有多个线程抢夺一个资源,当判断是否为空时线程A还没有实例化B线程就进入,这样会产生多个实例
public class Singleton3 {
public static void main(String[] args) {
Singleton instance=Singleton.getInstance();
Singleton instance2=Singleton.getInstance();
System.out.println(instance.hashCode());//1163157884
System.out.println(instance2.hashCode());//1163157884
System.out.println(instance==instance2);//true
}
}
class Singleton{
private static Singleton instance;
//只有当使用到这个类的时候才会去创建它,不为空的时候就
public static Singleton getInstance(){
if(instance==null){
instance=new Singleton();
}
return instance;
}
}
4.懒汉式(线程安全,同步方法)
优点:线程安全
缺点:效率低,一个线程实例化后其他线程直接拿取即可,没必要每个线程都去同步方法中获取
public class Singleton4 {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(()->{
Singleton instance = Singleton.getInstance();
System.out.println("instance:"+instance.hashCode());
}).start();
}
}
/**
* 线程不安全的情况下:
* instance:556244866
* instance:2132637868
* instance:1846890052
* instance:2132637868
* instance:2132637868
* instance:2132637868
* instance:2132637868
* instance:2132637868
* instance:2132637868
* instance:2132637868
*
*线程安全情况下
* instance:463237198
* instance:463237198
* instance:463237198
* instance:463237198
* instance:463237198
* instance:463237198
* instance:463237198
* instance:463237198
* instance:463237198
* instance:463237198
*/
}
class Singleton extends Thread{
private static Singleton instance;
private Singleton(){}
//synchronized同步方法 保证线程排队有序获取
public static synchronized Singleton getInstance(){
if(instance==null){
instance=new Singleton();
}
return instance;
}
}
5.懒汉式(同步代码块)
这种方式,是对懒汉式(同步方法)的一种改进,
但这种同步并不能起到线程同步的作用,当一个线程进入了if(instance == null) 判断语句,还没有进行实例化,另一个线程也进入if(instance == null)判断语句,这样也会产生多个实例
不能使用,线程不安全
class Singleton extends Thread{
private static Singleton instance;
private Singleton(){}
//同步方法块
public static Singleton getInstance(){
if(instance==null){
synchronized (Singleton.class){
instance=new Singleton();
}
}
return instance;
}
}
6.双重检查
优点:双重检查,同步代码块外检查一遍,同步方法内检查一遍,可以防止反复进行方法同步和多次实例
线程安全,延迟加载,效率较高,推荐使用
class Singleton{
//保证数据的可见性,有序性,防止指令重排
private static volatile Singleton instance;
private Singleton(){}
public static Singleton getInstance() {
if(instance==null){
synchronized (Singleton.class){
if(instance==null){
return instance=new Singleton();
}
}
}
return instance;
}
}
volatile介绍
**参考博客:**https://www.cnblogs.com/zhengbin/p/5654805.html
-
JMM内存模型volatile保证数据的可见性,有序性,不保证原子性
volatile:他修饰的变量不允许线程内部缓存和重排序
可见性:是指线程之间的可见性,一个线程修改的状态对另一个线程是可见的,保证新值能立即同步到主内存中,以及使用前立即从主内存中刷新
有序性:volatile禁止指令重排优化
指令重排:cpu采取了允许多条指令不按程序规定的顺序分发送给各响应的电路单元处理
例子:
实例化一个对象步骤为:
1)分配instance内存 2)getInstance()初始化instance
3)将instance指向分配的内存地址
当单个线程时,他走的不一定是123,有可能是132
多个线程时,假如有一个线程二突然过来,他发现instance已经有了指向的内存,他就直接将instance返回,这个instance没有完成实例化,会返回一个null
发生这种问题,主要的原因是重排序。重排序是指编译器和处理器为了优化程序性能而对指令序列进行重新排序的一种手段。
7.静态内部类
静态内部类的特点
1.当包含静态类的外部类装载时静态内部类不会被装载
2.当调用静态内部类时才会进行静态内部类的装载
1)静态内部类的方式采用了类装载机制保证初始化实例只有一个线程
2)静态内部类方式在Singleton类被装载时并不会立即实例化,而是需要实例化的时候调用getInstance方法才会装载SingletonInstance类,从而完成Singleton的实例化
3)类的静态属性只会在第一次加载类的时候初始化,类的加载线程是安全的
优点:避免了线程不安全,利用静态内部类的特点实现延迟加载,效率高 推荐使用
class Singleton{
//保证数据的可见性,有序性,防止指令重排
private static volatile Singleton instance;
private Singleton(){}
//静态内部类
private static class SingletonInstance{
private static final Singleton INSTANCE=new Singleton();
}
public static Singleton getInstance() {
return SingletonInstance.INSTANCE;
}
}
8.枚举
以上的方式都有一个缺点:使用反射即可破解
那什么不可以被反射破解?
反射的源码已告诉我们:
枚举方式的优点:
避免多线程同步问题,还能防止反序列化重新创建新的对象
推荐使用
public class Singleton8 {
public static void main(String[] args) {
Singleton instance = Singleton.INSTANCE;
Singleton instance2 = Singleton.INSTANCE;
System.out.println(instance==instance2);
}
}
enum Singleton{
INSTANCE;
public void sayOK(){
System.out.println("ok~");
}
}
单例模式在JDK应用中的源码分析
在我们JDK中 java.lang.Runtime就是经典的单例模式
饿汉式
单例模式的注意事项
- 使用场景:需要频繁进行创建对象和销毁对象,创建对象时耗时过多或消耗资源过多(重量级对象),但又经常用到的对象,像工具类对象,频繁访问数据库文件对象(比如数据源,session工厂等)