场景问题
业内都有一个不朽的传说,就是程序员是找不到女朋友的。没有女朋友怎么行。今天咱就带着大家用Java的知识,来”追”一个女朋友。
那在追女友之间阿,咱先定一个女友的标准。不过,这个标准不能乱定是吧。不能像网上流传的一样,”女的,活的”。做为一个有理想的程序员,我觉得我的女朋友,要有身高吧,然后罩杯也不对低,低重也不好太胖阿。那么用 Java 语言来表述,就是这个”女友”得有三个属性。身高,体重,与胸围。所以,咱就建这么个类。叫 GirlFriend.
public class GirlFriend {
private String cup;
private int height;
private int weight;
public String getcup() {
return cup;
}
public void setcup(String cup) {
this.cup = cup;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
public int getWeight() {
return weight;
}
public void setWeight(int weight) {
this.weight = weight;
}
}
那现在已经准备了一个女友类。接下来,是不是就要产生一个女朋友了。那咱们就在客户端 new一个呗。
public class Client {
public static void main(String[] args) {
GirlFriend gf = new GirlFriend();
}
}
问题
大家试想一下,如果我们在客户端里,再去”找”一个女友,是不是也是可以的。
public class Client {
public static void main(String[] args) {
GirlFriend gf = new GirlFriend();
GirlFriend gf1 = new GirlFriend();
System.out.println(gf==gf1);
}
}
而且,上述的代码,最终的结果也是 false。
那,做为一个有原则的男人,是不是要限制这种情况发现呢。
单例模式
单例模式定义
解决上面问题的一种方式,就是用单例模式。那么,何谓单例模式?我们先来看一下定义:
保证一个类仅有一个实例,并提供一个它的全局访问点。
实现方法
在 Java 中,单例模式实现分为两种。一种称为懒汉式,一种又称为饿汉式。我们分别来看一下这两种方式是怎么实现的。
1:懒汉式
public class GirlFriend {
private String cup;
private int height;
private int weight;
//定义一个变更来储存实例
private static GirlFriend instance =null;
private GirlFriend(){
}
public static GirlFriend getInstance(){
//判断实例是否为空
if(null==instance){
//如果当前实例还没有创建,那就生成一个,并赋值给储存实例
instance = new GirlFriend();
}
return instance;
}
//以下都是示例方法
public String getcup() {
return cup;
}
public void setcup(String cup) {
this.cup = cup;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
public int getWeight() {
return weight;
}
public void setWeight(int weight) {
this.weight = weight;
}
}
2:饿汉式:
public class GirlFriend {
private String cup;
private int height;
private int weight;
//定义一个变更来储存创建好的实例
private static GirlFriend instance =new GirlFriend();
private GirlFriend(){
}
public static GirlFriend getInstance(){
return instance;
}
//以下都是示例方法
public String getcup() {
return cup;
}
public void setcup(String cup) {
this.cup = cup;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
public int getWeight() {
return weight;
}
public void setWeight(int weight) {
this.weight = weight;
}
}
3:客户端调用
那这个时候,我们再调用,得到的就是一个唯一的女友了。
public class Client {
public static void main(String[] args) {
GirlFriend gf = GirlFriend.getInstance();
GirlFriend gf1 =GirlFriend.getInstance();
System.out.println(gf==gf1);
}
}
由于我们在女友类里,把构造方法给私有化了,所以,在客端端调用的时候,得到的就是唯一的一个女友对象了。(在这里,我们暂时不考虑反射。)
4:关于命名
其实,所谓饿汉与懒汉也是一种比较形象的说法吧。
所谓饿汉,也就是说你比较饥渴,饿,于是就在装载类的时候就已经创建好”女友了”。那懒汉,也就是懒,等你需要女友的时候再去创建。
延迟加载与缓存
延迟加载
懒汉式的单例模式体现了延迟加载的思想。那什么是延迟加载。
简单一点来主,延迟加载就是一开始不去加载数据或者资源,等到要用到的时候,才去加载,也就是 Lazy Load。这在实际开发中也是一种常见的思想,尽可能的节约资源。
缓存
懒汉式还体现了缓存的思想,缓存在开发中也是常见的功能。
简单的来说,就是当某些资源需要被反复使用的时候,而这些资源储存在系统外部,比如数据库,硬盘等,那如果每次操作都要重新读取一次,显然很浪费资源。所以,懒汉式加载中,在一开始,就定义了一个变量,来储存要生成的实例,也就是:
private static GirlFriend instance =null;
之后,再生成实例后,赋值给变量。
线程安全
从线程安全性上来讲,不加同步的懒汉式是线程不安全的。也就是说,如果多个线程同时调用getInstance方法,就会导制并发问题。那该如何解决。其实只要加上 synchronized就可以了。也就是:
public static synchronized GirlFriend getInstance(){}
但是这样一来,就会降低访问速度,而且每次都要判断,那该如何实现,这里就要用到双重加锁。
所谓双重加锁,指的是,不是每一次 getInstance都要同步。而是先不同步,进入方法之后,先检查实例是否存在。如果不存在,再进入同步块,这是第一重检查。进入同步块之后,再次检查实例是否存在,如果不存在,就在同步情况下创建一个实例。这是第二重检查。这样一来,就可以减少多次在同步情况下进行判断所浪费的时间了。
实现代码如下:
private volatile static GirlFriend instance =null;
public static GirlFriend getInstance(){
if(null==instance){
synchronized (GirlFriend.class){
if(instance==null){
instance = new GirlFriend();
}
}
}
return instance;
}
注:双重加载需要在 java5以上的版本。
枚举
其实,还有一种更高效的单例实现,也就是单元素的枚举。关于枚举的介绍,我这里就不多做介绍了。我们看一下如何用枚举实现单例。
public enum GirlFriend {
instance;
private String cup;
private int height;
private int weight;
//以下都是示例方法
public String getcup() {
return cup;
}
public void setcup(String cup) {
this.cup = cup;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
public int getWeight() {
return weight;
}
public void setWeight(int weight) {
this.weight = weight;
}
}
使用枚举实现单例会使代码更加的简洁。而且,从 JVM 上绝对的防止了多次实例化。