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

Posted kuangdw

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了单例模式在项目实战中的几个应用相关的知识,希望对你有一定的参考价值。

一、单例模式简单理解

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

    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;
    }

三、单例模式在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 = ThreadService.submit(new MyTask(requestData));
            //打印返回的结果(实际项目中将结果封装一下返回给前端就行了)
            System.out.println("返回结果="+re);
        }
    }
}

技术图片







以上是关于单例模式在项目实战中的几个应用的主要内容,如果未能解决你的问题,请参考以下文章

单例模式的几种方式!

单例模式——实战应用详解

常用的几个JQuery代码片段

JavaSE面试题——Singleton单例模式的几种写法

C#实现单例模式的几种方法

单例模式(下)---聊一聊单例模式的几种写法