多个线程要操作同一个对象,保证对象的唯一性,实例化过程只实例化一次;
解决的思路
- 有一个实例化的过程(只执行一次),产生实例化对象;
- 类外部不能new 这个对象;
- 提供返回实例对象的方法;
饿汉式
在加载类时就实例化一个对象
public class Singleton1 {
//加载该类的时候就创建
private static Singleton1 singleton1=new Singleton1();
//构造方法私有,外部不能new 对象
private Singleton1(){
}
public static Singleton1 getIntance(){
return singleton1;
}
}
特点
- 在加载的时候已经被实例化,所以只实例化了一次,线程安全;
- 没有延迟加载,如果实例化的对象长时间不用,占用空间;
- 性能比较好;
懒汉式
用这个对象的时候才去实例化
public class HoonSingleton {
private static HoonSingleton hoonSingleton=null;
private HoonSingleton(){
}
public static HoonSingleton getIntance() throws InterruptedException {
if(hoonSingleton==null){
hoonSingleton=new HoonSingleton();
}
return hoonSingleton;
}
}
并发环境下,存在线程安全问题
public class HoonSingleton {
private static HoonSingleton hoonSingleton=null;
private HoonSingleton(){
}
public static HoonSingleton getIntance() throws InterruptedException {
if(hoonSingleton==null){
//模拟并发环境线程阻塞
Thread.sleep(1);
hoonSingleton=new HoonSingleton();
}
return hoonSingleton;
}
public static void main(String[] args) {
for (int i = 0; i < 20; i++) {
new Thread(new Runnable() {
public void run() {
HoonSingleton intance = null;
try {
intance = HoonSingleton.getIntance();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(intance);
}
}).start();
}
}
}
可以看到实例化出了多个对象
懒汉式+同步方法
将实例化过程放到同步方法中
public class HoonSingleton {
private static HoonSingleton hoonSingleton=null;
private HoonSingleton(){
}
public synchronized static HoonSingleton getIntance() throws InterruptedException {
if(hoonSingleton==null){
hoonSingleton=new HoonSingleton();
}
return hoonSingleton;
}
}
保证每次第一次实例化只有一个线程执行,后面的线程不执行;但是这样锁的粒度太粗了,退化成了串行执行;
Double-Check-Locking
减小锁的粒度
public class HoonDoubleSyncSingleton {
private static HoonDoubleSyncSingleton hoonSingleton=null;
private HoonDoubleSyncSingleton(){
}
public static HoonDoubleSyncSingleton getIntance() throws InterruptedException {
//第一次检索:挡住后面大多数线程
if(hoonSingleton==null){
synchronized (HoonDoubleSyncSingleton.class){
//第二次检索:如果有多个线程通过第一次检索,则只有第一个线程可以实例化
if (hoonSingleton==null){
hoonSingleton=new HoonDoubleSyncSingleton();
}
}
}
return hoonSingleton;
}
保证了单例,但是没有保证指令重排序所带来的问题;
volatile-Douche-check
伪代码:
public class HoonDoubleSyncSingleton {
//模拟数据库、socket连接
static sqlConnection;
static socket;
private static HoonDoubleSyncSingleton hoonSingleton=null;
private HoonDoubleSyncSingleton(){
sqlConnection;
socket;
new HoonDoubleSyncSingleton();
}
public static HoonDoubleSyncSingleton getIntance() throws InterruptedException {
//第一次检索:挡住后面大多数线程
if(hoonSingleton==null){
synchronized (HoonDoubleSyncSingleton.class){
//第二次检索:如果有多个线程通过第一次检索,则只有第一个线程可以实例化
if (hoonSingleton==null){
hoonSingleton=new HoonDoubleSyncSingleton();
}
}
}
return hoonSingleton;
}
如果发生指令重排序,即先创建对象,然后再建立连接,如果这时对象需要使用连接,则会出现空指针异常。
解决方法
用volatile修饰对象,内存屏障保证在创建对象前先建立连接。
Holder
声明类的时候,成员变量中不生命实例变量,而放到静态内部类中;
public class HolderDemo {
private static class Holder{
private static HolderDemo insance=new HolderDemo();
}
public static HolderDemo getInstance(){
return Holder.insance;
}
}
只有当调用getInstance方法时,才开始加载静态内部类,开始实例化对象,保证了懒加载;而且静态类被存放在方法区,所以对象只有一个而且在共享数据区。
枚举
public class EumSingleton {
private enum EnumDemo{
INSTANCE;
private static EumSingleton instance=new EumSingleton();
private EumSingleton getInstance(){
return instance;
}
}
public static EumSingleton getInstance(){
return EnumDemo.INSTANCE.getInstance();
}
}