• 单例模式在项目实战中的几个应用


    一、单例模式简单理解

    单例模式:即某个类在程序运行过程中只被实例化一次,也就是说该类在程序的生存周期里只有一个实例对象。
    使用单例模式好处:由于这个类只实例化一次,不管多少个类中用到了这个类,也都只有一个该类的对象。因此,
    减少了类实例对象的创建-->减小了GC压力-->提升了程序的性能。

    二、单例模式的几种常见写法

    /**
     * 饿汉式(线程安全)。类加载时就创建唯一的单例实例,不管后面用不用都创建了再说
     * 空间换时间的思想,
     */
    public class Singleton {
        private static Singleton instance = new Singleton();
        private Singleton(){}
        public static Singleton getInstance(){
            return instance;
        }
    }
    
    /**
     * 饿汉变种模式,使用静态代码块。包括上面的那种饿汉式写法也都是线程安全的
     * 因为这两种方法实际上间接地使用了synchronized关键字,具体怎么用到的呢?
     * 这就要去了解类加载的机制和过程了
     */
    public class Singleton{
        private static Singleton instance = null;
        static{
            instance = new Singleton();
        }
        private Singleton(){}
        public static Singleton getInstance(){
            return this.instance;
        }
    }
    
    
    /**
     * 懒汉式(非线程安全,可以在创建函数前加synchronized关键字变为线程安全)
     * 单例实例在使用时才创建
     */
    public class Singleton{
        private static Singleton instance;
    
        private Singleton(){
    
        }
        public static Singleton getInstance(){ //方法前加synchronized关键字变为线程安全,但是会增加创建的时间消耗
            if (instance==null){
                instance = new Singleton();
            }
            return instance;
        }
    }
    
    /**
     * 懒汉方式(线程安全双重检查锁版本)
     */
    public class Singleton{
        private volatile static Singleton singleton;
        private Singleton(){}
    
        public static Singleton getSingleton() {
            if (singleton==null){ //第一重检查
                synchronized (Singleton.class){
                    if (singleton==null){ //第二重检查
                        singleton = new Singleton();
                    }
                }
            }
            return singleton;
        }
    
    /**
     * 枚举实现线程安全的单例模式
     * 其底层是依赖Enum类实现的,而枚举类的成员变量其实都是静态类型的,并且是在
     * 静态代码块中实例化的,有点像饿汉模式,也是天然线程安全的
     */
    public Enum Singleton{
        INSTANCE;
        public void getInstance{
        }
    }
    
    /**
     * 使用ThreadLocal实现线程安全的单例
     * 也是空间换时间的方式(因为ThreadLocal会为每一个线程提供一个独立的副本)
     * 它是多个线程对数据的访问相互独立
     */
    public class Singleton{
        private static final TheadLocal<Singleton> instance= new ThreadLocal<Singleton>(){
            @Override
            protected Singleton initialValue(){
                return new Singleton();
            }
        };
        public static Singleton getInstance(){
            return instance.get();
        }
        private Singleton(){}
    }
    
    

    三、单例模式在Redis工具类中的使用

    import org.apache.commons.beanutils.BeanUtils;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    import java.beans.BeanInfo;
    import java.beans.IntrospectionException;
    import java.beans.Introspector;
    import java.beans.PropertyDescriptor;
    import java.lang.reflect.InvocationTargetException;
    import java.util.Collections;
    import java.util.HashMap;
    import java.util.LinkedHashMap;
    import java.util.List;
    import java.util.Map;
    import java.util.Set;
    import java.util.concurrent.locks.ReentrantLock;
    
    import redis.clients.jedis.Jedis;
    import redis.clients.jedis.JedisPool;
    import redis.clients.jedis.JedisPoolConfig;
    import redis.clients.jedis.JedisPoolConfig;
    import redis.clients.jedis.Tuple;
    
    /**
     * Redis连接池配置及使用方法
     */
    public class Redis {
    
    
      private static final Logger logger = LoggerFactory.getLogger(Redis.class);
      private static ReentrantLock lock = new ReentrantLock();
      private static Redis instance;
      private  JedisPool pool = null;
    
      public Redis(){
    
      }
      //线程安全的单例模式
      public static Redis getInstance(){
        if (instance==null){
          lock.lock();
          if (instance==null){
            instance = new Redis();
          }
          lock.unlock();
        }
        return instance;
      }
    
      public  void initialRedisPool() {
        //Redis服务器IP
         String ADDR = "localhost";
        //Redis的端口号
        int PORT = 6379;
        //可用连接实例的最大数目,默认值为8;
        //如果赋值为-1,则表示不限制;如果pool已经分配了maxActive个jedis实例,则此时pool的状态为exhausted,再获取jedis就会报错了。
        //这里我们设置2000就足够了
         int MAX_ACTIVE = 2000;
        //一个pool最多有多少个状态为idle(空闲的)的jedis实例,默认值也是8。
         int MAX_IDLE = 200;
        //等待可用连接的最大时间,单位毫秒,默认值为-1,表示永不超时。如果超过等待时间,则直接抛出JedisConnectionException;
         int MAX_WAIT = 10000;
        //在borrow一个jedis实例时,是否提前进行validate操作;如果为true,则得到的jedis实例均是可用的;
         boolean TEST_ON_BORROW = true;
        /**
         * 初始化Redis连接池
         */
          try {
            JedisPoolConfig config = new JedisPoolConfig();
            config.setMaxTotal(MAX_ACTIVE);
            config.setMaxIdle(MAX_IDLE);
            config.setMaxWaitMillis(MAX_WAIT);
            config.setTestOnBorrow(TEST_ON_BORROW);
            pool = new JedisPool(config, ADDR, PORT);
          } catch (Exception e) {
            e.printStackTrace();
          }
      }
    
      /**
       * 获取Jedis对象
       *
       * @return Jedis
       */
      public synchronized Jedis getJedis() {
        Jedis jedis = null;
        if (pool == null){
          initialRedisPool();
        }
        jedis = pool.getResource();
        return jedis;
      }
      //下面就是一些其他的对应于redis命令的工具方法了
      //比如set(...),get(...),lpush(...),hset(...)等
    

    使用起来就很简单了,比如:

    String value = Redis.getInstance().get(String key)
    //或者是
    Redis redisObj = Redis.getInstance()
    String value = redisObj.get(String key)
    

    四、单例模式在线程池创建中的使用

    我在项目中碰到一个这样的场景:
    1)某个接口的并发请求较大;
    2)对收到的数据要进行复杂的验证及数据库相关操作;
    3)响应速度不能太慢,至少得2秒内吧;
    于是正好可以拿线程池来练练手,下面分享一下我的练手代码(项目实战中根据需求稍作修改即可应用):

    本人实际项目中亲测,并且使用JMeter做了压测,吞吐量大,响应速度巨快!

    1 任务类(这是一个实现Callable的线程任务,因为我需要返回结果)

    package service;
    
    import java.util.concurrent.Callable;
    
    /**
     * 任务类
     */
    public class MyTask implements Callable {
        //假设我们需要处理传入进来的数据
        private final String data;
        public MyTask(final String data){
            this.data = data;
        }
    
        @Override
        public Object call() throws Exception {
            System.out.println("==============正在处理收到的data:" + data);
            Thread.sleep(1000);  //模拟处理数据需要花点小时间
            return "处理成功";
        }
    }
    

    2 处理任务的线程工具类

    package service;
    
    import java.util.concurrent.Callable;
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.Future;
    import java.util.concurrent.ThreadPoolExecutor;
    
    public class TaskUtil  {
        private static ThreadPoolExecutor poolExecutor = ThreadPoolConfig.getInstance();
        public static String submit(Callable callable) throws ExecutionException, InterruptedException {
            String result = "";
            //使用submit()方法提交任务,execute()方法不接受Callable线程任务
            Future<String> future = poolExecutor.submit(callable);
            //获取结果
            result = future.get();  
            return result;
        }
    }
    

    3 线程池创建类

    package service;
    
    public class ThreadPoolConfig {
        //核心线程数
        private static final int corePoolSize = 32;
        //最大线程数
        private static final int maxPoolSize = 48;
        //线程最大空闲时间
        private static final int keepAlive = 30;
        //线程池缓冲队列
        private static final BlockingQueue poolQueue = new LinkedBlockingQueue(64);
        private static ThreadPoolExecutor poolExecutor;
        private ThreadPoolConfig(){
    
        }
    
        /**
         * 单例模式获取
         * @return
         */
        public static ThreadPoolExecutor getInstance(){
            if (poolExecutor == null){
                //使用synchronized保证多线程情况下也是单例的
                synchronized (ThreadPoolConfig.class){  
                    if (poolExecutor == null){
                        poolExecutor = new ThreadPoolExecutor(corePoolSize,maxPoolSize,keepAlive,TimeUnit.SECONDS,poolQueue,new 
                                                              ThreadPoolExecutor.DiscardOldestPolicy());
                    }
                }
            }
            return poolExecutor;
        }
    }
    
    //这里给使用ThreadPoolExecutor创建线程池的重要参数解释(来自源码的一部分)
    //百度上搜一下也一大把的解释,但是也基本都是来自于源码(建议狠一点,多看源码中的注释和代码)
    /**
         * Creates a new {@code ThreadPoolExecutor} with the given initial
         * parameters.
         *
         * @param corePoolSize the number of threads to keep in the pool, even
         *        if they are idle, unless {@code allowCoreThreadTimeOut} is set
         * @param maximumPoolSize the maximum number of threads to allow in the
         *        pool
         * @param keepAliveTime when the number of threads is greater than
         *        the core, this is the maximum time that excess idle threads
         *        will wait for new tasks before terminating.
         * @param unit the time unit for the {@code keepAliveTime} argument
         * @param workQueue the queue to use for holding tasks before they are
         *        executed.  This queue will hold only the {@code Runnable}
         *        tasks submitted by the {@code execute} method.
         * @param threadFactory the factory to use when the executor
         *        creates a new thread
         * @param handler the handler to use when execution is blocked
         *        because the thread bounds and queue capacities are reached
         * @throws IllegalArgumentException if one of the following holds:<br>
         *         {@code corePoolSize < 0}<br>
         *         {@code keepAliveTime < 0}<br>
         *         {@code maximumPoolSize <= 0}<br>
         *         {@code maximumPoolSize < corePoolSize}
         * @throws NullPointerException if {@code workQueue}
         *         or {@code threadFactory} or {@code handler} is null
         */
        /*public ThreadPoolExecutor(int corePoolSize,
                                  int maximumPoolSize,
                                  long keepAliveTime,
                                  TimeUnit unit,
                                  BlockingQueue<Runnable> workQueue,
                                  ThreadFactory threadFactory,
                                  RejectedExecutionHandler handler) {
            if (corePoolSize < 0 ||
                maximumPoolSize <= 0 ||
                maximumPoolSize < corePoolSize ||
                keepAliveTime < 0)
                throw new IllegalArgumentException();
            if (workQueue == null || threadFactory == null || handler == null)
                throw new NullPointerException();
            this.acc = System.getSecurityManager() == null ?
                    null :
                    AccessController.getContext();
            this.corePoolSize = corePoolSize;
            this.maximumPoolSize = maximumPoolSize;
            this.workQueue = workQueue;
            this.keepAliveTime = unit.toNanos(keepAliveTime);
            this.threadFactory = threadFactory;
            this.handler = handler;
        }*/
    

    4 测试类

    package service;
    
    import java.util.concurrent.ExecutionException;
    
    public class TestThreadPool {
        public static void main(String[] args) throws ExecutionException, InterruptedException {
            for (int i = 0; i < 50; i++) {
                System.out.println("-------收到请求任务" + i+"--------");
                //模拟从请求中拿到的数据
                String requestData = "this is request data to deal with"+i;
                //将数据处理任务丢给线程池异步处理
                String re = TaskUtil.submit(new MyTask(requestData));
                //打印返回的结果(实际项目中将结果封装一下返回给前端就行了)
                System.out.println("返回结果="+re);
            }
        }
    }
    

    Reference

    [1] https://blog.csdn.net/gan785160627/article/details/81946242
    [2] https://www.cnblogs.com/peter_zhang/p/Singleton.html
    [3] 公众号:程序员面试现场 文章《面试官真搞笑!让实现线程安全的单例,又不让使用synchronized!》
    [4] https://blog.csdn.net/ccf547461296/article/details/54097595

  • 相关阅读:
    GDAL并行IO的疑问
    memcpy一段内存到std::vector<double>
    解决mysql无法远程登陆问题
    .net 上传word 转为 html
    OnCheckedChanged的触发需要AutoPostBack="true"
    asp.net与word文档在线
    [转]mysql如何设置主键和外键,实现级联更新、级联删除
    asp.net 读取Word
    datalist 嵌套 datalist 中的table 乱
    [转]php中使用ignore_user_abort()函数后,如何停止后台运行的程序?
  • 原文地址:https://www.cnblogs.com/kuangdw/p/12840026.html
Copyright © 2020-2023  润新知