1、饿汉式
这种方式下,只要类加载了,单例对象就会产生,并且唯一(final),利用类加载器解决了线程安全问题,因此,饿汉式是线程安全的。写法有两种
1.1 静态常量
//饿汉式单例模式1(静态常量)
class Singleton{
//构造方法私有化,外部不能new这个对象,想要获得这个类的对象只能调用他的静态方法,且对象只能有一个。
private Singleton(){}
//本类内部创建对象,final修饰符可以做一个优化
private final static Singleton instance = new Singleton();
//对外开放的静态方法
public static Singleton getInstance(){
return instance;
}
}
1.2静态代码块
在静态代码块中为单例对象进行赋值。静态代码块中的内容也是在类加载时执行。
class Singleton{
//构造方法私有化
private Singleton(){}
//静态变量
private final static Singleton instance;
//静态代码块,初始化静态变量
static {
instance = new Singleton();
}
public static Singleton getInstance(){
return instance;
}
}
2、懒汉式
懒汉式单例模式,就是只有在用到这个对象的时候,才会去创建该对象,不使用就不会创建,虽然这种方式可以避免对象不使用而造成的内存浪费问题,但是该方式有一个很大的问题,那就是线程不安全。如果多个线程同时去访问创建对象的方法,有多个线程同时通过了判空的条件,那么就会产生多个对象实例,这就违背了单例模式.
2.1 线程不安全的懒汉式
class Singleton{
//懒汉式单例模式(线程不安全的)
//构造器私有化
private Singleton(){
}
//创建静态常量,不进行初始化
private static Singleton instance;
//只有调用这个方法的时候,才对上面的静态常量初始化
public static Singleton getInstance(){
//加上一个判断条件,要是实例没有初始化,就进行初始化,否则直接返回就可以了。
//但是这样不适用于多线程,如果一个线程在进入到这个判断语句,还没有往下执行,另一个线程也进来了,那么就会
//产生多个实例,违反了单例模式的规则。
if (instance == null){
instance = new Singleton();
}
return instance;
}
}
2.2 线程安全的懒汉式写法(同步方法)
需要在获取实例的方法上加上一个synchronized关键字,使线程同步,就是说,如果有一个线程正在访问这个方法,那么此时也来访问这个方法的线程就会在外面等待,这样可以保证获取对象实例的方法每次只会有一个线程访问,从而解决产生多个对象实例的问题。这种方式效率比较低,实际开发中不推荐使用。
class Singleton{
//懒汉式单例模式2(线程安全,同步方法),方法加上synchronized关键字
//构造方法私有化,防止从外部直接new
private Singleton(){}
//
private static Singleton instance;
//同步方法,解决线程安全的问题,但是效率会降低,因为每个线程访问这个方法的时候都要等待上一个线程调用完毕之后才能调用
public static synchronized Singleton getInstance(){
if (instance == null){
instance = new Singleton();
}
return instance;
}
}
3、双重检查
volatile关键字可以立即让被修饰的变量,只要有修改值,则立即更新到主存
被volatile关键字修饰的变量,如果值发生了变更,其他线程立马可见,避免出现脏读的现象。
这种方式在实际开发中可以使用, 推荐。
//双重检查
class Singleton{
//构造方法私有化
private Singleton(){}
//volatile关键字可以让变量的修改值立即更新到主存中
private static volatile Singleton instance;
//双重检查,线程安全,同时解决懒加载问题,也可以有效解决线程同步的效率问题
//推荐使用
public static Singleton getInstance(){
if (instance == null){
//在这个位置先排队,如果里面的值发生了改变,则其他线程是立即可见的,所以在进去判断就会为非空,之后再访问这个方法的线程
//在第一行的判断条件那里就会被控制住,因此也不会排队了。同时解决了同步方法效率低和线程安全的问题
synchronized (Singleton.class){
if (instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}
4、静态内部类
//静态内部类的方式实现单例模式,推荐使用的方式
class Singleton{
//构造方法私有化
private Singleton(){}
//写一个静态内部类,该类中有一个静态属性 Singleton,该类在外部类加载的时候不会被加载,实现了懒加载的效果
private static class SingletonInstance{
private final static Singleton INSTANCE = new Singleton();
}
//提供一个静态共有方法,直接返回内部类中的静态属性
//当调用这个方法的时候,JVM才会加载这个静态内部类,并且在JVM加载静态内部类的时候,是线程安全的
//所以我们利用了JVM的底层机制来实现了线程安全
public static Singleton getInstance(){
return SingletonInstance.INSTANCE;
}
}
5、枚举
class Singleton{
public static void main(String[] args]){
SingletonInstance instance1 = SingletonInstance.INSTANCE;
SingletonInstance instance2 = SingletonInstance.INSTANCE;
System.out.println(instance1 == instance2); //true
//输出结果为true,说明这俩是一个对象。
}
}
enum SingletonInstance{
//在枚举中只定义一个属性,这样就可以保证单例了
INSTANCE;
//下面的方法都是功能实现.....
public void say(){
System.out.println("Hello,枚举");
}
。。。
。。。
}
------------------------这是一条分割线------------------------
通过单例模式,我们可以自然而然的推广到多例模式,其实多例模式和单例模式没有什么本质区别,只不过是限制对象创建的个数不同而已,单例模式限制只有一个对象,多例模式限制只能创建 2个 3个 4个 …对象,区别就是个数不同
就像线程池和数据库的连接池一样,池中有固定数量的线程对象或者是连接对象,用完这个对象还还到池中,可以循环利用,这样就可以避免反复的创建和销毁对象造成的性能浪费。