游戏系统设计开发之单例模式

 二维码 1245
发表时间:2016-09-24 20:52

游戏服务器开发中使用的设计模式


设计模式都是前辈们总结的经验,他们帮我们铺就了一条管理代码的康庄大道!站在巨人的肩膀上可以看的更远!今天我们先总结第一个设计模式,单例模式,因为它是游戏中最常用的一种模式了!

Java中单例模式定义:一个类有且仅有一个实例,并且自行实例化向整个系统提供服务。在游戏服务器开发过程中,我们有很多服务类,在使用的时候需要实例化,但是并不需要实例化多次,这时就需要使用单例模式了!单例模式的实现方式有以下几种。我们以玩家管理类为例子。

1,懒汉模式

public class PlayerManager {

   private static PlayerManager playerManager = null;

   private PlayerManager(){}

   public static PlayerManager getInstance(){

       if(playerManager == null){

           playerManager = new PlayerManager();

       }

       return playerManager;

   }

}

单例模式都要把构造方法设置为private的,这样可以防止外部有人才new出这个类!这种模式是在第一次使用的时候初始化这个类的实例,它适合在单线程中使用,如果是在多线程中,它不是线程安全的,因为有可能有多个线程同时执行了new操作,我们看第二种方式!

2,饿汉模式

public class PlayerManager {

   private static PlayerManager playerManager = new  PlayerManager();

   private PlayerManager(){}

   public static PlayerManager getInstance(){

       return playerManager;

   }

}

这种方式简单粗暴,在jvm加载类的时候就把它初始化好了,一定是线程安全了!但是很明显的看出,它有点浪费内存,因为你可能有用不到的类的时侯,它也初始化了!当然,如果你不在意它占用些内存也无所谓了!那我们想节省点内存又想线程安全,该怎么办呢?看第三种模式!

3,线程安全的懒汉模式

public class PlayerManager {

   private static PlayerManager playerManager = null;

   private PlayerManager(){}

   public static synchronized PlayerManager getInstance(){

       if(playerManager == null){

           playerManager = new PlayerManager();

       }

       return playerManager;

    }

}

这时对获取实例方法添加了synchronized关键字,保证每次只有一个线程可以访问,这样虽然保证了线程安全,但是如果是在大并发的情况下,对性能明显影响很大,那有没有既可以保证线程安全,在大并发下又不怎么影响性能的呢,还有一种写法是:

4,双重检查锁定

public class PlayerManager {

   //注意此处加上了volatile关键字,要保证线程间的可见性。

   private volatile static PlayerManager playerManager = null;

   private PlayerManager() {

   }

   public static PlayerManager getInstance() {

       if (playerManager == null) {

           synchronized (PlayerManager.class) {

               if (playerManager == null) {

                   playerManager = new PlayerManager();

               }

           }

        }

       return playerManager;

   }

}

这样写只有在第一次为null的情况下加锁,并初始化实例,之后就不会运行到锁那里,而直接返回已经初始化好的实例。

5,利用静态内部类

public class PlayerManager {

   private static class InstanceHolder {

       private static final PlayerManager playerManager = new PlayerManager();

   }

   private PlayerManager() {

   }

   public static PlayerManager getInstance() {

       return InstanceHolder.playerManager;

   }

}

这种方式既实现了线程安全,又避免使用了锁,及同步带来的性能影响。因为java机制规定,内部类只有在getInstance()方法第一次调用的时候才会被加载ClassLoader(实现了lazy),而且其加载过程是线程安全的(实现线程安全)。内部类加载的时候实例化一次instance。

然而,对于上述四种方式的单例模式,如果你的Singleton类实现了Serializable序列化接口,那么可能会被序列化生成多个实例,因为readObject()方法一直返回一个新的对象,但是实际中我们很少这么做,大家注意一下就可以了。

那么另外有没有一种绝对安全的方式呢,请看枚举单例

6,枚举单例


上面说到的静态内部类方式不失为一个高级的单例模式实现。但如果开发要求更严格一些,比如你的Singleton类实现了序列化,又或者想避免通过反射来破解单例模式的话,单例模式还可以有另一种形式。那就是枚举单例。枚举类型在JDK1.5被引进。这种方式也是《Effective Java》作者Josh Bloch 提倡的方式,它不仅能避免多线程的问题,而且还能防止反序列化重新创建新的对象、防止被反射攻击。代码如下:

public enum PlayerManagerEnum {

   getInstance{

    @Override

    public boolean login(long userId, String token) {

       //这里是方法的实现

       return false;

    }


};

   //单例中要操作的方法

   public abstract boolean login(long userId,String token);

}

调用如下:

public static void main(String[] args) {

   PlayerManagerEnum.getInstance.login(1001, "abc");

}


总结:

在使用的时候,我们可以根据自己的情况去选择,不一定非要绝对的处理方案,只要能满足自己的需求,解决问题即可。

转载请注名来自游戏技术网:http://www.youxijishu.com

打赏