安全发布对象
什么是安全发布?
发布对象:使一个对象能够被当前范围之外的代码所使用
代码示例
package com.yanxizhu.demo.concurrency.unSafePublish;
import com.yanxizhu.demo.concurrency.annotation.UnThreadSafety;
import lombok.extern.slf4j.Slf4j;
import java.util.Arrays;
/**
* @description: 安全发布,这个例子是线程不安全的
* @author: <a href="mailto:batis@foxmail.com">清风</a>
* @date: 2022/4/21 21:45
* @version: 1.0
*/
@Slf4j
@UnThreadSafety
public class UnSafePublish {
private String[] states = {"a", "b", "c"};
public String[] getStates() {
return states;
}
public static void main(String[] args) {
UnSafePublish unSafePublish = new UnSafePublish();
log.info("{}", Arrays.toString(unSafePublish.getStates()));
unSafePublish.getStates()[0] = "d";
log.info("{}", Arrays.toString(unSafePublish.getStates()));
}
}
运行结果:
21:48:00.633 [main] INFO com.yanxizhu.demo.concurrency.unSafePublish.UnSafePublish - [a, b, c]
21:48:00.636 [main] INFO com.yanxizhu.demo.concurrency.unSafePublish.UnSafePublish - [d, b, c]
说明:
上面代码,可以通过对象,调用getStates()方法获取数据,并可以外部直接修改里面的值。这样states的值是不安全的。
对象溢出
对象逸出:一种错误的发布。当一个对象还没有构造完成时,就使它被其他线程所见
代码示例
package com.yanxizhu.demo.concurrency.unSafePublish;
import lombok.extern.slf4j.Slf4j;
/**
* @description: 对象溢出
* @author: <a href="mailto:batis@foxmail.com">清风</a>
* @date: 2022/4/21 21:53
* @version: 1.0
*/
@Slf4j
public class Escape {
private int thisCanBeEascpe = 0;
public Escape() {
new InnerClass();
}
private class InnerClass {
public InnerClass() {
log.info("{}", Escape.this.thisCanBeEascpe);
}
}
public static void main(String[] args) {
new Escape();
}
}
输出结果:
21:55:01.416 [main] INFO com.yanxizhu.demo.concurrency.unSafePublish.Escape - 0
说明:Escape对象还没构造完,就被new InnerClass()线程使用Escape.this.thisCanBeEascpe。
安全发布对象4种方法
- 在静态初始化函数中初始化一个对象引用
- 将对象的引用保存到volatile类型域或者AtomicReference对象中
- 将对象的引用保存到某个正确构造对象的final类型域中
- 将对象的引用保存到一个由锁保护的域中
单例-懒汉模式
代码实例,线程不按钮
package com.yanxizhu.demo.concurrency.singleton;
import com.yanxizhu.demo.concurrency.annotation.UnThreadSafety;
/**
* @description: 单例-懒汉模式
* 在第一次使用时创建
* 单线程下没问题,多线程下可能出现问题
* @author: <a href="mailto:batis@foxmail.com">清风</a>
* @date: 2022/4/21 22:05
* @version: 1.0
*/
@UnThreadSafety
public class SingLetonLazyMode {
//私有构造方法
private SingLetonLazyMode() {}
//实例对象
private static SingLetonLazyMode instance = null;
//静态工厂方法
public static SingLetonLazyMode getInstance() {
//注意:多线程下问题,当2个线程同时判断到不为null,是都会调用私有构造方法,创建2个示例对象。
// 如果在构造方法中有对环境一些设置,2次执行会出现一些错误
if (null == instance) {
instance = new SingLetonLazyMode();
}
return instance;
}
}
说明:
上面的单例-懒汉模式是线程不安全的。多线程下问题,当2个线程同时判断到不为null,是都会调用私有构造方法,创建2个示例对象。
如果在构造方法中有对环境一些设置,2次执行会出现一些错误。
单例-饿汉模式
代码实例,线程按钮
package com.yanxizhu.demo.concurrency.singleton;
import com.yanxizhu.demo.concurrency.annotation.ThreadSafety;
/**
* @description: 单例-饿汉模式
* 类装载时创建,线程安全的
* 不足: 1、如果私有构造方法里面有过多的设置,会加载很慢
* 2、每次都会创建示例对象,造成资源浪费
* @author: <a href="mailto:batis@foxmail.com">清风</a>
* @date: 2022/4/21 22:20
* @version: 1.0
*/
@ThreadSafety
public class SingLetonHungryManMode {
private SingLetonHungryManMode() {}
private static SingLetonHungryManMode instance = new SingLetonHungryManMode();
public static SingLetonHungryManMode getInstance() {
return instance;
}
}
说明:
单例-饿汉模式,类装载时创建,线程安全的。
不足地方:
1、如果私有构造方法里面有过多的设置,会加载很慢。
2、每次都会创建示例对象,造成资源浪费。
使用需要考虑:私有构造方法里面是否有过多设置,创建对象是否被使用。
单例-懒汉模式改进(synchronized)
代码实例,线程安全,但是不推荐使用,性能不太好
package com.yanxizhu.demo.concurrency.singleton;
import com.yanxizhu.demo.concurrency.annotation.ThreadSafety;
import com.yanxizhu.demo.concurrency.annotation.UnRecommend;
/**
* @description: 单例-懒汉模式(synchronized保证线程保证线程安全)
* 在第一次使用时创建
* 单线程下没问题,多线程下通过synchronized保证线程只被执行一次,线程安全
* @author: <a href="mailto:batis@foxmail.com">清风</a>
* @date: 2022/4/21 22:05
* @version: 1.0
*/
@ThreadSafety
@UnRecommend
public class SingLetonLazyModeSynchronized {
//私有构造方法
private SingLetonLazyModeSynchronized() {}
//实例对象
private static SingLetonLazyModeSynchronized instance = null;
//静态工厂方法
public static synchronized SingLetonLazyModeSynchronized getInstance() {
//注意:多线程下问题,当2个线程同时判断到不为null,是都会调用私有构造方法,创建2个示例对象。
// 如果在构造方法中有对环境一些设置,2次执行会出现一些错误
if (null == instance) {
instance = new SingLetonLazyModeSynchronized();
}
return instance;
}
}
说明:
synchronized保证线程只能同时被执行一次,但是带来了性能上的开销。因此是线程安全但不推荐的写法。
单例-懒汉模式改进(双重同步锁单例)
代码实例,线程不安全的
package com.yanxizhu.demo.concurrency.singleton;
import com.yanxizhu.demo.concurrency.annotation.UnThreadSafety;
/**
* @description: 单例-懒汉模式(双重同步锁单例)
* 线程不安全
* @author: <a href="mailto:batis@foxmail.com">清风</a>
* @date: 2022/4/21 22:05
* @version: 1.0
*/
@UnThreadSafety
public class SingLetonLazyModeSynchronized {
//私有构造方法
private SingLetonLazyModeSynchronized() {}
//实例对象
private static SingLetonLazyModeSynchronized instance = null;
//静态工厂方法
public static SingLetonLazyModeSynchronized getInstance() {
if (null == instance) { //双重检测机制
//当第一个线程执行后,已经实例化一个对象了,第二个线程再次判断兑现是否实例。
//线程不安全问题所在:
//
synchronized(SingLetonLazyModeSynchronized.class) { //同步锁
if(null == instance) {
instance = new SingLetonLazyModeSynchronized();
}
}
}
return instance;
}
}
说明:
instance = new SingLetonLazyModeSynchronized();分为3步骤。
1、分配对象的内存空间。
2、初始化对象。
3、设置instance指向刚分配的内存。
问题分析:
多线程情况下,上面3步,可能发生指令重排。
JVM和CPU优化,发生了指令重排。
因为指令2和3没有直接关联,所以上面3步指令可能优化重排成:
1、分配对象的内存空间。
3、设置instance指向刚分配的内存。
2、初始化对象。
当第一个线程执行instance = new SingLetonLazyModeSynchronized();设置对象指向刚分配的内存地址,还没执行2初始化对象时,第二个线程判断对象的执行步为空,直接返回对象,此时由于2初始化对象还没执行,导致调用对象报错。
单列-懒汉模式(volatile+双重检测机制)最终版
代码实例,线程安全,懒汉模式最终版
package com.yanxizhu.demo.concurrency.singleton;
import com.yanxizhu.demo.concurrency.annotation.ThreadSafety;
import com.yanxizhu.demo.concurrency.annotation.UnRecommend;
/**
* @description: 单例-懒汉模式(volatile+双重检测机制单列) 线程安全
* @author: <a href="mailto:batis@foxmail.com">清风</a>
* @date: 2022/4/21 22:05
* @version: 1.0
*/
@ThreadSafety
public class SingLetonLazyModeSynchronizedDoubleSyncVolatile {
//私有构造方法
private SingLetonLazyModeSynchronizedDoubleSyncVolatile() {}
//实例对象
//volatile禁止指令重排
private volatile static SingLetonLazyModeSynchronizedDoubleSyncVolatile instance = null;
//静态工厂方法
public static SingLetonLazyModeSynchronizedDoubleSyncVolatile getInstance() {
if (null == instance) { //双重检测机制
synchronized (SingLetonLazyModeSynchronizedDoubleSyncVolatile.class) { //同步锁
if (null == instance) {
instance = new SingLetonLazyModeSynchronizedDoubleSyncVolatile();
}
}
}
return instance;
}
}
说明:
volatile+双重检测机制,volatile禁止指令重排,双重检测,加同步锁,保证多线程下线程安全。
单例-饿汉模式(静态代码块写法)
代码实例,错误的,会发生控制在异常
package com.yanxizhu.demo.concurrency.singleton;
import com.yanxizhu.demo.concurrency.annotation.ThreadSafety;
/**
* @description: 单例-饿汉模式,静态代码写法
* 注意:静态代码和静态域的顺序,不然会导致空指针
* @author: <a href="mailto:batis@foxmail.com">清风</a>
* @date: 2022/4/21 22:20
* @version: 1.0
*/
@ThreadSafety
public class SingLetonHungryManModeStatic {
private SingLetonHungryManModeStatic() {}
//静态代码块
static{
instance = new SingLetonHungryManModeStatic();
}
//静态作用域
private static SingLetonHungryManModeStatic instance = null;
public static SingLetonHungryManModeStatic getInstance() {
return instance;
}
public static void main(String[] args) {
System.out.println(getInstance().hashCode());
System.out.println(getInstance().hashCode());
}
}
输出结果:
Exception in thread "main" java.lang.NullPointerException
at com.yanxizhu.demo.concurrency.singleton.SingLetonHungryManModeStatic.main(SingLetonHungryManModeStatic.java:30)
错误原因:静态代码和静态域的顺序,导致空指针
代码调整后,正确代码
package com.yanxizhu.demo.concurrency.singleton;
import com.yanxizhu.demo.concurrency.annotation.ThreadSafety;
/**
* @description: 单例-饿汉模式,静态代码写法
* 注意:静态代码和静态域的顺序,不然会导致空指针
* @author: <a href="mailto:batis@foxmail.com">清风</a>
* @date: 2022/4/21 22:20
* @version: 1.0
*/
@ThreadSafety
public class SingLetonHungryManModeStatic {
private SingLetonHungryManModeStatic() {}
//静态作用域
private static SingLetonHungryManModeStatic instance = null;
//静态代码块
static{
instance = new SingLetonHungryManModeStatic();
}
public static SingLetonHungryManModeStatic getInstance() {
return instance;
}
public static void main(String[] args) {
System.out.println(getInstance().hashCode());
System.out.println(getInstance().hashCode());
}
}
输出结果:
75457651
75457651
单例-枚举模式线程安全
最终推荐版本时枚举模式的单例。
示例代码:
package com.yanxizhu.demo.concurrency.singleton;
import com.yanxizhu.demo.concurrency.annotation.Recommend;
import com.yanxizhu.demo.concurrency.annotation.ThreadSafety;
/**
* @description: 单例-枚举模式,线程最安全的,推荐使用的
* @author: <a href="mailto:batis@foxmail.com">清风</a>
* @date: 2022/4/21 23:11
* @version: 1.0
*/
@ThreadSafety
@Recommend
public class SingLetonEnum {
//私有构造方法
private SingLetonEnum() {};
public static SingLetonEnum getInstance() {
return SingLeton.INSTANCE.getSingLetonEnum();
};
private enum SingLeton {
INSTANCE;
private SingLetonEnum singLetonEnum;
//JVM保证这个方法绝对只会被执行一次
SingLeton(){
singLetonEnum = new SingLetonEnum();
}
public SingLetonEnum getSingLetonEnum() {
return singLetonEnum;
}
}
}
说明:枚举的构造方法,JVM保证这个方法绝对只会被执行一次。
线程安全,推荐使用的单例模式-枚举模式。
比饿汉模式更能保证安全,比懒汉模式更能保证性能。
枚举模式只有使用时调用一次,不会造成资源浪费。
总结:
饿汉模式:有2种写法,最后这种饿汉模式注意静态代码块和作用域顺序。
懒汉模式:只有最终版(volatile+双重检测机制)正确,其它都存在线程安全或性能问题。
单例模式,线程安全,最终推荐枚举模式。
评论 (0)