#不安全发布对象
##发布对象
使一个对象能够被当前范围外的代码所使用
package com.mmall.concurrency.example.publish;
import lombok.extern.slf4j.Slf4j;
import javax.annotation.concurrent.NotThreadSafe;
import java.util.Arrays;
@Slf4j
@NotThreadSafe
public class UnsafePublish {
private String[] states={"a","b","c"};
public String[] getStates(){
return states;
}
private class OtherThread extends Thread{
private String[] states;
OtherThread(String[] states){
this.states=states;
}
@Override
public void run() {
states[0]="d";
}
}
public static void main(String [] args){
UnsafePublish unsafePublish=new UnsafePublish();
log.info("{}", Arrays.toString(unsafePublish.getStates()));
OtherThread otherThread=unsafePublish.new OtherThread(unsafePublish.getStates());
otherThread.run();
log.info("{}", Arrays.toString(unsafePublish.getStates()));
}
}
由于对象中成员变量的值会被其他线程修改,所以当一个线程修改变量后,变量的值也很可能变成其他线程修改的值。
对象溢出
指的是一种错误的发布,当一个对象还没有构造完成时,就使它被其他线程所见。
public class Escape {
private int thisCanBeEscape=0;
public Escape(){
new InnerClass();
}
private class InnerClass {
public InnerClass(){
log.info("{}",Escape.this.thisCanBeEscape);
}
}
public static void main(String [] args){
new Escape();
}
}
安全发布对象
1.在静态初始化函数中初始化一个对象索引
2.将对象的引用保存到volatile类型域或者AtomicReference对象中
3.将对象的引用保存到某个正确构造对象的final类型域中
4。将对象的引用保存到一个由锁保护的域中
一个线程不安全的单例懒汉式发布对象
package com.mmall.concurrency.example.singleton;
import com.mmall.concurrency.annoations.NotRecommend;
import javax.annotation.concurrent.NotThreadSafe;
/*
* 懒汉式->双层同步锁单例模式
* */
@NotThreadSafe
public class SingletonExample4 {
//私有构造函数(因为只有私有化了才能避免外界通过new的方式不断创建新的单例对象)
private SingletonExample4(){
}
//单例对象
private static SingletonExample4 instance=null;
//静态工厂方法来获取单
public static synchronized SingletonExample4 getInstance(){
if(instance==null){
synchronized(SingletonExample4.class){
if(instance==null){//双层检测机制
instance=new SingletonExample4();
}
}
}
return instance;
}
}
尽管有双层检测机制,但是线程仍是不安全的。
当我们创建一个对象时,jvm会进行指令重排。如果按着正常步骤创建对象是这样的:
1.memory=allocate()//分配内存空间
2.ctorInstance()//初始化对象
3.instance=memory//将引用指向分配的空间地址
由于jvm的指令重排,上面的顺序有可能是这样的
1.memory=allocate()//分配内存空间
3.instance=memory//将引用指向分配的空间地址
2.ctorInstance()//初始化对象
在这种情况下,假如有两个线程A、B,如果线程A执行到了
instance=new SingletonExample4();
并且jvm执行这段代码的顺序是按着1 3 2来的,那么当A在执行完1 3后2还没来得及执行初始化对象,线程B执行
public static synchronized SingletonExample4 getInstance(){
if(instance==null){
synchronized(SingletonExample4.class){
if(instance==null){//双层检测机制
instance=new SingletonExample4();
}
}
}
return instance;
}
}
就会判断instance!=null从而直接获取到未被初始化的instance。
为了避免这种现象,我们使用volatile关键字修饰就可以避免这种情况。
package com.mmall.concurrency.example.singleton;
import com.mmall.concurrency.annoations.ThreadSafe;
import javax.annotation.concurrent.NotThreadSafe;
/*
* 懒汉式->双层同步锁单例模式
* */
@ThreadSafe
public class SingletonExample5 {
//私有构造函数(因为只有私有化了才能避免外界通过new的方式不断创建新的单例对象)
private SingletonExample5(){
}
//单例对象
private static volatile SingletonExample5 instance=null;
//静态工厂方法来获取单
public static synchronized SingletonExample5 getInstance(){
if(instance==null){
synchronized(SingletonExample5.class){
if(instance==null){
instance=new SingletonExample5();
}
}
}
return instance;
}
}
使用枚举类来实现线程安全的单例模式
package com.mmall.concurrency.example.singleton;
import com.mmall.concurrency.annoations.Recommend;
import com.mmall.concurrency.annoations.ThreadSafe;
/*
* 枚举模式:线程最安全的
* */
@ThreadSafe
@Recommend
public class SingletonExample7 {
//私有构造函数(因为只有私有化了才能避免外界通过new的方式不断创建新的单例对象)
private SingletonExample7(){
}
//静态工厂方法来获取单
public static SingletonExample7 getInstance(){
return Singleton.INSTANCE.getInstance();
}
private enum Singleton{
INSTANCE;
private SingletonExample7 singleton;
//jvm保证这个方法绝对只调用一次
Singleton(){
singleton=new SingletonExample7();
}
public SingletonExample7 getInstance(){
return singleton;
}
}
}