Java单列模式(从0开始分析)

Java中的单例模式的资料已经有很多了,很优秀的也有许多,但是我的博客,还是为小白写的。(因为我也是小白啊!)希望你可以按我的步骤一起学习一下,我已经写的很详细了,写一遍大于看一遍!!!如果你是大神也不要紧,你可以看直接这篇博客(我大部分想法都来源于他),或者帮我看看我有没有错,也可以看看有没有用当年没有搞清楚的。----(写所有给看的人)最重要的是希望你能帮我看看哪里不对,或者有问题,不合理都可以告诉我,我们一起学习一下。(谢谢啦)

联系方式:QQ:1441289873

Java的的中有众多设计模式,其中最常用的就是单例模式。大家肯定看到许多单例模式和用过,接下我先说一下单例模式的区别和用法,然后从简单的开始学习。

单例模式:单例对象能保证在一个JVM中,该对象只有一个实例存在。

先简单说一下,各自的区别:

常见的五种单例模式实现方式主要:

  1. 饿汉式(线程安全,调用效率高;但是不能延时加载)
  2. 懒汉式(线程安全,调用效率不高,但是可以延时加载)

其他:

  1. 双重检测锁式(由于JVM底层内部模型原因,偶尔会出问题,不建议使用)
  2. 静态内部类式(线程安全,调用效率高,但是,可以延时加载)
  3. 枚举类(线程安全,调用效率高,防反射和反序列化,不能延时加载)如何选用?
  4. 单例对象占用资源少,不需要延时加载时:枚举类好于饿汉式 - 单例对象占用资源大,需要延时加载时:

静态内部类式好于懒汉式

资料来源

一、接下来从最简单的单例模式开始吧直接上代码:


public class Android {

   /*声明一个变量等于null,实现了延迟加载*/
    private static Android android = null;

    /*构造方法私有化,防止被new出来*/
    private Android() {

    }

     /*调用这个方法时判断对象不存在时创建对象*/

    public static Android getinstance() {
        if (android == null) {
            android = new Android();
        }
        return android;
    }

}

大部分看过单例文章,都知道这个单例线程不安全呗。为什么不安全呢,当时小白的我就没有搞懂,尤其是我这种自学的单例,只是抄别人的,什么都不懂。(哈哈)

哪里不安全呢,我给大家一段测试代码,你就可以看出来了。

public class ExampleUnitTest {


    @Test
    public void test() {
        MyThread myThread =new MyThread();
        MyThread myThread1 =new MyThread();
        myThread.start();
        myThread1.start();
    }

        class MyThread extends Thread {
            @Override
            public void run() {
                Android getinstance = Android.getinstance();
                System.out.println("多线程下: " + getinstance.toString());
            }
    }
}

我的第一次接下是测试结果:

多线程下:com.example.a14412.myapplication.Android@2957fcb0
多线程下:com.example.a14412.myapplication.Android@475cf196

有同学说了他的结果和我一样是这样的:

多线程下:com.example.a14412.myapplication.Android@1a9841c0
多线程下:com.example.a14412.myapplication.Android@1a9841c0

不要紧,我来解释一下原因。第一次的结果是这样来的。因为代码的执行速度很快,即便是两个线程。一个线程走到如果判断时,另一个也到了,如果判断,他们两个都得到对象是空的结果,就各自实例化了对象。(这也是线程不安全的原因)

第二次的结果原因大家应该理解了,就是说,一个线程快,一个线程慢,快的实例化后对象。第二个线程才判断,此时的对象已经被实例化过了,所以如果判断是不等于空。

线程的速度为什么会快慢呢,这时的你需要理解多线程去了。

2.加锁的单例模式

在多线程模式下,大家能想到第一个就是同步的,这个关键字吧。这个关键字我就不在这说了。直接上代码吧!

这个单例是为了弥补上面单例在多线程下实例了两个对象的不足。

public class Android {
    private static Android android = null;

    private Android() {
    }

    public synchronized static Android getinstance() {
        if (android == null) {
            android = new Android();
        }
        return android;
    }

}

对,基本没有变化只是加了一个同步的关键字而已看测试效果:

第一次:

多线程下:com.example.a14412.myapplication.Android@6b5b9135
多线程下:com.example.a14412.myapplication.Android@6b5b9135

第二次:
多线程下:com.example.a14412.myapplication.Android@1a9841c0

