一.单例模式
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);