十一、并发安全

并发安全

【1】什么是类的线程安全?

​ 当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些线程将如何交替执行,并且在调用代码中不需要任何额外的同步或者协同,这个类都能表现出正确的行为,那么就称这个类是线程安全的。

【2】线程不安全引发的问题

死锁
死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁。

​ 解决:保证加锁的有序性

动态顺序死锁

动态顺序死锁是在实现时按照某种顺序加锁了,但是因为外部调用的问题,导致无法保证加锁顺序而产生的。

解决方法:

1)通过内在排序,保证加锁的顺序性(使用identityHashCode获得原生的hashcode,按照这个的hashcode大小制定加锁顺序);

2)通过显示锁tryLock()进行尝试拿锁

活锁

尝试拿锁的机制中,发生多个线程之间互相谦让,不断发生拿锁,释放锁的过程。

解决办法:每个线程休眠随机数,错开拿锁的时间。

线程饥饿

【3】怎么才能做到类的线程安全?

栈封闭
所有的变量都是在方法内部声明和使用,这些变量都处于栈封闭状态。

无状态类

没有任何成员变量的类,就叫无状态的类

让类不可变

让类不可变有两种方法:

  • 加final关键字,对于一个类,所有的成员变量应该是私有的,同样的只要有可能,所有的成员变量应该加上final关键字,但是加上final,要注意如果成员变量又是一个对象的引用时,这个对象所对应的类也要是不可变,才能保证整个类是不可变
  • 不提供修改成员变量的方法,同时成员变量也不作为方法的返回值

volatile

保证类的可见性,最适合一个线程写,多个线程读的情景

对于写,可以使用Synchronized加锁等效为一个线程写

加锁和CAS

详情见往期博客

安全的发布

类中持有的成员变量,特别是对象的引用,如果这个成员对象不是线程安全的,通过get等方法发布出去,会造成这个成员对象本身持有的数据在多线程下不正确的修改,从而造成整个类线程不安全的问题。

解决方法:

  • 用线程安全的容器替换
  • 返回副本,深拷贝
  • 加锁

TheadLocal

详情见往期博客

【4】线程安全的单例模式

饿汉式

  • 在声明的时候就new这个类的实例,因为在JVM中,对类的加载和类初始化由虚拟机保证线程安全。
public class SingletonHungry {
    public static SingletonHungry singletonHungry = new SingletonHungry();
    private SingletonHungry(){}
}
  • 或者使用枚举。

懒汉式
在单例类的内部由一个私有静态内部类来持有这个单例类的实例。

public class SingletonLazy {
    private SingletonLazy(){}

    //定义一个私有类,来持有当前类的实例
    private static class InstanceHolder{
        public static SingletonLazy instance = new SingletonLazy();
    }

    public static SingletonLazy getInstance(){
        return InstanceHolder.instance;
    }
}
posted @ 2019-10-22 22:20  ねぇ  阅读(219)  评论(0编辑  收藏  举报