java并发编程--04(安全发布对象与不可变对象和线程封闭)

目录

 1.发布对象

 2.不安全的发布对象

 3.四种安全发布对象的方法

4.不可变对象

5.线程封闭

6.线程不安全类的写法


 1.发布对象

   发布对象:使一个对象能够被当前范围之外的代码所使用

   对象溢出:一种错误的发布,当一个对象还没有构造完成就使他被其他线程所见。

 2.不安全的发布对象

   下面给出一个不安全发布对象的例子:


public class Test4 {
    private int[] ints = {1, 2, 3};

    public int[] getInts() {
        return ints;
    }

    public static void main(String[] args) {
        Test4 test4 = new Test4();
        System.out.println(Arrays.toString(test4.getInts()));
        
        test4.getInts()[1] = 555;
        System.out.println(Arrays.toString(test4.getInts()));
    }
}

 

 这里是通过public修饰的get方法导致了对象的溢出,或者说不安全,因为他对象的私有域可能会被篡改。导致其他线程拿到不正确值。

 再举一个例子:

public class Test5 {

    private int a = 1;

    public Test5() {
        new InnerClass();
    }

    class InnerClass {
        public InnerClass() {
            System.out.println(Test5.this.a);
        }
    }

    public static void main(String[] args) {
        new Test5();
    }
}

  这里是对象还没有构造完成就通过Test5.this给发布了出去,所以也是不安全的。

 3.四种安全发布对象的方法

  • 1.在静态初始化函数中初始化一个对象

     这个最常见的就是单例模式了,饱汉模式->饿汉模式->饿汉模式的静态方法加锁->双检查锁机制->volatile修饰

  下面说说每个的特点

   饱汉模式不是线程安全

public class SingletonExample1 {
    private SingletonExample1() {

    }
    private static SingletonExample1 instance = null;

    public static SingletonExample1 getInstance() {
        if (instance == null) {
            instance = new SingletonExample1();
        }
        return instance;
    }
}

 饿汉模式可能会浪费资源

public class SingletonExample1 {
    private SingletonExample1() {

    }
    private static SingletonExample1 instance = new SingletonExample1();

    public static SingletonExample1 getInstance() {
        return instance;
    }
}

  说道饿汉模式再多说一句,如果对象放在静态代码块中去初始化会出现什么情况呢?如果顺序不当会导致返回的结果为空,静态域的执行是讲顺序的。

  

  •  2.将对象应用保存到一个由锁保护的域中

饱汉模式的静态方法加锁会导致阻塞

public class SingletonExample1 {
    private SingletonExample1() {

    }

    private static SingletonExample1 instance = null;

    public static synchronized SingletonExample1 getInstance() {
        if (instance == null) {
            instance = new SingletonExample1();
        }
        return instance;
    }
}

 双检查锁机制,CPU指令重排序导致出现多个实例

public class SingletonExample1 {
    private SingletonExample1() {

    }

    private static SingletonExample1 instance = null;

    public static SingletonExample1 getInstance() {
        if (instance == null) {
            synchronized (SingletonExample1.class) {
                if (instance == null) {
                    instance = new SingletonExample1();
                }
            }
        }
        return instance;
    }
}
  • 3.将对象的引用保存到volatile类型域或者AtomicReference对象中

这里做一个说明:instance = new SingletonExample1();比如这行代码在会有三个动作,分配内存空间,初始化对象,instance 指向刚分配的对象。CPU指令重排后,二步和三步颠倒就会出现两个实例。

 那么防止CPU指令重排不就安全了吗?对的,使用volatile就可以防止指令重排

public class SingletonExample1 {
    private SingletonExample1() {

    }

    private static volatile SingletonExample1 instance = null;

    public static SingletonExample1 getInstance() {
        if (instance == null) {
            synchronized (SingletonExample1.class) {
                if (instance == null) {
                    instance = new SingletonExample1();
                }
            }
        }
        return instance;
    }
}

  ok,介绍完了饱汉饿汉的单例写法,那么还有没有其他的写法呢?使用枚举来做实例化其实是最安全的。使用JVM来保证只有一个对象,使用这种方式相比于懒汉饿汉模式都有优点。

public class SingletonExample1 {
    private SingletonExample1() {
    }
    public static SingletonExample1 getInstance() {
        return Singleton.INSTANCE.getInstance();
    }
    private enum Singleton {
        INSTANCE;
        private SingletonExample1 instance;
        //用JVM来保证方法绝对只被调用一次
        Singleton() {
            instance = new SingletonExample1();
        }

        public SingletonExample1 getInstance() {
            return instance;
        }
    }
}
  •  4.将对象的引用保存到某个正确构造对象的final类型域中

4.不可变对象

  有一种对象只要发布了就是安全的,那就是不可变对象,比如String类,

 final关键字,修饰类,修饰方法,变量(基本类型,引用类型,方法参数)

 java中除了final可以定义不可变的对象是否还有比其他的手段定义不可变对象?

final修饰的Map引用虽然不可以指向其他引用,但是值还可以修改,如果使用Collections.unmodifiableMap()那么他连值都不能修改。

还有一种方式就是使用谷歌Guava的Immutable,

 做成不可变对象的好处就是多线程环境下是线程安全的。

5.线程封闭

使用不可变对象史为了躲开并发,还有一种躲开并发的操作就是线程封闭。就是把一个对象封装到一个线程里面,只有这一个线程能访问。

 主要掌握ThreadLocal的用法

6.线程不安全类的写法

  StringBilder(不安全)  StringBuffer(安全)

  StringBuffer的线程安全的实现是通过synchronized加锁实现的,所以通常我们使用StringBilder,让她堆栈封闭就可以了。

  SimpleDateFormat(不安全)  DateTimeFormatter(安全)注意这不是time包下的,而是JodaTime包下的。

  集合(集合我们这里暂时不讲,主要在java基础里面去讲)

  一种常见的线程不安全的写法:先检查,再执行,这样的话是分成了两个操作,即使之前的操作安全后面可能回出现问题。所以这个a对象要仔细想想看,他是不是多线程共享的,如果是多线程共享的,一定要加一个锁。

 

发布了217 篇原创文章 · 获赞 70 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/weixin_37650458/article/details/103000705