单例模式看着一篇就够了
问题:
多个线程操作不同实例对象,现在需要多个线程要操作同一对象,要保证对象的唯一性。
解决问题:
实例化过程中只实例化一次
单例模式属于创造型模式,是常用的设计模式之一。
单例模式(Singleton),保证一个类仅有一个实例,并提供一个访问它的全局访问点。
如何区分饿汉式和懒汉式:在自己被加载时就将自己实例化,称之为饿汉式单例类,而要是在第一次被引用时,才会将自己实例化,称之为懒汉式。
饿汉式
线程安全:在加载的时候(ClassLoader) 已经被实例化,所以只有一次,线程安全的。
懒加载:没有延迟加载,对于长时间不使用,影响性能,如果占的内存多会导致内存溢出
性能:比较好
package sheji;
/**
* @author zhr
* @create 2018-11-15 下午4:02
*/
public class Singleton {
//static 在加载的时候就产生了实例对象
private static Singleton instance = new Singleton();
private Singleton(){
}
public static Singleton getInstance(){
return instance;
}
public static void main(String[] args) {
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
if(s1 == s2){
System.out.println("这两个对象是相同的实例");
}
}
}
控制台打印:这两个对象是相同的实例
懒汉式
线程安全:不能保证实例对象的唯一性,可能出现多个线程同时进入到instance==null中 生成多个对象(一票否决不要用)
懒加载:有延迟加载
性能:好
跟饿汉式比:优化了在调用过程的时候才去实例化,但又出现了新的问题那就是线程安全的问题
/**
* @author zhr
* @create 2018-11-15 下午3:59
*/
public class Singleton {
private static Singleton instance;
private Singleton(){
}
public static Singleton getInstance(){
if(instance == null){
instance = new Singleton();
}
return instance;
}
public static void main(String[] args) {
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
if(s1 == s2){
System.out.println("这两个对象是相同的实例");
}
}
}
控制台打印:这两个对象是相同的实例
懒汉式+同步方法
线程安全:可以保证实例唯一性
懒加载:有延迟加载
性能:不好,因为使用了synchronized 多个线程访问的会后就会蜕化到了串行执行
跟懒汉式比:线程安全了,但是锁的粒度太大性能损失很大
/**
* @author zhr
* @create 2018-11-15 下午4:00
*/
public class Singleton {
private static Singleton instance;
private Singleton(){
}
public synchronized static Singleton getInstance(){
if(instance == null){
instance = new Singleton();
}
return instance;
}
public static void main(String[] args) {
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
if(s1 == s2){
System.out.println("这两个对象是相同的实例");
}
}
}
控制台打印:这两个对象是相同的实例
懒汉式+同步代码块
线程安全:不能保证实例唯一
懒加载:有延迟加载
性能:一般
懒汉式+同步方法比较:我们锁的粒度虽然减小了,但是在并发的环境下当,多个线程同时进入到instance==null的时候第一个实例创建对象,释放锁的时候第二个线程可能就会创建对象,从而导致线程不安全
/**
* @author zhr
* @create 2018-11-15 下午4:07
*/
public class Singleton {
private static Singleton instance;
private Singleton(){
}
public static Singleton getInstance(){
if(instance == null){
synchronized (Singleton.class){
instance = new Singleton();
}
}
return instance;
}
public static void main(String[] args) {
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
if(s1 == s2){
System.out.println("这两个对象是相同的实例");
}
}
}
控制台打印:这两个对象是相同的实例
双重校验锁 DCL
线程安全:可以保证实例唯一性
懒加载:有延迟加载
性能:比较好
问题:因为happens-before指令重排,里面有多个引用对象需要初始化的时候,会出现空指针异常
/**
* @author zhr
* @create 2019-09-14 下午3:08
*/
public class Singleton {
private static Singleton instance;
Connection conn;
Socket socket;
private Singleton(){
//初始化conn 和 socket 这里指令重排 把初始化放在前面了,导致第一个线程初始化了,第二个线程进来认为已经初始化了,然后获取了conn就会出现空指针异常
}
public static Singleton getInstance(){
if(instance == null){
synchronized (Singleton.class){
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}
public static void main(String[] args) {
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
if(s1 == s2){
System.out.println("这两个对象是相同的实例");
}
}
}
控制台打印:这两个对象是相同的实例
Volatile+双重校验锁 DCL
线程安全:可以保证实例唯一性
懒加载:有延迟加载
性能:比较好(推荐使用)
解决DCL指令重排问题方案是机上volatile使初始化前的东西不能被指令重排就可以了。
/**
* @author zhr
* @create 2019-09-14 下午3:10
*/
public class Singleton {
private volatile static Singleton instance;
Connection conn;
Socket socket;
private Singleton(){
}
public static Singleton getInstance(){
if(instance == null){
synchronized (Singleton.class){
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}
public static void main(String[] args) {
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
if(s1 == s2){
System.out.println("这两个对象是相同的实例");
}
}
}
控制台打印:这两个对象是相同的实例
Holder持有者
声明类的时候,成员变量中不声明实例变量,而放到内部静态类中,在内部静态类中我们提供实例化这个类,主要是为了避免加锁,只有主动使用的时候才会去进行实例化
线程安全:可以保证实例唯一性
懒加载:有延迟加载
性能:比较好(广泛使用)
/**
* @author zhr
* @create 2019-09-14 下午3:20
*/
public class HolderDemo{
//可以删除
private HolderDemo(){
}
private static class Holder{
//懒加载 没加锁
private static HolderDemo instance = new HolderDemo();
}
public static HolderDemo getInstance(){
return Holder.instance;
}
}
控制台打印:这两个对象是相同的实例
枚举
线程安全:可以保证实例唯一性
懒加载:没有延迟加载
性能:比较好(广泛使用)
评价:,其实跟饿汉式很像,代码简洁优雅
/**
* @author zhr
* @create 2019-09-14 下午3:25
*/
public enum EnumSingleton{
//常量,在加载的时候被实例化一次
INSTANCE;
public static EnumSingleton getInstance(){
return INSTANCE;
}
}
枚举+Holder
线程安全:可以保证实例唯一性
懒加载:有延迟加载
性能:比较好(广泛使用)
/**
* @author zhr
* @create 2019-09-14 下午3:30
*/
public class EnumSingletonDemo{
private EnumSingletonDemo(){
}
//静态内部类 只有在使用的时候才会加载实例化
private enum EnumHolder{
//常量,在加载的时候被实例化一次,方法区
INSTANCE;
private EnumSingletonDemo instance;
EnumHolder(){
this.instance = new EnumSingletonDemo();
}
private EnumSingletonDemo getInstance(){
return instance;
}
}//懒加载
public static EnumSingletonDemo getInstance(){
return EnumHolder.INSTANCE.getInstance();
//return EnumHolder.INSTANCE.instance;
}
}
如果看完感觉受益匪浅,可以帮忙点点关注,点点赞,还会持续更新好的作品。