单例模式
下面给大家分享面试必问8大设计模式中的第三种:单例模式
大家如果被面试官问到单例模式,只需回答下面的内容,并分析其中的几个关键点即可.
单例模式分两类:
1.开发使用饿汉式
2.但是懒汉式是必须会写的
要求解释未加锁版的缺陷,和解决方法(加锁)-并解释两个判断的用意
代码以及讲解详情:
/**
* 饿汉式
*/
class Sigleton_E {
//私有化,静态对象,外界拿不到
private static final Sigleton_E s = new Sigleton_E();
//"私有化"构造函数,外界不能创建
private Sigleton_E() {
}
//提供公共,静态的方法获取实例,不管怎么样获取的都是上面new好的
public static Sigleton_E getInstance() {
return s;
}
}
/**
* 懒汉式,注意需要解决线程安全问题
*/
class Sigleton_L {
//私有化,静态对象,外界拿不到
private static Sigleton_L s = null;//先声明引用,不创建
//"私有化"构造函数,外界不能创建
private Sigleton_L() {
}
//提供公共,静态的方法获取实例
public static Sigleton_L getInstance() {
if(s==null){
s = new Sigleton_L();
}
return s;
}
}
/**
* 改装版,加上锁
*/
class Sigleton_L_L {
//私有化,静态对象,外界拿不到
private static Sigleton_L_L s = null;//先声明引用,不创建
//"私有化"构造函数,外界不能创建
private Sigleton_L_L() {
}
//提供公共,静态的方法获取实例,+双重判断
public static Sigleton_L_L getInstance() {
//为空,单线程锁住,第二次不为空就进不来了
if (s == null) {
synchronized (Sigleton_L_L.class) {
//还为空,就创建,
//1.多个线程的时候,如果s==null,同时多个进来,那么碰到锁,只能一个再进这层,为空就创建,创建完毕
//2.创建完毕后,s!=null,再到刚刚进到锁面前的其他线程,再进来一个,发现不是null了,所以不创建
if (s == null) {
s = new Sigleton_L_L();
}
}
}
//不为空就返回第一次那个
return s;
}
}
//测试类:
public class Test {
public static void main(String[] args) {
/*--------------非多线程情况--------------*/
/*--------------饿汉式-----------------*/
Sigleton_E e1 = Sigleton_E.getInstance();//不能new而是通过公共方法获取
Sigleton_E e2 = Sigleton_E.getInstance();
System.out.println(e1);
System.out.println(e2);//打印结果:是同一个对象,标准单例.
*--------------未改装版懒汉式-----------------*/
Sigleton_L l1 = Sigleton_L.getInstance();
Sigleton_L l2 = Sigleton_L.getInstance();
System.out.println(l1);
System.out.println(l2);//打印结果:同一个对象,符合.
/*--------------改装版懒汉式-----------------*/
Sigleton_L_L ll1 =Sigleton_L_L.getInstance();
Sigleton_L_L ll2 =Sigleton_L_L.getInstance();
System.out.println(ll1);
System.out.println(ll2);//发现两个打印的是:是同一个对象
/*------------多线程版本--------------*/
}
}
打印结果:
--------------------饿汉式----------------------
com.example.Sigleton_E@efb78af
com.example.Sigleton_E@efb78af
--------------------未改装版懒汉式----------------------
com.example.Sigleton_L@1fb030d8
com.example.Sigleton_L@1fb030d8
--------------------装版懒汉式----------------------
com.example.Sigleton_L_L@18a9fa9c
com.example.Sigleton_L_L@18a9fa9c
下面我们来测试多线程情况下的问题:
- 首先饿汉式:
public class Single_E {
private static Single_E s = new Single_E();
private Single_E() {
}
public static Single_E getInstance() {
return s;
}
public static void main(String[] args) {
//开启十个线程,方便看出对象的情况
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
Single_E s = Single_E.getInstance();
System.out.println(s);
}
}).start();
}
}
}
打印结果:
com.company.Single_E@9a7d59b
com.company.Single_E@9a7d59b
com.company.Single_E@9a7d59b
com.company.Single_E@9a7d59b
com.company.Single_E@9a7d59b
com.company.Single_E@9a7d59b
com.company.Single_E@9a7d59b
com.company.Single_E@9a7d59b
com.company.Single_E@9a7d59b
com.company.Single_E@9a7d59b
全部一样,证明饿汉式线程安全
- 未改装版懒汉式:
public class Single_L {
private static Single_L s = null;
private Single_L() {
}
public static Single_L getInstance() {
//既然这里是最容易出现的问题,那么我们就在模拟
if (s == null) {
//一个线程进入后,先休眠,然后其他线程相继进入if判断语句
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//这样就得到多个结果了
s = new Single_L();
}
return s;
}
public static void main(String[] args) {
//开启十个线程,方便看出对象的情况
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
Single_L s = Single_L.getInstance();
System.out.println(s);
}
}).start();
}
}
}
打印结果:
com.company.Single_L@6f580c3a
com.company.Single_L@7d8aecf1
com.company.Single_L@2bca029b
com.company.Single_L@5b712492
com.company.Single_L@61181c24
com.company.Single_L@75dfb148
com.company.Single_L@30f02a6d
com.company.Single_L@67717334
com.company.Single_L@49428ffa
com.company.Single_L@30fa8ba9
各个对象不同,说明"懒汉式"非再装版线程不安全
- “改装版懒汉式”
public class Single_L_L {
private static Single_L_L s = null;
private Single_L_L() {
}
public static Single_L_L getInstance() {
if (s == null) {
try {
//同样模拟一个进来
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 加锁
synchronized (Single_L_L.class) {
if (s == null) {
s = new Single_L_L();
}
}
}
return s;
}
public static void main(String[] args) {
// 开启十个线程,方便看出对象的情况
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
Single_L_L s = Single_L_L.getInstance();
System.out.println(s);
}
}).start();
}
}
}
打印结果:
com.company.Single_L_L@7d8aecf1
com.company.Single_L_L@7d8aecf1
com.company.Single_L_L@7d8aecf1
com.company.Single_L_L@7d8aecf1
com.company.Single_L_L@7d8aecf1
com.company.Single_L_L@7d8aecf1
com.company.Single_L_L@7d8aecf1
com.company.Single_L_L@7d8aecf1
com.company.Single_L_L@7d8aecf1
com.company.Single_L_L@7d8aecf1
得出结论:"改装版(加锁版)的懒汉式"是线程安全的.
使用拓展
开发的时候我们经常用到很多工具类,绝大部分工具类都不会做成单例,那么如果我想做成单例的话,一个类中的内部类和同级的外部类调用单例工具时将会只是一个单例,使用其中的方法将会是叠加的.所以一般工具类不做成单例,除非是设计数据方面细节问题.
下面举两个例子:
包下的Test1类:
public class Test1 {
public static void main(String[] args) {
/*--------------第一次使用工具--------------*/
SingleTool s = SingleTool.getInstance();
System.out.println("s1 = "+s);
s.add(123);
s.add(true);
s.add("abc");
s.sout();
/*--------------第二次使用工具--------------*/
SingleTool s2 = SingleTool.getInstance();
System.out.println("s2 = "+s2);
/*--------------"同一个程序中"另一类(内部类)里再使用工具--------------*/
Test1_in.use();
/*--------------"同一个程序中"另一类(同级)再使用工具--------------*/
Test1_1.meth();
/*--------------"同包下"的类使用工具--------------*/
Test2.use();
}
//内部类
static class Test1_in {
static void use() {
SingleTool s = SingleTool.getInstance();
System.out.println("s3 = "+s);
s.sout();
}
}
}
//同级外部类
class Test1_1 {
static void meth() {
SingleTool s = SingleTool.getInstance();
System.out.println("s4 = "+s);
}
}
打印结果:
s1 = com.company.SingleTool@6a75d65c
123
true
abc
s2 = com.company.SingleTool@6a75d65c
s3 = com.company.SingleTool@6a75d65c
123
true
abc
s4 = com.company.SingleTool@6a75d65c
s : com.company.SingleTool@6a75d65c
123
true
abc
OK
说明:一个类中调用了同个工具类中的各个类,使用的工具是一样的(单例) ,换成非单例的就不一样了.
包下的另一个Test2类:
public class Test2 {
public static void use(){
SingleTool s = SingleTool.getInstance();
System.out.println("s : "+s);
s.add("OK");
s.sout();
}
}
class My {
public static void main(String[] args) {
/*--------------"同级别类"用工具--------------*/
Test2.use();
/*--------------"自己类使用工具"--------------*/
SingleTool s = SingleTool.getInstance();
System.out.println(" s : 自己"+s);
s.sout();
}
}
打印结果:
s : com.company.SingleTool@61a32fd1
OK
s : 自己com.company.SingleTool@61a32fd1
OK
结论和上面一样;除非不同的程序(有各自的main)调用单例,才不会是同一个对象(符合)
安卓拓展:
- (启动后,各个页面得到的单例都会是唯一一个)
- 1.如果界面曾经使用过单例工具,那么即使这个页面销毁(没有完全退出程序时),再次跳转过去,将还会是原来的单例.
2.除非是栈底完全退出时销毁,如”滑动预览任务退出”“(返回键退到桌面不代表完全销毁).
原理其实多个活动界面都是”相当于存在一个main中“,他们相当于一个类中”同级别的类”而已这样就可以用过java刚才了例子来理解这些界面了.