Java中的单例模式的资料已经有很多了,很优秀的也有许多,但是我的博客,还是为小白写的。(因为我也是小白啊!)希望你可以按我的步骤一起学习一下,我已经写的很详细了,写一遍大于看一遍!!!如果你是大神也不要紧,你可以看直接这篇博客(我大部分想法都来源于他),或者帮我看看我有没有错,也可以看看有没有用当年没有搞清楚的。----(写所有给看的人)最重要的是希望你能帮我看看哪里不对,或者有问题,不合理都可以告诉我,我们一起学习一下。(谢谢啦)
联系方式:QQ:1441289873
Java的的中有众多设计模式,其中最常用的就是单例模式。大家肯定看到许多单例模式和用过,接下我先说一下单例模式的区别和用法,然后从简单的开始学习。
单例模式:单例对象能保证在一个JVM中,该对象只有一个实例存在。
先简单说一下,各自的区别:
常见的五种单例模式实现方式主要:
- 饿汉式(线程安全,调用效率高;但是不能延时加载)
- 懒汉式(线程安全,调用效率不高,但是可以延时加载)
其他:
- 双重检测锁式(由于JVM底层内部模型原因,偶尔会出问题,不建议使用)
- 静态内部类式(线程安全,调用效率高,但是,可以延时加载)
- 枚举类(线程安全,调用效率高,防反射和反序列化,不能延时加载)如何选用?
- 单例对象占用资源少,不需要延时加载时:枚举类好于饿汉式 - 单例对象占用资源大,需要延时加载时:
静态内部类式好于懒汉式
一、接下来从最简单的单例模式开始吧直接上代码:
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用法很简单,百度一下就好。