饿汉式
饿汉式版本1
1创建一个类并私有化构造器
public class Single1 {
private Single1(){};//通过私有化构造器,使得Single1不能被new
}
通过私有化构造器,使得Single1不能被new
2.在类中用 private static final 创造一个Single1的唯一实例 INSTANCE
说明:①通过static 关键字修饰属性是可以被多个对象所共享的,不会随着对象的不同而不同。②final 用来修饰变量:此时的"变量"就称为是一个常量,即不可以被修改。③静态变量随着类的加载而加载由于类只会加载一次,则静态变量在内存中也只会存在一份(保证线程安全)。通过两个关键字修饰,INSTANCE 达到了“共享”与“不可修改”的作用
public class Single1 {
private Single1(){};//通过私有化构造器,使得Single1不能被new
private static final Single1 INSTANCE = new Single1();//实例化
}
3.声明public、static的返回当前类对象的方法,
public class Single1 {
private Single1(){};//通过私有化构造器,使得Single1不能被new
private static final Single1 INSTANCE = new Single1();//实例化
public static Single1 getInstance(){//向外暴露调用方法
return INSTANCE;
}
}
外部通过调用getInstance来return INSTANCE 而获得当前对象
通过运行sout可以知道,s1,s2,s3都是属于同一个对象,即同一个实例
饿汉式版本2
版本2与版本1的区别是使用静态代码块赋值
package com.shun.Single;
public class Single2 {
private Single2(){};//私有化构造器
private static final Single2 INSTANCE;//声明变量
static {//使用静态代码块赋值
INSTANCE =new Single2();
}
public static Single2 getInstance(){
return INSTANCE;
}
}
特别说明,使用static修饰的代码块称为静态代码块。
静态代码块:随着类的加载而执行,而且只执行一次(保证线程安全)
static { //使用静态代码块赋值初始化类的信息
INSTANCE =new Single2();
}
测试饿汉式是否线程安全
package com.shun.Single;
public class Single1 {
private Single1(){};
private static final Single1 INSTANCE = new Single1();
public static Single1 getInstance(){
return INSTANCE;
}
//通过创建100个线程测试
public static void main(String[] args) {
for(int i=0;i<100;i++){
new Thread(()->{//打印当前线程对象的hashCode(是否相同)
System.out.println(Single1.getInstance().hashCode());
}).start();
}
}
}
Single1运行main方法
运行结果Single1所有对象的hashCode相同说明是同一个实例(线程安全)
Single2
运行结果Single2所有对象的hashCode相同说明是同一个实例(线程安全)
懒汉式
懒汉式版本1
1懒汉式首先并没有一开始就实例化。
public class Single3 {
private static Single3 INSTANCE;
private Single3(){}
public static Single3 getInstance(){
if(INSTANCE == null){
INSTANCE = new Single3();
}
return INSTANCE;
}
}
当调用getInstances时通过判断 INSTANCE 是否为null,如果为空就会创建实例。理论上当被第一个线程调用后,便会创建一个实例,在以后其它对象调用时使用的就是同一个实例(因为INSTANCE不为null)便不会创建一个新的。
public static Single3 getInstance(){
if(INSTANCE == null){
INSTANCE = new Single3();
}
return INSTANCE;
}
但是会存在线程安全,注意看以下代码在new Single3之前先进行Thread.sleep(1);将线程挂起一毫秒
public class Single3 {
private static Single3 INSTANCE;
private Single3(){}
public static Single3 getInstance(){
if(INSTANCE == null){
try{
Thread.sleep(1);//人为将线程挂起1毫秒
}catch (InterruptedException e){
e.printStackTrace();
}
INSTANCE = new Single3();
}
return INSTANCE;
}
public static void main(String[] args) {
for(int i=0;i<100;i++){
new Thread(()->{
System.out.println(Single3.getInstance().hashCode());
}).start();
}
}
}
测试懒汉式1是否线程安全?
运行main结果输出hashCode值不唯一,说明不是同一个实例(线程不安全)
因为在如下方法中,当第一个线程进来判断INSTANCE时INSTANCE为null但是并没有直接执行创建Single3实例而是先被挂起1毫秒,此时第二个线程进来时发现INSTANCE还没有被第一个线程创建(被sleep(1))且INSTANCE也为null,这样也进到方法里来了。此时就会被多个线程创建不同的实例,即线程不安全。
public static Single3 getInstance(){
if(INSTANCE == null){
try{
Thread.sleep(1);//人为将线程挂起1毫秒
}catch (InterruptedException e){
e.printStackTrace();
}
INSTANCE = new Single3();
}
return INSTANCE;
}
懒汉式版本2
通过加入synchronized关键字(加锁)实现线程安全
synchronized在修饰静态方法,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁。
public class Single4 {
private static Single4 INSTANCE;
private Single4(){}
public static synchronized Single4 getInstance(){
if(INSTANCE == null){
try{
Thread.sleep(1);
}catch (InterruptedException e){
e.printStackTrace();
}
INSTANCE = new Single4();
}
return INSTANCE;
}
}
运行main方法
运行结果hashCode值相同线程安全
通过加锁,虽然解决了线程安全,但是会影响运行效率每个线程必须等前面线程释放锁才能执行下一个线程。
懒汉式版本3
通过双重判断实现线程安全
public class Single5 {
private static volatile Single5 INSTANCE;
private Single5(){}
public static Single5 getInstance(){
if(INSTANCE == null){
synchronized(Single5.class){
if(INSTANCE == null){
try{
Thread.sleep(1);
}catch (InterruptedException e){
e.printStackTrace();
}
INSTANCE = new Single5();
}
}
}
return INSTANCE;
}
}
注意此时的getInstance方法,在调用时进行双重判断,第一个先判断是否为null,如果为null则上锁,上锁后再判断是否为null此时为null再new 出实例。如果在某个线程执行锁之前new 出来了一个INSTANCE的话,在第二个线程进入锁时INSTANCE不为null就new不出来了。当后面的线程执行到第一个判断时便不会执行锁里面的内容了。极大减少了锁的使用又保证了线程的安全。
public static Single5 getInstance(){
if(INSTANCE == null){
synchronized(Single5.class){
if(INSTANCE == null){
try{
Thread.sleep(1);
}catch (InterruptedException e){
e.printStackTrace();
}
INSTANCE = new Single5();
}
}
}
return INSTANCE;
}
测试
运行结果hashCode值相同线程安全。
静态内部类实现单例模式
通过使用静态内部类,在静态内部类Single7Hoder 中new INSTANCE
使用getInstance 来返回内部类new的外部类的实例。
public class Single6 {
private Single6(){}
private static class Single7Hoder{
private final static Single6 INSTANCE = new Single6();
}
public static Single6 getInstance(){
return Single7Hoder.INSTANCE;
}
}
特别说明:①静态内部类不会因为外部类的加载而加载
②当静态内部类的变量被调用时,从而才进行了加载
所以当被getInstance调用时才进行加载。
测试
运行结果:线程安全,因为虚拟机加载静态内部类的时候只加载一次
枚举类实现单例
public enum Single7 {
INSTANCE;
}
运行结果