并发安全

怎么才能做到线程安全

  1. 无状态的类
    没有任何成员变量的类,就叫无状态的类,这种类一定是线程安全的。因为当多个线程使用同一个对象时,这个对象的成员变量被多个线程共享,而局部变量会在每个线程的本地内存拷贝一份,不被其他线程共享。
  2. 让类不可变
    有两种方式让类不可变:一是给基本类型加入final关键字(如果是引用类型,对象内部数据可变),二是不给成员变量提供修改的方法。
  3. volatile
    仅能保证在一写多读的场景中是线程安全的。
  4. 加锁和CAS
    内部使用synchronization或显示锁(CAS机制)修改数据达到线程安全,但是需要预防死锁。
  5. 安全的发布
    成员变量使用线程安全的类,如CurrentHashMap。
  6. ThreadLocal
    没有任何成员变量的类,就叫无状态的类,这种类一定是线程安全的。

Servlet
不是线程安全的类,为什么平时感知不到线程不安全:
1、在需求上,很少有共享的需求。
2、接收到了请求,返回应答的时候,一般都是由一个线程来负责的。
但是只要Servlet中有成员变量,一旦有多线程下的写,就很容易产生线程安全问题。

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

产生死锁的原因
死锁的发生必须具备以下四个必要条件:

  1. 互斥条件:资源同一时间只能被一个进程占用。
  2. 持有和请求条件:一个进程在持有至少一个资源的同时请求另外一个资源。
  3. 不剥夺条件:其他进程如果请求已被占有的资源,只能等待该资源被释放。
  4. 环路等待条件:即A线程持有线程A1,同时等待B1,B线程持有线程B1,同时等待A1。

其他
避免死锁常见的算法有有序资源分配法、银行家算法。
mysql数据库在同时操作多个表时,可能产生死锁,只能通过外力kill掉其中的死锁线程才能解决这个问题。

解决死锁
死锁的产生可能是偶然情况下触发的,重现难度大,也没有报错日志。

  1. 定位死锁
    JDK提供jps查询到JVM上的应用和对应的id,再通过jstack id查看应用的死锁持有情况。IDEA有一个工具”get Thread dump“可以查看当前应用的死锁。
  2. 解决方法
    a)保证加锁顺序一致。
    b)使用活锁。使用ReentrantLock的tryLock()方法尝试拿锁,如果拿锁失败,释放已经获得的锁,并休眠一点时间,减少再次发生资源交叉竞争的概率。

怎么减少锁的竞争
3. 尽量少的使用锁
4. 减少锁粒度
5. 使用类似CurrentHashMap的段锁机制

线程安全的单例模式
下面程序需要给SingleDcl单例对象用volatile修饰,否则可能会发生对象逃逸现象。即线程A给singleDcl对象分配了内存后,还没来得及给singleDcl对象的成员变量初始化,另外一个线程B就获得该单例对象,最终导致发生程序错误。
在这里插入图片描述
除了上述的懒汉式单例模式外,还有其他线程安全的单例模式:
1) 饿汉式
2)使用枚举

多线程带来的性能问题
引入多线程的目的是为了发挥cpu多核心的优势,使得程序性能更好。但是,如果使用不当,反而会降低系统性能。因为引入多线程会涉及到上下文的切换,内存同步、阻塞问题。

1) 上下文切换:线程切换涉及到上下文的切换,需要消耗50-10000个时钟周期,可能是操作系统消耗最大的一个动作了。
2) 内存同步:多线程环境往往会涉及到volatile操作,volatile会给程序加入内存屏障,抑制编译器的优化。还可能会导致其他线程的缓存失效。这都增加了时间开销。
3) 阻塞:多线程对锁的竞争会导致竞争失败的线程挂起。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值