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