單例模式在項目實戰中的幾個應用


一、單例模式簡單理解

單例模式:即某個類在程序運行過程中只被實例化一次,也就是說該類在程序的生存周期里只有一個實例對象。
使用單例模式好處:由於這個類只實例化一次,不管多少個類中用到了這個類,也都只有一個該類的對象。因此,
減少了類實例對象的創建-->減小了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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM