单利设计模式饿汉式和懒汉式
单利设计模式:解决一个类在内存只存在一个对象
想要保证对象唯一:
- 避免其他程序过多建立该类对象,保证实例的唯一性
- 为让其他程序可以访问到该类对象,只好在本类中自定义一个对象
- 为方便其他程序对自定义对象的访问,必须要对外提供一些访问方法
对这三步的具体代码实现:
- 将构造函数私有化
- 在类中创建一个本类对象
- 提供一个方法可以获取到该对象
饿汉式
package test1;
public class Hunger {
int num;
public void setNum(int num)
{
this.num = num;
}
public int getNum()
{
return num;
}
private Hunger(){}
private static Hunger hunger = new Hunger(); //更加严谨:private static final Hunger hunger = new Hunger();
public static Hunger getInstance()
{
return hunger;
}
}
class Main{
public static void main(String[] args) {
Hunger t1 = Hunger.getInstance();
Hunger t2 = Hunger.getInstance();
t1.setNum(23); //set的t1
System.out.println(t2.getNum()); //get的t2
}
}
运行结果为:
鉴于饿汉式的代码较直观,也易于理解,在此不进行过多讲解
懒汉式
同样通过对单利设计模式的三点要素我们写出了 -->
–> 最初的懒汉式:
package test1;
public class Lazy {
private static Lazy lazy = null; //注意与饿汉式进行区分,它不能这样写:private static final Lazy lazy = null;
private Lazy(){}
public static Lazy getInstance()
{
if(lazy == null)
{ //佩奇处
lazy = new Lazy(); //乔治处
}
return lazy;
}
}
因为lazy是共享数据,如果多个线程并发(详情可移步至java多线程自学总结)访问 getInstance() 方法时,程序会出现什么问题呢?
(有关getInstance()方法可移步至getInstance()小结)
我们通过一个例子来说明,若有A和B两个先生(线程),他们并发的访问 getInstance() 方法,假如当A先生率先判断完if语句并挂机在佩奇处(可通过sleep等方式),由于A先生并未执行乔治处的操作,接着B先生紧随其后的也进到了佩奇处,恰巧他也挂机了,而此时的A先生苏醒了,就往后执行了乔治处的操作new了一个对象,然后B也苏醒了同样执行了乔治处的操作new了一个对象,这很明显违背了我们设计单利模式(保证对象唯一)的初衷。(也就是出现了安全问题)
为了避免多线程同时创建对象,于是有些学过同步的小伙伴就会非常自信说,简单啊!在 getInstance() 方法前加一个synchronized就解决问题了啊。于是就有了以下的代码:
package test1;
public class Lazy {
private static Lazy lazy = null;
private Lazy(){}
public static synchronized Lazy getInstance()
{
if(lazy == null)
{
lazy = new Lazy();
}
return lazy;
}
}
看过代码后,我们可以试着用刚才A和B线程的例子验证以下你会发现,这样的确可以解决安全问题,但是当有更多的线程并发的访问 getInstance() 方法时,每一次都要判断我们的锁,这样的程序会变得特别低效,所以我们可以将共享数据通过同步代码块优化一下,形成如下的代码:
package test1;
public class Lazy {
private static Lazy lazy = null;
private Lazy(){}
public static Lazy getInstance()
{ //BBC处
if(lazy == null)
{ //WTO处
synchronized(Lazy.class)
{ //ABC处
if(lazy == null)
{ //NBA处
lazy = new Lazy(); //TNT处
}
}
}
return lazy;
}
}
同样我们可以通过刚才的例子来验证一下,只不过这次多了个C先生(线程)。若A、B、C先生同时并发的访问getInstance()方法时,当A先生率先过五关斩六将(你懂的)的来到 NBA 处,他不出意外的挂机了(sleep等方法),而此时的B先生由于A先生没有到 TNT 处,于是也迅速的来到了 WTO 处,但因没持有锁而停留至此,此时我们的A先生按照剧本设定苏醒过来并到达了 TNT 处new了一个对象,然后离开了。故我们的B先生可以来到ABC处,但由于A先生在TNT处的操作而停留于此,最后是我们的C先生,他由于A先生的操作只能停留在BBC处。
假若对锁的理解还不是太清晰,可移步至“对象锁,方法锁,类锁总结”
通过上面的不当举例(嘿嘿),我们可以设想假如有更多的先生(线程)并发的访问 getInstance()方法时,他们大多数都会像C先生一样连第一关都闯不过而停留在BBC处,这样也就减少了对锁的判断,提高了效率
饿汉式和懒汉式的比较
安全性
因为懒汉式的特点是延时加载,多线程访问时会出现安全隐患,虽然可以通过加同步和双重判断的方式来解决问题,但饿汉式属于”天生丽质“,所以开发时,尽可能的选择饿汉式
单利设计模式总结
优点
- 在内存里只有一个实例,减少了内存的开销
- 避免对共享资源的多重占用
缺点
- 没有接口,扩展困难
- 不能继承
- 与单一职责原则冲突
使用场景
- 要求生成唯一序列号的环境
- 需要定义大量的静态常量和静态方法的环境,可以采用单例模式
由于本人刚进入多线程的学习,无相关开发经验,对于优秀博客的一些地方还未理解,故不会直接粘贴复制,对于其他很多没总结完的特点,请移步至”相关优秀博客“