单例概念
- 单例模式属于创建者模式,该模式提供了一种最佳的创建对象方式,为何最佳??
- 单例是指对某个类而言,该类负责自己创建自己,同时确保只有唯一的对象被创建
- 同时该类对外提供访问该唯一实例的方式,外界不能重复创建,取用即可
实际意义
- 全局只需要该类的唯一对象即可,节省系统资源内存开销
- 案例:一个公司只需一个老板;创建的一个对象需要消耗太多资源,如与数据库连接
- 在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡驱动的对象通常设计成单例,这些应用都或多或少具有资源管理器的功能,即统一供各使用方使用,所有操作都在此处,避免不一致的状态,避免政出多头
- 单例对象通常作为程序中存放配置信息的载体,因为它能保证其他对象读到一致的信息
- 如服务器程序中,该服务器的配置信息可能存放在数据库或文件中,这些配置数据由某个单例对象统一读取,服务器中其他对象若要获取这些配置信息,只需访问该单例对象即可,但对该单例的访问可能涉及到同步问题
具体实现
- 饿汉式
-
- 线程安全
-
- 类加载即初始化,基于classloader 机制避免了多线程同步问题
-
- static getInstance接口是触发类加载的一种方式,此刻是实例的lazy loading,其他触发类加载则**达不到lazy loading 的效果
-
- 容易产生垃圾对象
-
/**
* 饿汉单例模式
*/
public class MyHungerSingleton {
//static:类加载时期即初始化 singleton,全局唯一性
private static MyHungerSingleton singleton = new MyHungerSingleton();
//私有构造函数,避免外界new 实例
private MyHungerSingleton() {
}
//对外提供的获取实例接口,该方法第一次调用则会导致类加载,从而初始化 static singleton 变量,
//后面调用时,singleton 变量已初始化成功,直接使用即可,所以无需判断
public static MyHungerSingleton getInstance() {
return singleton;
}
}
- 懒汉式
- 延迟加载,只在getInstance 接口调用时才会初始化唯一实例
- 非线程安全
- if判断与其后的实例赋值语句在CPU层面会有时间间隔,这段时间差是导致多线程问题的根本原因???
/**
* 懒汉单例模式
*/
public class MyLazySingleton {
//static 全局变量,但类加载时不初始化
private static MyLazySingleton myLazySingleton = null;
//私有构造函数
private MyLazySingleton() {
}
//方法1:线程不安全
public static MyLazySingleton getInstance() {
//判断全局实例是否存在,若不判断,则会新产生一个实例,之前的实例对象仍存在,只是无指针指向
//不判断则会重复创建,浪费资源
//if判断与其后的实例赋值语句在CPU层面会有时间间隔,这段时间差是导致多线程问题的根本原因
if (myLazySingleton == null) {
myLazySingleton = new MyLazySingleton();
}
return myLazySingleton;
}
}
- 解决懒汉的多线程问题
- 延迟加载,第一次调用初始化,避免内存浪费
- 多线程安全
- 对方法加锁效率低???
- getInstance()性能 对应用程序不是很关键???
public class MyLazySingleton {
//static 全局变量,但类加载时不初始化
private static MyLazySingleton myLazySingleton = null;
//私有构造函数
private MyLazySingleton() {
}
......
//方法2:线程安全,效率低
//添加Synchronized 关键字,只允许同一段时间内只有一个线程进入该方法
//拒绝出现多线程同时if 判断和赋值的操作
//针对的是整个方法
public static synchronized MyLazySingleton getInstance() {
//if判断仍需要断定全局是否已经存在singleton 变量
if (myLazySingleton == null) {
myLazySingleton = new MyLazySingleton();
}
return myLazySingleton;
}
}
- 双检锁/双重校验锁
- lazy 初始化
- 多线程安全
- 高性能???
- getInstance() 的性能对应用程序很关键???
锁1
public class MyLazySingleton {
//static 全局变量,但类加载时不初始化
private static MyLazySingleton myLazySingleton = null;
//任意锁对象
private static Object synLock = new Object();
//私有构造函数
private MyLazySingleton() {
}
......
//方法3:线程安全,效率高
public static MyLazySingleton getInstance3() {
if (myLazySingleton == null) {
//静态方法内不能锁 this 对象,故单独使用了一个static 对象
synchronized (synLock) {
if (myLazySingleton == null) {
myLazySingleton = new MyLazySingleton();
}
}
}
return myLazySingleton;
}
}
锁2
//方法3:线程安全,效率高
public static MyLazySingleton getInstance3() {
if (myLazySingleton == null) {
//synchronized (synLock) {
//直接锁class 对象
synchronized (MyLazySingleton.class) {
if (myLazySingleton == null) {
myLazySingleton = new MyLazySingleton();
}
}
}
return myLazySingleton;
}
附:懒汉无加锁环境到枷锁环境整个过程的问题展现
public class MyLazySingleton {
private static MyLazySingleton myLazySingleton = null;
private MyLazySingleton() {
}
......
//多个线程执行该代码
public static MyLazySingleton getInstance1() throws InterruptedException {
if (myLazySingleton == null) {
//制造创建单例前的准备工作,放大判断语句和赋值语句时间间隔
Thread.sleep(2000);
myLazySingleton = new MyLazySingleton();
}
return myLazySingleton;
}
}
public class MySingletonDemo extends Thread{
public void run() {
//MyHungerSingleton singleton = MyHungerSingleton.getInstance();
MyLazySingleton singleton = null;
try {
singleton = MyLazySingleton.getInstance1();
} catch (InterruptedException e) {
e.printStackTrace();
}
//打印出每个线程得到的 单例对象 的 hashcode
System.out.println("ThreadName: " + Thread.currentThread().getName() + ", hashcode: " + singleton.hashCode());
}
public static void main(String[] args) {
Thread[] testThreads = new Thread[10];
for(int i = 0; i< 10; i++) {
testThreads[i] = new MySingletonDemo();
}
for (Thread item : testThreads) {
//启动每个线程
item.start();
}
}
}
1) 输出
ThreadName: Thread-9, hashcode: 915343504
ThreadName: Thread-8, hashcode: 1516995504
ThreadName: Thread-7, hashcode: 1127673581
ThreadName: Thread-5, hashcode: 1905381423
ThreadName: Thread-6, hashcode: 1211216510
ThreadName: Thread-4, hashcode: 982120049
ThreadName: Thread-3, hashcode: 1222692380
ThreadName: Thread-1, hashcode: 936272565
ThreadName: Thread-2, hashcode: 1098437177
ThreadName: Thread-0, hashcode: 726367991
2) 注释掉ThreadSleep代码后结果,此刻由于判断与赋值间隔很短,出现了一致的效果
ThreadName: Thread-0, hashcode: 726367991
ThreadName: Thread-1, hashcode: 726367991
ThreadName: Thread-2, hashcode: 726367991
ThreadName: Thread-3, hashcode: 726367991
ThreadName: Thread-4, hashcode: 726367991
ThreadName: Thread-5, hashcode: 726367991
ThreadName: Thread-6, hashcode: 726367991
ThreadName: Thread-7, hashcode: 726367991
ThreadName: Thread-8, hashcode: 726367991
ThreadName: Thread-9, hashcode: 726367991
3) 为方法加上 synchronized 关键字
ThreadName: Thread-4, hashcode: 915343504
ThreadName: Thread-1, hashcode: 915343504
ThreadName: Thread-7, hashcode: 915343504
ThreadName: Thread-2, hashcode: 915343504
ThreadName: Thread-8, hashcode: 915343504
ThreadName: Thread-5, hashcode: 915343504
ThreadName: Thread-3, hashcode: 915343504
ThreadName: Thread-9, hashcode: 915343504
ThreadName: Thread-6, hashcode: 915343504
ThreadName: Thread-0, hashcode: 915343504
4) 使用 synchronized 代码块
-----------------------------------------------------------------------------
synchronized (MyLazySingleton.class) { //synchronized位置 1,达到同步效果
if (myLazySingleton == null) {
//创建单例前的准备工作
Thread.sleep(3000);
myLazySingleton = new MyLazySingleton();
}
}
-----------------------------------------------------------------------------
if (myLazySingleton == null) {
//synchronized位置 2,达不到同步效果
//因为多个线程都已经执行完判断,都判断到实例为空,都排队等着new 一个呢
//所以最后生成的实例都不同
synchronized (MyLazySingleton.class) {
//创建单例前的准备工作
Thread.sleep(3000);
myLazySingleton = new MyLazySingleton();
}
}
-----------------------------------------------------------------------------
if (myLazySingleton == null) { //判断 1
//synchronized位置 3
synchronized (MyLazySingleton.class) {
//判断 2,再次进行判断,因为之前最开始的判断可能失效
//因为其他线程已经释放锁且 new 值了此刻已经不为 null
//避免二次赋值
if (myLazySingleton == null) {
//创建单例前的准备工作
Thread.sleep(3000);
myLazySingleton = new MyLazySingleton();
}
}
}
-----------------------------------------------------------------------------
volatile 变量作用
- 线程可见性,A线程对线程共享变量的修改能立即被B线程感知,即A修改后,B能立刻读取最新修改值
- 读:A线程读取volatile 修饰的变量时,会从主线程重新拉取,保证子线程与主线程一致
- 写:A线程修改volatile 变量时,会在修改结束后,刷新到主线程中,使主线程为最新值
- 禁止指令重排序
- A a = new A() 触发类加载
- 执行顺序非原子性
-
- 为A 对象分配内存空间
-
- 对象的初始化
-
- a 引用指向 内存空间
-
- 理想顺序1->2->3
- 实际可能为 1->3->2,此刻对象还没有完全初始化,但a 引用已经!=null,有了内存地址,导致对a 的操作是针对不完整对象的
- 执行顺序非原子性
- A a = new A() 触发类加载
双检锁情况下 volatile 变量的使用
- 双检锁不一定完全正确
- 原因在于两次判断时,所判断变量皆是线程局部变量,而非主线程变量
- 或者说,其他线程对 static 实例的修改new 操作未必立即刷新到 主线程中,
- 从而导致A线程已经new,但未刷新到主线程, 但B线程第二次判断是并不知晓
private static volatile MyHungerSingleton singleton;