Java多线程下的单例模式
1.首先看一下非多线程下的懒汉式单例模式:
package test2;
/**
- Title:传统懒汉式单例模式
- @author admin
*/
public class Instance3 {
private static Instance3 instance;
private Instance3() {
System.out.println(“我是Instance构造函数”);
}
public static Instance3 getInstance() {
if (instance == null)
instance = new Instance3();
System.out.println(“获取instance对象”);
return instance;
}
//测试
public static void main(String[] args) {
Thread t1 = new Thread() {
public void run() {
try {
System.out.println(“线程一初始化instance对象”);
Instance3.getInstance();
} catch (Exception e) {
e.printStackTrace();
}
}
};
t1.start();
Thread t2 = new Thread() {
public void run() {
try {
System.out.println(“同时线程二访问getInstance方法”);
Instance3.getInstance();
} catch (Exception e) {
e.printStackTrace();
}
}
};
t2.start();
}
}
正常情况下,在单线程下工作不会出现问题,但是在多线程模式下工作,这种方式就会变得不安全,例如如果有两个线程同时访问if (instance == null)这条语句,那么这两条线程就会同时返回true,那么这两个线程各自会创建一个实例,这样就破坏了单例模式。如图:
可见,Instance构造函数被访问了两次,即线程t1,t2都创建了instance对象,多线程破坏了单例模式。
2.所以我们可以借助java的同步机制:synchronized
package test2;
/**
- Title:多线程下加了同步机制的单例模式
- Description:保证了线程安全但每次只能一个线程访问getInstance(),效率不高
- @author admin
*/
public class Instance4{
private static Instance4 instance;
private Instance4() {
System.out.println(“我是构造函数”);
}
public synchronized static Instance4 getInstance() {
if (instance == null)
instance = new Instance4();
System.out.println(“获取instance对象”);
return instance;
}
//测试
public static void main(String[] args) {
Thread t1 = new Thread() {
public void run() {
try {
System.out.println(“线程一初始化instance对象”);
Instance4.getInstance();
} catch (Exception e) {
e.printStackTrace();
}
}
};
t1.start();
Thread t2 = new Thread() {
public void run() {
try {
System.out.println(“同时线程二访问getInstance方法”);
Instance4.getInstance();
} catch (Exception e) {
e.printStackTrace();
}
}
};
t2.start();
}
}
可见只调用了一次构造函数,保证了线程安全,因为同一时间只有一个线程能访问getInstance()这个方法,也就不会出现上面那种两个线程同时访问if (instance == null)这条语句的情况,因此这种情况下是绝对的线程安全。但是这种直接在方法名上加锁有个缺点:效率不高,因为在同一时间只有一个线程可以访问这个getInstance()方法,这就导致如果存在多个线程需要调用这个方法,那就需要线程等待。
3.所以有了下面这种最常用的多线程下的单例模式DCL:
package test2;
/**
- 单例模式DCL
- @author admin
*/
public class Instance {
private static volatile Instance instance;
private Instance() {
System.out.println(“我是Instance构造函数”);
}
public static Instance getInstance() {
if (instance == null) {
System.out.println(“第一次初始化”);
synchronized (Instance.class) {
if (instance == null) {
instance = new Instance();
}
}
}
System.out.println(“获取instance对象”);
return instance;
}
//测试
public static void main(String[] args) {
Thread t1 = new Thread() {
public void run() {
try {
System.out.println(“线程一初始化instance对象”);
Instance.getInstance();
} catch (Exception e) {
e.printStackTrace();
}
}
};
t1.start();
Thread t2 = new Thread() {
public void run() {
try {
System.out.println(“同时线程二访问getInstance方法”);
Instance.getInstance();
} catch (Exception e) {
e.printStackTrace();
}
}
};
t2.start();
}
}
DCL模式中首先给instance变量加一个volatile关键字,这是为了保证这个变量在线程间的可见性,并且防止指令重排序,这样就保证了instance引用在各线程间都是一致的。之后我们通过两个判断,将synchronized加在第一个if语句块内,将锁的粒度缩小,这样使得多个线程都可以同时调用getInstance()获得对象的同时保证instance引用不会被多线程改变,从而实现实例唯一性,即实现了单例模式。
测试结果如下:
可见,线程t1和t2都能访问getInstance()方法,提高了效率,但只有第一次访问getInstance()方法的线程一能访问构造函数,并初始化instance对象,往后的其他线程访问getInstance()只能获取instance对象,确保了Instance类对象的唯一性。
4.除此之外,还有一种内部类实现的线程安全的单例模式:
package test2;
/**
- Title:内部类实现多线程的单例模式
- @author admin
*/
public class Instance2 {
private static class InstanceHolder {
private static Instance2 instance = new Instance2();
}
private Instance2() {
System.out.println(“我是Instance构造函数”);
}
public static Instance2 getInstance() {
System.out.println(“获取instance对象”);
return InstanceHolder.instance;
}
//测试
public static void main(String[] args) {
Thread t1 = new Thread() {
public void run() {
try {
System.out.println(“线程一初始化instance对象”);
Instance2.getInstance();
} catch (Exception e) {
e.printStackTrace();
}
}
};
t1.start();
Thread t2 = new Thread() {
public void run() {
try {
System.out.println(“同时线程二访问getInstance方法”);
Instance2.getInstance();
} catch (Exception e) {
e.printStackTrace();
}
}
};
t2.start();
}
}
可见,Instance构造函数也仅被调用一次,由于Java虚拟机内部机制的原因,可以通过静态内部类让instance全局只存在唯一实例,也实现了线程安全的单例模式。