并发编程技术「技术辩证分析」在并发编程模式下进行线程安全以及活跃性问题简析

Posted 洛神灬殇

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了并发编程技术「技术辩证分析」在并发编程模式下进行线程安全以及活跃性问题简析相关的知识,希望对你有一定的参考价值。

什么是线程安全?

线程安全,有两个重要的特征说明:“共享”和“可变”。

  • 共享是指可以被多个线程同时访问;

  • 可变是指变量的值在生命周期内是可以变化的;

如何实现线程安全

  • 一个对象是否需要线程安全的,取决于它是否被多个线程访问;

  • 如何保证一个对象的线程安全,则需要采用同步机制来协同对对象可变状态的访问;

  • 给线程安全下一个明确的定义:当多个线程访问这个对象或者资源时,如果这个对象或资源始终都能表现出数据的一致性的状态,那么就称这个对象或者资源是线程安全的;

数据资源的有无状态化

  • 无状态的对象一定是线程安全的。

  • 有状态的对象,多线程环境下,多个线程共享资源,且进行的不是原子性操作,这个时候就要考虑线程的安全控制问题

比如:count++,其实是不具备原子性的,因为这个步骤实际会被拆分为三个步骤,即 读取、修改和写入,而这三个步骤有可能在某个时刻因CPU时间片的切换问题,而只执行其中一两个步骤,这就不具备原子性。

原子化能力支持

在Java中,为了解决这个问题,java.util.concurrent.atomic包提供了很多的类,来保证数据操作的原子性,比如我们之前的程序可以修改为

  • 基本数据类型 AtomicInteger

  • 数组类型 AtomicIntegerArray

AtomicInteger integer = new AtomicInteger(0);
integer.incrementAndGet()

内部的原理是采用了CAS机制

那么什么是CAS机制?

CAS有人翻译为Compare And Set或Compare And Swap都是正确的。

多线程并发执行的状态下,锁的状态改变,基本都是使用CAS原理,它有一个比较别扭的叫法“CPU硬件同步原语”,算法是基于CPU硬件的,原子性操作,不会被其他线程打断。

CAS的算法,比较当前值和期望的值是否相等,如果相等,则将当前值赋予一个新值。

再比如修改一个Boolean的类型的变量的值,我们也可以采用

private AtomicBoolean atomicBoolean = new AtomicBoolean(false);
public void lock()
    //期望是false,如果是false,则可以修改为true
    atomicBoolean.compareAndSet(false, true);

同步锁机制支持

只要程序中存在“先判断,再更新”,那么就要保证这两个操作在一个原子操作里面,才能保证线程安全。

public synchronized int getCount()
    return count++;

Java锁机制的一些特点

监视锁、互斥锁、可重入锁都是在这个锁的特点。

  • 监视锁:java的每一个对象都可以用来做监视锁,也就是为什么我们的wait、notify方法定义在Object类的原因。

  • 互斥锁:表示最多只有一个线程可以持有这把锁。

  • 可重入锁:是指当线程A请求一个由线程B持有的锁时,线程B会进入阻塞状态;而如果线程A如果再访问另一段代码,而这个代码的锁是已经被线程A持有的,这个时候请求是可以成功的,这就叫可重入。

Java锁机制的简单原理

JVM为每个锁设置两个属性,获取计数值和所有者线程,当计数值为0时,这个锁就被认为是没有被任何线程持有,当线程请求一个未被持有的锁时,JVM将记录锁的持有者,并且计数值+1。

如果同一个线程再次获取这个锁,则计数值将递增,而当线程退出同步代码块时,计数器会相应递减,当计数值为0,这个锁将被释放。

活跃性问题

承接上面解决安全性的问题分析,锁机制会存在活跃性问题,比如:死锁,饥饿,活锁,这些都是属于活跃性问题。

死锁

多个线程,各自占对方的资源,都不愿意释放,从而造成死锁,A线程需要等待的锁被B线程占用,而B线程需要的等待的锁被A线程占用,所以相互都不释放,于是就陷入了死锁。

饥饿

多个线程访问同一个同步资源,有些线程总是没有机会得到互斥锁,这种就叫做饥饿。

出现饥饿的三种情况
  1. 高优先级的线程吞噬了低优先级的线程的CPU时间片

    • 理论上来说,线程优先级高的线程会比线程优先级低的线程获得更多的执行机会,但是java的线程优先级不是绝对出现这样的效果。

    • 一般而言:优先级高的出现频率会比优先级低的高很多

    • 不同的操作系统对线程的优先级支持是不同的,规定是在1-10之间,java通过3个常量来屏蔽这种操作系统的底层差异化。

  2. 线程被永久阻塞在等待进入同步代码块的状态

  3. 等待的线程永远不被唤醒

建议大家采用公平锁来代替synchronized这种互斥锁

活锁

两个人在走廊上碰见,大家都互相很有礼貌,互相礼让,A从左到右,B也从从左转向右,发现又挡住了地方,继续转换方向,但又碰到了,反反复复,一直没有机会运行下去。

以上是关于并发编程技术「技术辩证分析」在并发编程模式下进行线程安全以及活跃性问题简析的主要内容,如果未能解决你的问题,请参考以下文章

Java并发编程之CAS

Java技术指南「并发编程专题」Guava RateLimiter限流器入门到精通(源码分析)

java并发编程看啥书比较好

并发编程技术六之共享锁CountDownLatch源码分析

深度分析:Java并发编程之线程池技术,看完面试这个再也不慌了!

并发技术系列「多线程并发编程」技术体系和并发模型的基础探究(夯实基础)