1、问题:如何在Java中创建线程安全的单例
要回答这样的问题,首先要了解什么是单例--一个类只有一个实例,其次在了解单例的几种模式
1、饿汉模式
public class Singleton{
public Singleton(){}
public static Singleton singleton = new Singleton();
public static Singleton getInstance(){
return singleton;
}
}
缺点:浪费资源空间
2、懒汉模式
public class Singleton{
public Singleton(){}
public static Singleton singleton;
public static Singleton getInstance(){
if(singleton==null){
singleton = new Singleton();
}
return singleton;
}
}
优点:可以实现延迟加载,即什么时候使用什么时候加载实例
缺点:多线程下会造成会生成多实例
import java.util.concurrent.FutureTask;
public class Singleton {
public static Singleton singleton;
public static Singleton getInstance(){
if(singleton==null){
singleton = new Singleton();
}
return singleton;
}
}
class Test1 {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
Thread th = new Thread() {
@Override
public void run() {
System.out.println(Singleton.getInstance());
}
};
th.start();
}
}
}
执行结果,多线程情况下会出现多实例情况
多线程情况下,如何保证线程安全,加锁
public class Singleton {
public static Singleton singleton;
public static synchronized Singleton getInstance(){
if(singleton==null){
singleton = new Singleton();
}
return singleton;
}
}
class Test1 {
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
Thread th = new Thread() {
@Override
public void run() {
System.out.println(Singleton.getInstance());
}
};
th.start();
}
}
}
执行结果
通过添加类锁,可以防止锁多次实例化,缺点由于对整个getInstance方法加锁,性能较低,限定了访问速度
3、双重检测机制(DCL)-- 懒汉模式
public class Singleton {
public static Singleton singleton;
public static Singleton getInstance(){
synchronized (Singleton.class) {
if(singleton==null) {
singleton = new Singleton();
}
}
return singleton;
}
}
执行结果:
结果再次证明生成了多实例,原因是因为线程A和线程B均在未初始化签获取对象,此时对象分别为null,最终实例化为新对象
解决方案,在锁之前进行null判断,但是忽略了一点,singleton = new Singleton() 不是原子性的
加载一个对象的过程
(1)分配内存空间
(2)执行构造函数,初始化对象
(3)将对象指向内存空间
不同的服务器下,有可能造成指令重排,即(1)-> (3) -> (2)这时线程A持有未初始化对象A,线程B进入后对象已经不为空,直接使用对象,但是此对象未被初始化,容易出现问题,这是我们只需引入volatitle关键字
public class Singleton {
public static volatile Singleton singleton;
public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
上面代码为双重检测锁(DCL)完整代码
4、静态内部类实现线程安全(推荐)
public class Singleton {
public static class SingletonHolder{
private static Singleton instance = new Singleton();
}
public static Singleton getInstance(){
return SingletonHolder.instance;
}
}
当Singleton被加载时,其内部类不会被初始化,所以实例也不会被初始化,只有getInstance被调用时,才会加载SingletonHolder从而instance才会被初始化,实例的建立是类加载时发生的,天生对线程友好,所以不用再使用volatitle
5、枚举单例模式
public class Singleton {
public Singleton(){}
public enum SingletonEnum{
INSTANCE;
SingletonEnum(){
singletonEnum = new Singleton();
}
private Singleton singletonEnum;
public Singleton getInstance(){
return singletonEnum;
}
}
}
我们在访问枚举时,都会执行构造函数,由于枚举枚举的实例都是static final类型,所以对象只能被实例化一次