目录
应用场景
只需要一个实例
单例模式是指不允许用户调构造方法自己创建,通过静态方法获取对象,且获取的是同一个实例。
工作中用什么样的方式解决实际问题,不追求不必要的完美
主要分为:饿汉式、懒汉式、双重校验锁的方式、静态内部类的方式、枚举的方式
严格来讲,它有八种写法,只有两种是完美无缺的!
01、饿汉式【线程安全-最常见】
package com.yyh.singleton;
/**
* 饿汉式【线程安全-最常见】
* 类加载到内存后,就实例化一个对象,JVM保证线程安全
* 简单实用,推荐使用!
* 唯一缺点:不管用到与否,类装载时就完成实例化
* (话说你不用它,你装载它干啥)
*/
public class Mgr01 {
private static final Mgr01 INSTANCE = new Mgr01();
private Mgr01(){}
public static Mgr01 getInstance(){
return INSTANCE;
}
public void m(){
System.out.println("m");
}
public static void main(String[] args){
Mgr01 m1 = Mgr01.getInstance();
Mgr01 m2 = Mgr01.getInstance();
System.out.println(m1 == m2);
}
}
m1==m2 说明这两个的引用是指向的一个对象,即只有一个实例。
private static final Mgr01 INSTANCE = new Mgr01();
不管用到与否,类装载时就完成实例化!
能不能把创建的这个过程放在一个方法里需要的时候再创建,也就是用到再实例化,
于是就有了懒汉式的写法:03、懒汉式【线程不安全】
02、饿汉式-写法2【线程安全】
package com.yyh.singleton;
/**
* 饿汉式-写法2【线程安全】
* 跟01是一个意思,写法有点不同
*/
public class Mgr02 {
private static final Mgr02 INSTANCE;
static{
INSTANCE = new Mgr02();
}
private Mgr02(){}
public static Mgr02 getInstance(){
return INSTANCE;
}
public void m(){
System.out.println("m");
}
public static void main(String[] args){
Mgr02 m1 = Mgr02.getInstance();
Mgr02 m2 = Mgr02.getInstance();
System.out.println(m1 == m2);
}
}
03、懒汉式【线程不安全】
同一个类的不同对象,它的hash码是不同的。hash码相同也有可能不是同一个对象。
添加代码睡眠一秒的目的,即线程之间运行,被其他线程打断的机会就增加了,更容易发现懒汉式的问题。
final修饰必须初始化,所以final加不了
package com.yyh.singleton;
/**
* lazy loading 也称懒汉式【线程不安全】
* 虽然达到了按需初始化的目的,但却带来线程不安全的问题
*/
public class Mgr03 {
//final修饰必须初始化,所以final加不了
//private final static Mgr03 INSTANCE;
private static Mgr03 INSTANCE;
private Mgr03(){}
public static Mgr03 getInstance(){
if(INSTANCE == null){
try{
//添加代码睡眠1秒的目的:
//即线程之间运行,被其他线程打断的机会就增加了,更容易发现懒汉式的问题。
Thread.sleep(1);
}catch (Exception e){
e.printStackTrace();
}
INSTANCE = new Mgr03();
}
return INSTANCE;
}
public void m(){
System.out.println("m");
}
public static void main(String[] args){
for(int i=0; i<100; i++){
new Thread(()->{
//同一个类的不同对象,它的hash码是不同的。hash码相同也有可能不是同一个对象。
System.out.println(Mgr03.getInstance().hashCode());
}).start();
}
}
}
多线程下发现会出现不同对象,达不到单线程的效果。分析如下:
步骤1:一个线程A,进入if(INSTANCE == null)以后暂停,
步骤2:线程B进入if(INSTANCE == null),new了一个对象以后,线程B暂停
步骤3:这时候线程A继续执行,又new了一对象
这时候就有两个对象了!
04、懒汉式【线程安全】
package com.yyh.singleton;
/**
* lazy loading 也称懒汉式【线程安全】
* 虽然达到了按需初始化的目的,但却带来线程不安全的问题
* 可以通过 synchronized 解决,但也带来效率下降
*/
public class Mgr04 {
//final修饰必须初始化,所以final加不了
//private final static Mgr04 INSTANCE;
private static Mgr04 INSTANCE;
private Mgr04(){}
//锁定当前对象
public static synchronized Mgr04 getInstance(){
//业务代码
//业务代码
if(INSTANCE == null){
try{
//添加代码睡眠1秒的目的:
//即线程之间运行,被其他线程打断的机会就增加了,更容易发现懒汉式的问题。
Thread.sleep(1);
}catch (Exception e){
e.printStackTrace();
}
INSTANCE = new Mgr04();
}
return INSTANCE;
}
public void m(){
System.out.println("m");
}
public static void main(String[] args){
for(int i=0; i<100; i++){
new Thread(()->{
//同一个类的不同对象,它的hash码是不同的。hash码相同也有可能不是同一个对象。
System.out.println(Mgr04.getInstance().hashCode());
}).start();
}
}
}
但是这种直接把整个方法全锁了,这种锁太粗了,我的 "业务代码" 也执行不了,
在锁的优化中,有一种就是锁细化。
05、懒汉式【线程不安全】
package com.yyh.singleton;
/**
* lazy loading 也称懒汉式【线程不安全】
* 虽然达到了按需初始化的目的,但却带来线程不安全的问题
* 可以通过 synchronized 解决,但也带来效率下降
*
* 加锁的同时,如何提高效率?
* 不在整个方法上加锁,在方法内部需要的时候加锁
* 多线程访问的时候,会出现线程安全问题
*/
public class Mgr05 {
//final修饰必须初始化,所以final加不了
//private final static Mgr04 INSTANCE;
private static Mgr05 INSTANCE;
private Mgr05(){}
//锁定当前对象
//public static synchronized Mgr05 getInstance(){
public static Mgr05 getInstance(){
//业务代码
//业务代码
if(INSTANCE == null){
//妄图通过减少同步代码块的方式提高效率,然而不可行
synchronized (Mgr05.class){
try{
//添加代码睡眠1秒的目的:
//即线程之间运行,被其他线程打断的机会就增加了,更容易发现懒汉式的问题。
Thread.sleep(1);
}catch (Exception e){
e.printStackTrace();
}
}
INSTANCE = new Mgr05();
}
return INSTANCE;
}
public void m(){
System.out.println("m");
}
public static void main(String[] args){
for(int i=0; i<100; i++){
new Thread(()->{
//同一个类的不同对象,它的hash码是不同的。hash码相同也有可能不是同一个对象。
System.out.println(Mgr05.getInstance().hashCode());
}).start();
}
}
}
多线程下发现会出现不同对象,达不到单线程的效果。分析如下:
步骤1:一个线程A,进入if(INSTANCE == null)以后暂停,
步骤2:线程B进入if(INSTANCE == null),加锁,new了一个对象以后,解锁
步骤3:由于线程B解锁了,这时候线程A继续执行,加锁,又new了一对象,
这时候就有两个对象了!
06、双重检验锁【线程安全】---volatile
package com.yyh.singleton;
/**
* lazy loading 也称懒汉式【线程安全】
* 虽然达到了按需初始化的目的,但却带来线程不安全的问题
* 可以通过 synchronized 解决,但也带来效率下降
*
* 加锁的同时,如何提高效率?
* 不在整个方法上加锁,在方法内部需要的时候加锁
* 多线程访问的时候,会出现线程安全问题
*
* 怎么解决?有人想出------->双重检验锁【线程安全】
*/
public class Mgr06 {
//final修饰必须初始化,所以final加不了
//private final static Mgr06 INSTANCE;
//private static Mgr06 INSTANCE;
//volatile表示禁止指令重排序。出现几率极少,几百万分之一
private static volatile Mgr06 INSTANCE;
private Mgr06(){}
//锁定当前对象
//public static synchronized Mgr05 getInstance(){
public static Mgr06 getInstance(){
if(INSTANCE == null){//Double check lock 【DCL】
//妄图通过减少同步代码块的方式提高效率,然而不可行
synchronized (Mgr06.class){
if(INSTANCE == null){//双重检查
try{
//添加代码睡眠1秒的目的:
//即线程之间运行,被其他线程打断的机会就增加了,更容易发现懒汉式的问题。
Thread.sleep(1);
}catch (Exception e){
e.printStackTrace();
}
INSTANCE = new Mgr06();
}
}
}
return INSTANCE;
}
public void m(){
System.out.println("m");
}
public static void main(String[] args){
for(int i=0; i<100; i++){
new Thread(()->{
//同一个类的不同对象,它的hash码是不同的。hash码相同也有可能不是同一个对象。
System.out.println(Mgr06.getInstance().hashCode());
}).start();
}
}
}
真正 new 的过程,是由上图几步构成的,主要是指下面三步:
(1)new指令:在堆中开辟一块内存。成员变量 m 的值设为0
(2)invokespecial指令:调用构造方法。把初始化 m 的值变成8
(3)astore指令:建立关联。对象变量指向这块内存
指令重排,astore指令建立关联,线程得到的 m 的值为0,得到的是半初始化的值
07、静态内部类方式【线程安全】
package com.yyh.singleton;
/**
* 静态内部类方式【线程安全】
* JVM保证单例
* 加载外部类时不会加载内部类,这样可以实现懒加载
*/
public class Mgr07 {
private Mgr07(){}
private static class Mgr07Holder{
private final static Mgr07 INSTANCE = new Mgr07();
}
public static Mgr07 getInstance(){
return Mgr07Holder.INSTANCE;
}
public void m(){
System.out.println("m");
}
public static void main(String[] args){
for(int i=0; i<100; i++){
new Thread(()->{
//同一个类的不同对象,它的hash码是不同的。hash码相同也有可能不是同一个对象。
System.out.println(Mgr07.getInstance().hashCode());
}).start();
}
}
}
08、枚举的方式【线程安全,防止反序列化】
package com.yyh.singleton;
/**
* 枚举的方式【线程安全,防止反序列化】
* 不仅可以解决线程同步,还可以防止反序列化问题
*/
public enum Mgr08 {
INSTANCE;
public void m(){
System.out.println("m");
}
public static void main(String[] args){
for(int i=0; i<100; i++){
new Thread(()->{
//同一个类的不同对象,它的hash码是不同的。hash码相同也有可能不是同一个对象。
System.out.println(Mgr08.INSTANCE.hashCode());
}).start();
}
}
}