一.單例模式
1.餓漢式
class CarSIngletonByEager { private static int count ; private CarSIngletonByEager() { System.out.println("Singleton 私有的構造方法被實例化 " + (++count) + " 次。"); } private static Car car = new Car(); public static Car getCar() { return car; } }
2.懶漢式
class CarSingleton { private static int count ; private CarSingleton() { System.out.println("Singleton 私有的構造方法被實例化 " + (++count) + " 次。"); } private static Car car = null; public synchronized static Car getCar() { if (car == null) { car = new Car(); } return car; } }
3.懶漢式的線程安全問題
public static void main(String[] args) { Runnable runnable = new Runnable() { @Override public void run() { String threadName = Thread.currentThread().getName(); Car c = CarSingleton.getCar(); System.out.println("線程 " + threadName + "\t => " + c.hashCode()); } }; for(int i=0;i<20;i++){ new Thread(runnable,"" + i).start(); } }
這個測試類是這樣的,在CarSingleton的私有構造方法里面,設置一個記錄變量,每實例一次就自增一次,如果是正常情況,應該是只是調用了一次,我們建了20個線程,每個線程都試圖去獲取Car實例,看看情況是怎樣的,首先是把getCar方法的synchronize關鍵字去掉的:
可以看到,構造器不斷被調用,其實問題就在於,比如說線程一沒有實例,剛剛判斷完if (car == null),進入循環體,還沒有新建Car的時候,線程二也沒有Car實例,也進入了這個方法體內准備創建Car,但是線程一這個時候以及創建了Car,但是線程二是不知道的,它也干了反正,出現了線程安全問題。我們下面加一個synchronized關鍵字到方法試下:
可以看到,只是實例化了一次,其他沒有實例的線程只能是在某個線程進去實例成功后,再進去,這個時候發現已經有實例了,直接拿來用。不過呢,synchronized加在方法上面比較影響性能,因為當一個線程創建實例后,其實鎖就是十分沒有必要的東西了,還有其他方法么?
public static Car getCar() { if (car == null) { synchronized (CarSingleton.class) { if(car==null){ car = new Car(); } } } return car; }
上面的代碼就是經典的double-checked locking 解決單例懶漢多線程的安全問題的示例,其實就是把鎖的粒度減低,但是其實還是會有問題,比如AB線程,A先進入同步代碼塊,它創建新的Car對象。但是,JVM不保證對對象使用構造函數進行初始化和將內存地址賦給棧中的變量這兩個操作的先后順序。所以A在分配好內存后,可能直接賦值給棧中的變量而還沒有初始化,這個時候B想進去第二個if判斷,發現已經存在實例了,所以直接拿去用了這個還沒初始化的對象,出現錯誤。其實這個在測試中基本沒有問題,可能還是不夠完美吧。
其實單例比較簡單的保證線程安全的方法有靜態內部類,靜態內部類在類加載時就創建對象,JVM的機制保證了類在加載時是互斥的,所以也是線程安全的。多線程環境下測試成功,只是實例化了一次。
class CarSIngletonByInnerClass { private static int count ; private CarSIngletonByInnerClass() { System.out.println(Thread.currentThread().getName()+"Singleton 私有的構造方法被實例化 " + (++count) + " 次。"); } private static class CarInnerClass { private static Car car = new Car(); } public static Car getCar() { return CarInnerClass.car; } }
二.Jedis的使用
1.概述
Redis Client擁有眾多對接版本,Jedis是目前使用Jedis為官方推薦Java對接客戶端,是基於其對Redis良好的版本支持和API對接,簡單說,就是封裝了操作redis的api,可以想一想Hibernate。
2.使用
這個是一個Jedis使用的HelloWord示例,使用就是這么簡單,設置ip,端口,auth密碼,就可以愉快地增刪了。
Jedis jedis = new Jedis("192.168.1.227", 6379); jedis.auth("redis123"); // 存儲數據和獲取數據 jedis.set("username", "成成"); String username = jedis.get("username"); System.out.println(username);
3.JedisPool
上面的代碼,每次要連接redis都要寫一遍,太麻煩了,想一想以前的Mysql,是不是有個叫連接池的東西?配置好連接數據庫的基本信息,然后要用到就從里面拿一個數據庫連接實例來用就行。沒錯,JedisPool就是拿來干這些的,這次項目中我是使用單例模式創建了一個RedisUtil類,里面以單例的模式創建JedisPool對象,還提供了關閉redis客戶端的方法,具體看下面的代碼:
public class JedisPoolUtile { private JedisPoolUtile(){} private static JedisPool jedisPool = null; public static JedisPool getJedisPool (){ if (jedisPool==null){ synchronized (JedisPoolUtile.class){ if (jedisPool==null){ JedisPoolConfig poolConfig = new JedisPoolConfig(); poolConfig.setMaxIdle(32); poolConfig.setMaxWaitMillis(100*1000); poolConfig.setTestOnBorrow(false); jedisPool = new JedisPool(poolConfig,"192.168.174.128",6379); } } } return jedisPool; } public static void release (JedisPool jedisPool, Jedis jedis){ if (jedis!=null){ jedisPool.returnResourceObject(jedis); } } }
下面是在RedisService里面使用的代碼,這里注意就是客戶端的密碼還是在要連接的時候登一下,目前發現在linux那么的客戶端測試時,過了一段時間又要重新輸入密碼,如果一開始設置密碼而后面需要操作數據庫的時候沒有設置,還是會連不上。
private JedisPool jedisPool; private Jedis jedis; public RedisService(){ jedisPool = JedisPoolUtile.getJedisPool(); jedis = jedisPool.getResource(); System.out.println(jedis); System.out.println("jedis連接redis服務器完成--------------"); }
//下面是取值的時候用到的
jedis.auth("123");
jedis.set(key,value);