单例模式在实现上,有以下几点要求:
(1) 构造器私有化
(2) 自行创建,并且用静态变量保存
(3) 向外提供这个实例
(4) 强调这是一个单例,用static关键字修饰
按照创建实例的时机,又有懒汉式和饿汉式两种实现模式。
饿汉式:在类的初始化时就创建实例,所以不存在线程安全问题:
1 package single;
2
3 /**
4 * 饿汉式,类一加载直接创建好实例对象
5 *
6 * (1) 构造器私有化
7 * (2) 自行创建,并且用静态变量保存
8 * (3) 向外提供这个实例
9 * (4) 强调这是一个单例,用static关键字修饰
10 */
11 public class Singleton1 {
12 private static final Singleton1 INSTANCE=new Singleton1();
13 private Singleton1(){
14 System.out.println("创建一个实例!");
15 }
16 public static Singleton1 getInstance(){
17 return INSTANCE;
18 }
19 }
如果在创建实例时需要传参,可以对以上代码进行改造如下:
1 package single;
2
3 /**
4 * 可以给类传参的饿汉式写法,对第一种方法的改造
5 */
6 public class SingleTon3 {
7 private static final SingleTon3 INSTANCE;
8 private String info;
9 private SingleTon3(String info){
10 this.info=info;
11 }
12
13 static {
14 INSTANCE=new SingleTon3("传入参数");
15 }
16 public static SingleTon3 getInstance(){
17 return INSTANCE;
18 }
19 }
懒汉式:调用获取实例的方法时才创建实例,在多线程环境下会出现线程安全问题
•不加处理的懒汉式:
1 package single;
2
3 /**
4 * 懒汉式单例模式
5 */
6 public class Singleton4 {
7 private static Singleton4 INSTANCE;
8 private Singleton4(){
9
10 }
11 public static Singleton4 getInstance(){
12 if(INSTANCE==null){
13 INSTANCE=new Singleton4();
14 }
15 return INSTANCE;
16 }
17 }
不加线程安全处理的懒汉式在多线程并发时极有可能创建多个实例达不到单例的作用,这时可以使用synchronized关键字修饰获取实例的方法,通过加锁控制创建实例的线程。
1 package single;
2
3 /**
4 * 懒汉式单例模式
5 */
6 public class Singleton4 {
7 private static Singleton4 INSTANCE;
8 private Singleton4(){
9
10 }
11 public static synchronized Singleton4 getInstance(){
12 if(INSTANCE==null){
13 INSTANCE=new Singleton4();
14 }
15 return INSTANCE;
16 }
17 }
但是考虑到性能开销问题。用synchronized关键字修饰整个方法锁的粒度太大,可以使用双端检测机制来实现更精准的加锁,减小性能开销。
1 package single;
2
3 /**
4 * 为解决多线程情况下的可能被创建多个实例的情况,在实例为空,创建第一个实例的时候加入synchronize同步锁
5 */
6 public class Singleton5 {
7 private static Singleton5 INSTANCE;
8 private Singleton5(){
9 System.out.println("构建一个实例!");
10 }
11 public static Singleton5 getInstance(){
12 //必须双重判断,捋捋就知道了(双端检测机制)
13 if(INSTANCE==null) {
14 synchronized (Singleton5.class){
15 if(INSTANCE==null){
16 //模拟多线程下的延时
17 try {
18 Thread.sleep(1000);
19 } catch (InterruptedException e) {
20 e.printStackTrace();
21 }
22 INSTANCE = new Singleton5();
23 }
24 }
25 }
26 return INSTANCE;
27 }
28 }
29
30 class Test{
31 public static void main(String[] args) {
32 for (int i = 0; i <10; i++) {
33 new Thread(()->{
34 Singleton5.getInstance();
35 }).start();
36 }
37 }
38 }
很多人以为这样就是线程最安全的懒汉式单例模式,然鹅,到这里还并没有结束!多线程环境下在创建实例时会出现指令重排所带来的问题。创建实例时会发生指令重排,虽然问题机率非常低,但仍不排除指令重排的问题。这时需要用volatile关键字来修饰实例来解决指令重排序问题。
1 package single;
2
3 /**
4 * 为解决多线程情况下的可能被创建多个实例的情况,在实例为空,创建第一个实例的时候加入synchronize同步锁
5 */
6 public class Singleton5 {
7 private volatile static Singleton5 INSTANCE;
8 private Singleton5(){
9 System.out.println("构建一个实例!");
10 }
11 public static Singleton5 getInstance(){
12 //必须双重判断,捋捋就知道了(双端检测机制)
13 if(INSTANCE==null) {
14 synchronized (Singleton5.class){
15 if(INSTANCE==null){
16 //模拟多线程下的延时
17 try {
18 Thread.sleep(1000);
19 } catch (InterruptedException e) {
20 e.printStackTrace();
21 }
22 INSTANCE = new Singleton5();
23 }
24 }
25 }
26 return INSTANCE;
27 }
28 }
29
30 class Test{
31 public static void main(String[] args) {
32 for (int i = 0; i <10; i++) {
33 new Thread(()->{
34 Singleton5.getInstance();
35 }).start();
36 }
37 }
38 }