简介 总结
1. 安全发布对象-发布与溢出
2. 安全发布对象-四种方法
不安全发布代码示例
package com.mmall.concurrency.publishDemo;
import lombok.extern.slf4j.Slf4j;
import java.util.Arrays;
/**
* 定义一个数组,发布get方法,其他示例线程修改数组
*/
@Slf4j
public class UnSafePub {
private String[] states = {"a","b","c","d"};
// 发布出去一个
public String[] getStates(){
return states;
}
public static void main(String[] args){
UnSafePub unSafePub = new UnSafePub();
// log.info("init array: {}", unSafePub.getStates());
log.info("Init array is: {}", Arrays.toString(unSafePub.getStates()));
unSafePub.getStates()[0] = "FUCK!";
log.info("After modify.the array is: {}", Arrays.toString(unSafePub.getStates()));
}
}
对象溢出
package com.mmall.concurrency.publishDemo;
import lombok.extern.slf4j.Slf4j;
/**
* 线程溢出
*/
@Slf4j
public class Escape {
private int thisCanBeEscape = 0;
public Escape(){
new InnerClass();
}
//内部类
public class InnerClass {
public InnerClass(){
log.info("{}", Escape.this.thisCanBeEscape);
}
}
//主方法测试
public static void main(String[] args){
new Escape();
}
}
案例分析
内部类包含对分装实例的隐涵应用,这样在对象没有被正确构造完成之前它就会被发布,有可能有不安全的因素,一个导致(Escape.this.thisCanBeEscape) 中,this引用在构造期间导致的错误他是在函数构造过程中(public Escape(){) 相当于启动了一个线程,无论是显式的还是隐式的,都会造成this引用的逸出错误,新线程总会在所属对象构造完成之前就看到他了,所以如果要在构造函数中创建线程,那么,不要启动它,而是应该用一个专有start方法或者一个统一启动的方法来启动,这里可以采用工厂方法和私有构造函数来完成对象创建和监听器的注册等操作,这样才可以避免不正确的创建,
注意, 我们这里的目的是:在对象没有完成构造之前,不可以将其发布,不安全,不推荐
不正确的发布可变对象,导致两种错误。
- 发布线程以外的任何线程都可以看到被发布对象的过期的值,
- 线程看到的被发布对象的引用是最新的,然而被发布对象的状态是过期的,如果一个对象是可变对象,
那么它要被安全发布才可以。
接下来讲安全的发布对象的四种方法
如何保证一个实例只被初始化一次,且线程安全呢? 单例代码
1. 要安全 = 不能随便new一个对象出来。构造方法私有, 构造单例对象,静态工厂方法获取单例对象(不安全)
第一种实现
package com.mmall.concurrency.singleton;
import com.mmall.concurrency.annotations.NotThreadSafe;
import lombok.extern.slf4j.Slf4j;
/**
* 懒汉模式
* 单例实例在第一次使用时候创建
*/
@Slf4j
@NotThreadSafe
public class Singleton1 {
// 私有构造函数
private Singleton1(){
}
// 单例对象
private static Singleton1 instance = null;
// 静态工厂类获取实例 -- 19行两个线程同时执行时候不安全
public static Singleton1 getInstance(){
if (instance == null){
instance = new Singleton1();
}
return instance;
}
}
public static synchronized Singleton1 getInstance(){
这样就是安全的,只是性能不好
第二种实现
package com.mmall.concurrency.singleton;
import com.mmall.concurrency.annotations.ThreadSafe;
import lombok.extern.slf4j.Slf4j;
/**
* 饿汉模式
* 单例实例在装载的时候进行创建
*/
@ThreadSafe
@Slf4j
public class Single2 {
//私有构造
private Single2(){
}
// 单例
private static Single2 instance = new Single2();
// 工厂类
public static Single2 getInstance(){
return instance;
}
}
不足
如果构造方法存在过多的处理,导致类加载特别慢,性能问题,只加载,不使用,资源浪费
因此饿汉模式考虑两个问题,
- 1.私构造函数没有太多处理
-
- 这个类肯定被使用
第三种实现
// CPU new 操作的步骤问题
// 1. memory = allocate() 分配对象的内存空间
// 2. ctorInstance() 初始化对象
// 3. instance = memory 设置instance指向刚分配的内存
// * JVM 和cpu 优化 指令重排
// 1. memory = allocate() 分配对象的内存空间
// 3. instance = memory 设置instance指向刚分配的内存
// 2. ctorInstance() 初始化对象
// 调用没有初始化的对象 --> 解决办法 --> 禁止指令重排
// 单例对象
/**
* 懒汉模式
* 单例实例在第一次使用时候创建
*/
/**synchronized,主要的性能消耗
*
*/
@Slf4j
@NotThreadSafe
@NotRecommend
public class Singleton4 {
// 私有构造函数
private Singleton4(){
}
private static Singleton4 instance = null;
// 静态工厂类获取实例 -- 19行两个线程同时执行时候不安全
public static Singleton4 getInstance(){
if (instance == null){
// 改变性能消耗 -- 双重检测机制 // B
synchronized(Singleton4.class){ // 同步锁
if (instance == null){
instance = new Singleton4(); // A
}
}
}
return instance;
}
}
饿汉第四种实现
/**
* 懒汉模式
* 单例实例在第一次使用时候创建
*/
/**synchronized,主要的性能消耗
*
*/
@Slf4j
@ThreadSafe
@NotRecommend
public class Singleton5 {
// 私有构造函数
private Singleton5(){
}
// 调用没有初始化的对象 --> 解决办法 --> 禁止指令重排 --> volatile
// volatile + double check 禁止指令重拍
private volatile static Singleton5 instance = null;
// 静态工厂类获取实例 -- 19行两个线程同时执行时候不安全
public static Singleton5 getInstance(){
if (instance == null){
// 改变性能消耗 -- 双重检测机制 // B
synchronized(Singleton5.class){ // 同步锁
if (instance == null){
instance = new Singleton5(); // A
}
}
}
return instance;
}
}
懒汉完了 – 将饿汉的优化办法
除了静态域还用静态块 – 写静态域和静态方法时候一定要注意顺序
package com.mmall.concurrency.singleton;
import com.mmall.concurrency.annotations.NotRecommend;
import com.mmall.concurrency.annotations.ThreadSafe;
import lombok.extern.slf4j.Slf4j;
/**
* 懒汉模式
* 单例实例在第一次使用时候创建
*/
@Slf4j
@ThreadSafe
@NotRecommend
public class Singleton6 {
// 私有构造函数
private Singleton6(){
}
// 单例对象
private static Singleton6 instance = null;
// 静态域
static {
instance = new Singleton6();
}
// 静态工厂类获取实例 -- 19行两个线程同时执行时候不安全
public static synchronized Singleton6 getInstance(){
return instance;
}
//main test
public static void main(String[] args ){
System.out.println(getInstance().hashCode());
System.out.println(getInstance().hashCode());
}
}
关于单例实例的初始化介绍了懒汉 , 饿汉模式,还有枚举模式
package com.mmall.concurrency.singleton;
import com.mmall.concurrency.annotations.Recommend;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.annotation.ThreadSafe;
/**
* m枚举模式最安全
*/
@Slf4j
@ThreadSafe
@Recommend
public class Singleton7 {
private Singleton7(){
}
public static Singleton7 getInstance(){
return Singleton.INSTANCE.getInstance();
}
private enum Singleton{
INSTANCE;
private Singleton7 singleton;
//jvm 保证绝对值调用一次
Singleton(){
singleton = new Singleton7();
}
public Singleton7 getInstance(){
return singleton;
}
}
}