知识点:Java 并发编程 - 理解原子性

知识点:Java 并发编程 - 理解原子性_第1张图片

Java 并发编程 - 原子性

    • 原子性定义
    • Java 8种原子操作
    • synchronized
    • JDk操作的原子类

线程安全是多线程编程中需要重要关注的领域,在并发编程时会使用 机制来解决多线程之间同一共享变量操作的问题,多线程操作同一共享变量不加锁时会让变量的状态不可控,这样的情况下线程的操作是不安全的。

而当多线程访问同一个类时,如果不用考虑这些线程在运行时环境下调度和交叉执行,并且不需要额外的同步与调用方法上的协调,这个类的行为状态仍然正确,那称这个类是线程安全的。

Servlet就是线程安全的,请求之间相互隔离互不影响,在多线程运行时不需要做额外的同步与协调,任务与任务之间也没有交叉。Servlet保持任务是无状态的,所以无状态对象永远是线程安全的

但并不能保证所有的任务都是无状态,在生活中无状态的事务举不胜举,文章阅读量 交通信号 电源开关 这些事务在遇到多线程时都需要使用状态去表示,如果使用无状态来表示将会导致事务不可控甚至混乱。

原子性定义

原子性是指一个操作不可被中断,要么全部执行成功要么全部执行失败;关系性数据库事务的一大重要特性及为原子性

下面我们来讲讲并发编程中如何保证操作的原子性以及Java有那些原生的方法能保证原子性的操作。

Java 8种原子操作

  • lock :它把一个变量标识为一个线程独占的状态
  • unlock:它释放一个处理线程独占状态的变量,使释放后的变量能被其它线程锁定
  • read:它把一个变量的值从主内存中传输到线程的工作内存中,以便后面的load使用
  • load:将read命令传输的变量值放入工作内存中的变量副本
  • use:它把工作内存中一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用到变量的值的字节码指令时将会执行这个操作
  • assign:它把一个从执行引擎接收到的值赋给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作
  • store:它把工作内存中一个变量的值传送给主内存中以便随后的write操作使用
  • write:它把store操作从工作内存中得到的变量的值放入主内存的变量中

上面的这些指令操作是底层的,可以作为扩展知识面掌握下。那么如何理解这些指令了?比如,把一个变量从主内存中复制到工作内存中就需要执行read,load操作,将工作内存同步到主内存中就需要执行store,write操作。注意的是:java内存模型只是要求上述两个操作是顺序执行的并不是连续执行的。也就是说read和load之间可以插入其他指令,store和writer可以插入其他指令。比如对主内存中的a,b进行访问就可以出现这样的操作顺序:read a,read b, load b,load a

由原子性变量操作read,load,use,assign,store,write,可以大致认为基本数据类型的访问读写具备原子性(例外就是long和double的非原子性协定.

int a=1; 
a++;

上面示例中第一个行的赋值操作是原子性的,第二行的a++操作不是原子性的操作,会解释成a=a+1

synchronized

上面例子中的a++不是原子操作,在并发编程时就会导致状态不一致。

AB线程同时对变量aa++操作

A实际执行的步骤为:

  1. 获取a变量的值
  2. 执行a+1表达式的值计算
  3. a+1表达式计算的值赋值给a

B执行的步骤与A是一样的,设AB同时开始执行a+1的操作,同时执行了第一步获取变量a的值,同时执行a+1表达式的计算,同时将表达式的值赋值给变量a最后的结果a变量的值只加了1;但预期值应该是加两次1。

针对上面的问题可以使用下面的示例代码来保证多线程执行多个表达式的原子操作:

int a=0;
synchronized{
     
  a++;
  System.out.println(a)
}

synchronized块代码实际是在进行代码块之前获取一个锁操作,在获取锁成功后再执行代码块中的表达式,执行完成后自动释放锁,通过线程锁机制来保证代码块中的表达式操作的原子性。

JDk操作的原子类

在Jdk包java.util.concurrent.atomic下提供了基本类型与引用类型操作的原子类型以Atomic开头,操作Integer类型可以使用ActomicInteger,操作Long类型可以使用ActomicLong, 操作引用类型可以使用AtomicReference;使用原子类来执行a++的表达式时可以这样:

AciomicInteger ai=new AciomicInteger(1);
System.out.println(ai.incrementAndGet());

在方法incrementAndGet会对ai变量的值加1后结果返回值,在内部保证了操作的原子性

原子类相比于synchronized关键字做a++这样的语义表达时更加简洁,但对于复杂的表达式ActomicInteger无法表达,在原子类与关键字synchronized的选择上需要看实际的情况,只是对值类型做简单的操作可以使用原子类,需要做复杂的数学计算时选择synchronized将会更加方便。

精彩推荐:

包邮赠书!《阿里巴巴Java开发手册 第二版》
知识点:Java sychronized 内部锁实现原理
知识点: Java FutureTask 使用详解
Java ClassLoader详解双亲委派的实现原理

你可能感兴趣的:(JAVA,多线程,java,并发编程,原子性)