文章目录
1.饿汉模式(可以使用)
先来第一种常见饿汉模式:
//饿汉式
class Dog{
private Dog() {
}
private final static Dog instance = new Dog();
public static Dog createDog(){
return instance;
}
}
构造器私有化是为了不让你像平时一样 Dog dog = new Dog(); 这样new一个对象。
private final static Dog instance = new Dog();这种方式在类加载时就完成了初始化。
我们要用的话只需要 类名.静态方法名即可。
public class DanLi {
public static void main(String[] args) {
Dog dog = Dog.createDog();
Dog dog2 = Dog.createDog();
System.out.println(dog == dog2); //结果 true
System.out.println(dog.hashCode()); //结果 356573597
System.out.println(dog2.hashCode()); //结果 356573597
//这里可以看两次的hashCode也是相同的,说明用的都是同一个
}
}
2.饿汉式 (静态代码块,可以使用)
饿汉式还有一种就是放在静态代码块里的写法,其实跟第一种差不多。
//饿汉式 (静态代码块)
class Cat{
private static Cat cat;
static {
cat = new Cat();
}
private Cat() {}
public static Cat createDog(){
return cat;
}
}
以上这两种方法都是饿汉模式,它是在类加载时就完成了初始化,所以类加载较慢,也就是说不管你用没用到它,它都已经创造了一个对象等着你用,即使你全程都没有用到它,这就是饿汉式,是不是感觉跟它的名字很合得来。它也有一个小缺点,会造成内存的浪费。
3.懒汉式 (线程不安全)
class Cat2{
private static Cat2 cat2;
private Cat2() {
}
public static Cat2 createDog(){
if (cat2 == null){
cat2 = new Cat2();
}
return cat2;
}
}
那懒汉式就是反着来了,它一开始没有给你创建好对象,等你需要用,调用它的时候才给你创建。就像咱们平时完成老师布置的作业,有些人当天就完成了老师布置的作业,等到要交的时候也能立马拿出来;有些人要等到交作业截止时间到了才想起有作业,不过最终结果都是大家都交了作业,最终都能给你创建好一个对象。它是在静态方法里加了一层判断,判断它是不是为空,在单线程下第一次创建一个cat2对象 if 判断的结果为true,new了一个对象;当第二次调用在进来的时候,if判断结果为false所以就不会再去new一个新的对象,这就满足了单例模式的基本需求。
再仔细想想它其实是有局限性的,在多线程下的话会出现线程安全问题,可以来分析一下: 第一个线程进来createDog方法,if判断结果为true,没问题,进入if,但是它还没来得及往下执行,也就是没有创建对象,此时第二个线程进来了由于第一个线程没有执行new Cat2();第二个线程进入if的判断结果为true,第二个线程也进来了并且执行了new语句一路往下走,第一个线程的下一步也是new语句,等第一个也走完这样就有了两个对象了,虽然达到懒加载的效果但是就不是单例模式了。开发过程中也不会用这种方式。
4.懒汉式 (解决了线程不安全问题,效率低)
上一方法不是存在安全问题嘛,欧克好办,那加一个锁总可以了吧,就在方法上加一个synchronized 关键字,其他不动。
class Cat3{
private static Cat3 cat3;
private Cat3() {
}
public synchronized static Cat3 createDog(){
if (cat3 == null){
cat3 = new Cat3();
}
return cat3;
}
}
这样确实解决了线程安全问题,在开发过程中createDog方法肯定是会经常用的,在整个方法上进行同步会导致效率太低了。
5.懒汉式 (不太靠谱,线程不安全)
上一个方法在方法上加synchronized不是效率太低嘛,欧克好办,搞一个同步代码块总可以了吧。
class Cat4{
private static Cat4 cat4;
private Cat4() {
}
public static Cat4 createDog(){
if (cat4 == null){
synchronized (Cat4.class){
cat4 = new Cat4();
}
}
return cat4;
}
}
其实仔细分析一下这种方法更太靠谱,它连线程安全都没有保证,可以套用第三种懒汉式的分析逻辑来分析,不难发现此方法存在线程安全问题。
6.懒汉式 (线程安全,双重检查,推荐使用)
上一方法不是存在安全问题嘛,欧克好办,再加一层判断,双重检查,这总行了吧。
//volatile 解决双重检查指令重排序问题
class Cat5{
private static volatile Cat5 cat5;
private Cat5() {
}
public static Cat5 createCat(){
if (cat5 == null){
synchronized (Cat5.class){
if (cat5 == null){
cat5 = new Cat5();
}
}
}
return cat5;
}
}
当两个线程都在第一个if里面的时候,由于同步代码块里面只能进一个线程,所以不管其中哪个线程创建好了对象,另一个线程会再次判断cat5是否为空。再下一次调用createCat也不用担心,它会在第一层判断就挡住了,结果为false直接return ,这样就不用再次进入同步代码块里面去,是不是比上一个高效。
7.懒汉式 (线程安全,静态内部类,推荐使用)
还有一种就是运用了静态内部类的方法。
class Cat6{
private Cat6() {
}
private static final class CatInstance{
private static final Cat6 CAT6= new Cat6();
}
public static Cat6 createDog(){
return CatInstance.CAT6;
}
}
它是利用了类加载的机制保证了初始化实例时只有一个线程,JVM帮助我们保证了线程的安全性,类Cat6装载的时候,此静态内部类并不会马上被装载,等碰到使用了才会被装载,在装载类的时候是线程安全的。
8.枚举(线程安全,推荐使用)
还有一种用枚举的方法。
//枚举
enum Cat7{
Instance;
public static void say(){
System.out.println("Hello");
}
}
public class DanLi {
public static void main(String[] args) {
Cat7 instance = Cat7.Instance;
Cat7 instance2 = Cat7.Instance;
System.out.println(instance == instance2); //true
Cat7.sayOk(); //输出:Hello
}
}
总结
单例模式主要还是分为饿汉式和懒汉式两种,每种都有不同的写法和各自的优缺点。懒汉式有好多种,上述也算讲了一步一步的改进演化过程,真正开发能用的也没几种,在开发的过程中还需要自己衡量使用哪种才能达到最好的效果。