单例模式,目标就是在JVM内创建线程安全的单个实例.用途很多,比如加载资源配置文件.
在Java中实现单例的方法有很多种.有些是线程安全有不是.线程安全的实现方式有:
- 双重检查机制的懒汉式单例
- 静态内部类实现的单例
- 静态代码块实现的单例
- 使用枚举类实现的单例
其实还有一种有一个线程安全的Map登记实现的单例
下面线程安全和不安全的都分析下:
-
方法1 饿汉式 线程安全待定,个人觉得是安全,网上都说不安全
package com.byedbl.singleton.unsafe.method1;
/**
* 饿汉模式
* 这种测得为啥一直是线程安全的??
*
* @author : zengzhijun
* @date : 2018/5/18 17:56
**/
public class MyObject {
// 立即加载方式==饿汉模式
private static MyObject myObject = new MyObject();
private MyObject() {
}
public static MyObject getInstance() {
// 此代码版本为立即加载
// 此版本代码的缺点是不能有其它实例变量
// 因为getInstance()方法没有同步
// 所以有可能出现非线程安全问题
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return myObject;
}
}
这种方法据说是不安全,但是实际测试的时候每次都是一个实例.具体测得代码如下:
先创建一个线程类
package com.byedbl.singleton.unsafe.method1;
public class MyThread extends Thread {
@Override
public void run() {
System.out.println(MyObject.getInstance().hashCode());
}
}
再用多个线程一起去跑
package com.byedbl.singleton.unsafe.method1;
import java.util.concurrent.CountDownLatch;
public class Run {
public static void main(String[] args) {
int len = 1000;
CountDownLatch latch = new CountDownLatch(len);
MyThread[] threads = new MyThread[len];
for(int i =0 ;i< len;i++) {
threads[i] = new MyThread();
latch.countDown();
}
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
for(int i= 0;i
结果都是只有一个实例出来,从类的加载流程来看,private static MyObject myObject = new MyObject();
这句代码是只执行一次的.我们看JDK源码里java.lang.Runtime
这个类的实现也是这种方式.
真正的缺点是不能有实例变量吧.
-
方法1 懒汉式 线程不安全
package com.byedbl.singleton.unsafe.method2;
/**
* 懒汉式,没加锁,不安全,加锁性能也低
* @author : zengzhijun
* @date : 2018/5/18 17:55
**/
public class MyObject {
private static MyObject myObject;
private MyObject() {
}
public static MyObject getInstance() {
try {
if (myObject != null) {
} else {
// 模拟在创建对象之前做一些准备性的工作
Thread.sleep(3000);
myObject = new MyObject();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return myObject;
}
}
这个是真的不安全,在没有用CountDownLatch
的时候就可以测试出来,代码如下:
package com.byedbl.singleton.unsafe.method2;
public class Run {
public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
MyThread t3 = new MyThread();
t1.start();
t2.start();
t3.start();
}
}
MyThread
和上面方法1一个套路.
结果如下:
1770731361
903341402
186668687
打印出3个实例
-
方法3 懒汉式升级版1 线程安全,但性能比较低
package com.byedbl.singleton.safe.normal.method3;
/**
* 这个虽然线程安全,但是效率太低了
* @author : zengzhijun
* @date : 2018/5/18 19:00
**/
public class MyObject {
private static MyObject myObject;
private MyObject() {
}
// 设置同步方法效率太低了
// 整个方法被上锁
synchronized public static MyObject getInstance() {
try {
if (myObject != null) {
} else {
// 模拟在创建对象之前做一些准备性的工作
Thread.sleep(3000);
myObject = new MyObject();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return myObject;
}
}
这个直接在整个方法加synchronized
效率比较低,不推荐.
-
方法4 懒汉式升级版2 线程安全,但性能比较低
package com.byedbl.singleton.safe.normal.method4;
/**
* 这个虽然线程安全,但是效率太低了
* @author : zengzhijun
* @date : 2018/5/18 19:00
**/
public class MyObject {
private static MyObject myObject;
private MyObject() {
}
public static MyObject getInstance() {
try {
// 此种写法等同于:
// synchronized public static MyObject getInstance()
// 的写法,效率一样很低,全部代码被上锁
synchronized (MyObject.class) {
if (myObject != null) {
} else {
// 模拟在创建对象之前做一些准备性的工作
Thread.sleep(3000);
myObject = new MyObject();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return myObject;
}
}
虽然这个不是在方法上加锁,但是将整个代码块锁起来了,效率还是一样的低.不推荐.
-
方法5 懒汉式升级版3 线程不安全,
package com.byedbl.singleton.unsafe.method5;
public class MyObject {
private static MyObject myObject;
private MyObject() {
}
public static MyObject getInstance() {
try {
if (myObject != null) {
} else {
// 模拟在创建对象之前做一些准备性的工作
Thread.sleep(3000);
// 使用synchronized (MyObject.class)
// 虽然部分代码被上锁
// 但还是有非线程安全问题
synchronized (MyObject.class) {
myObject = new MyObject();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return myObject;
}
}
那我们能不能少加一行代码的锁呢?答案是否定的,这样就线程不安全了.而要解决这个问题,就得用下面这种比较推荐的,双重检查机制的懒汉式
-
方法6 双重检查机制的懒汉式 线程安全,推荐指数4颗星
package com.byedbl.singleton.safe.suggest.method6;
/**
* 线程安全之双检测机制
* DCL 双检查锁机制
* @author : zengzhijun
* @date : 2018/5/18 19:08
**/
public class MyObject {
//要声明为 volatile
private volatile static MyObject myObject;
private MyObject() {
}
// 使用双检测机制来解决问题
// 即保证了不需要同步代码的异步
// 又保证了单例的效果
public static MyObject getInstance() {
try {
if (myObject != null) {
} else {
// 模拟在创建对象之前做一些准备性的工作
Thread.sleep(3000);
synchronized (MyObject.class) {
if (myObject == null) {
myObject = new MyObject();
}
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return myObject;
}
// 此版本的代码称为:
// 双重检查Double-Check Locking
}
这个代码我们用多线程去跑也是没问题的.
package com.byedbl.singleton.safe.suggest.method6;
import java.util.concurrent.CountDownLatch;
public class Run {
public static void main(String[] args) {
int len = 100;
CountDownLatch latch = new CountDownLatch(len);
MyThread[] threads = new MyThread[len];
for(int i =0 ;i< len;i++) {
threads[i] = new MyThread();
latch.countDown();
}
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
for(int i= 0;i
-
方法7 静态内部类实现的单例,线程安全,推荐指数2颗星
package com.byedbl.singleton.safe.suggest.method7;
import java.io.ObjectStreamException;
import java.io.Serializable;
/**
* 线程安全之静态内部类
*
*
* @author : zengzhijun
* @date : 2018/5/18 19:08
**/
public class MyObject implements Serializable{
// 内部类方式
private static class MyObjectHandler {
private static MyObject myObject = new MyObject();
}
private MyObject() {
}
public static MyObject getInstance() {
return MyObjectHandler.myObject;
}
/**
* 解决在序列化时线程不安全的问题
**/
protected Object readResolve() throws ObjectStreamException {
System.out.println("调用了readResolve方法!");
return MyObjectHandler.myObject;
}
}
用这种方法需要注意的是要实现 Serializable
接口并覆盖readResolve
方法,主要是为了解决持久化在反序列化时得到的是不同的实例问题,可以注释readResolve
方法,用下面的代码测试看到效果:
package com.byedbl.singleton.safe.suggest.method7;
import java.io.*;
public class SaveAndRead {
public static void main(String[] args) {
try {
MyObject myObject = MyObject.getInstance();
FileOutputStream fosRef = new FileOutputStream(new File(
"myObjectFile.txt"));
ObjectOutputStream oosRef = new ObjectOutputStream(fosRef);
oosRef.writeObject(myObject);
oosRef.close();
fosRef.close();
System.out.println(myObject.hashCode());
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
FileInputStream fisRef = new FileInputStream(new File(
"myObjectFile.txt"));
ObjectInputStream iosRef = new ObjectInputStream(fisRef);
MyObject myObject = (MyObject) iosRef.readObject();
iosRef.close();
fisRef.close();
System.out.println(myObject.hashCode());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
-
方法8 静态代码块实现的单例,线程安全,推荐指数3颗星
package com.byedbl.singleton.safe.suggest.method8;
/**
* 静态代码块中的代码在使用类的时候就已经执行了,所以可以利用这个特性来实现单例
* @author : zengzhijun
* @date : 2018/5/18 19:21
**/
public class MyObject {
private static MyObject instance = null;
private MyObject() {
}
static {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
instance = new MyObject();
}
public static MyObject getInstance() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return instance;
}
}
-
方法9 使用枚举类实现的单例 线程安全,推荐指数5颗星
package com.byedbl.singleton.safe.suggest.method9;
import org.apache.commons.lang3.RandomUtils;
/**
* 使用枚举类实现单例
* 终极方法,能用静态枚举类的就用静态枚举类
* @author : zengzhijun
* @date : 2018/5/18 19:27
**/
public class MyObject {
private enum MyEnumSingleton {
INSTANCE;
String arg ;
private MyObject myObject = null;
MyEnumSingleton() {
//这里可以初始化变量,当然也可以在一个 static代码块里面初始化
arg = "get arg from properties" + RandomUtils.nextInt();
myObject = new MyObject();
}
public MyObject getInstance() {
return myObject;
}
}
public static MyObject getConnection() {
return MyEnumSingleton.INSTANCE.getInstance();
}
public String getArg() {
return MyEnumSingleton.INSTANCE.arg;
}
}
枚举类在JVM里面天然就是单例的.这种方法比较推荐.