第三次

已经结束了,对你猜对了,第三次没有打印任何东西。你说你是不这样的,好吧。你先看看和我的代码一样吗?

如果一样的话,请多运行几次,看看。这三种结果,都遇到了吗?

遇到了,我们继续。

原因很简单啊,主线程先结束了,子线程执行不到打印的代码了。修改加个睡眠试试。

public class ExampleUnitTest {


    @Test
    public void test() {
        MyThread myThread =new MyThread();
        MyThread myThread1 =new MyThread();
        myThread.start();
        myThread1.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

        class MyThread extends Thread {
            @Override
            public void run() {
                Android getinstance = Android.getinstance();
                System.out.println("多线程下: " + getinstance.toString());
            }
    }
}

打印结果:

第一次:

多线程下:com.example.a14412.myapplication.Android@1a9841c0
多线程下:com.example.a14412.myapplication.Android@1a9841c0

第二次:

多线程下:com.example.a14412.myapplication.Android@6b5b9135
多线程下:com.example.a14412.myapplication.Android@6b5b9135

第三次:

多线程下:com.example.a14412.myapplication.Android@2608dd6c
多线程下:com.example.a14412.myapplication.Android@2608dd6c

这个单例,没有问题了?但是我们可以优化的。为什么要优化呢。从性能上看,当一个线程进入能得到同步锁,加上锁以后,第二个线程只能等待。如果这时那个线程已经实例化对象,由于有同步锁,另一个线程还要等待。小程序就没事了,如果程序大了。每次实例化这个对象,都要有些线程在等待。,就会很耗时和耗性能。上代码

3.双重检测锁式

public class Android {
    private static Android android = null;

    private Android() {
    }

    public  static Android getinstance() {
        if (android == null) {
            synchronized (Android.class) {
                if (android==null) {
                    android = new Android();
                }
            }
        }
        return android;
    }

}

双重如果判断,这是已经搞定了。但是这个单例还是不推荐。如果双重,容易出问题。在程序大了,代码复杂了,就不是这么简单的使用了。说不定会出现什么未知情况,比如死锁啊,等待。以上都是懒汉式是单例。(延时加载)想继续了解看这篇的Java的单例模式中双重检查锁的问题

那我也就不罗嗦了,写我最终版的单例模式吧!

4.内部类单例

先上代码:

public class Android {

    private Android() {
    }
    public static Android getAndroidHolder(){
        return AndroidHolder.android;
    }
    private static class AndroidHolder{
        private final static Android android=new Android();
    }

}

内部类的写法。在类里写个内部类。由于内部类是私有的只有Androi可以访问的。那怎么访问,这不是还有一个public static的方法吗?由于内部类是静的,就可以使用AndroidHolder.android获取的就是新的Android的这个对象了。

优点:线程安全,保证单例的唯一性,延迟了对象的实例化。

看测试代码:

public class ExampleUnitTest {


    @Test
    public void test() {
        MyThread myThread =new MyThread();
        MyThread myThread1 =new MyThread();
        myThread.start();
        myThread1.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

        class MyThread extends Thread {
            @Override
            public void run() {
                Android androidHolder = Android.getAndroidHolder();
                //打印的是地址值
                System.out.println("多线程下1: " + androidHolder.toString());
                //打印的是类名
                System.out.println("多线程下1: " + androidHolder.getClass().getName());
            }
    }

再看结果:

多线程下1:com.example.a14412.myapplication.Android@29abcaca
多线程下1:com.example.a14412.myapplication.Android
多线程下1:com.example.a14412.myapplication.Android@29abcaca
多线程下1 :com.example.a14412.myapplication.Android

单例模式的优点:

1.在内存中只有一个实例,减少内存开支

2.只生产一个实例,减少系统性能的性能开销

3.避免对资源的多重占用。

4.可以在系统设置全局的访问点,优化和共享资源访问。(例如可以设置一个单例类,负责所有数据表的映射处理)

单例的缺点;

1.单例一般没有接口,扩展很困难。

2.单例如果持有Context对象,很容易引起内存泄漏,最好传递全局的Application Context。
 

单例模式到这就结束了,安卓开发的有福利了。送你给插件,SingletonTest。

SingletonTest用法很简单,百度一下就好。

猜你喜欢

转载自blog.csdn.net/qq_41346910/article/details/82924611
今日推荐