Java线程安全

createh51周前 (04-11)技术教程11

当多个线程处理相同的数据,数据值发生变化时,会得到不一致的结果,这种情况不是线程安全的。 当一个线程已经在一个对象上工作并阻止另一个线程在同一个对象上工作时,这个过程称为线程安全。

线程安全体现

  1. 原子性提供互斥访问,同一时刻只能有一个线程对数据进行操作(atomic,synchronized,lock);
  2. 可见性一个线程对主内存的修改可以及时地被其他线程看到(volatile,synchronized);
  3. 有序性: 一个线程观察其他线程中的指令执行顺序,由于指令重排序,该观察结果一般杂乱无序(happens-before原则)

原子性

  • Atomic

它们是通过CAS完成原子性。

CAS定义

CAS(compare and swap),比较并交换。可以解决多线程并行情况下使用锁造成性能损耗的一种机制.CAS 操作包含三个操作数—内存位置(V)、预期原值(A)和新值(B)。如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。否则,处理器不做任何操作。一个线程从主内存中得到num值,并对num进行操作,写入值的时候,线程会把第一次取到的num值和主内存中num值进行比较,如果相等,就会将改变后的num写入主内存,如果不相等,则一直循环对比,知道成功为止。

CAS使用的时机

  1. 线程数较少、等待时间短可以采用自旋锁进行CAS尝试拿锁,较于synchronized高效。
  2. 线程数较大、等待时间长,不建议使用自旋锁,占用CPU较高
  • Synchronized

synchronized是一种同步锁,通过锁实现原子操作。

JDK提供锁分两种:一种是synchronized,依赖JVM实现锁,因此在这个关键字作用对象的作用范围内是同一时刻只能有一个线程进行操作;另一种是LOCK,是JDK提供的代码层面的锁,依赖CPU指令,代表性的是ReentrantLock。

synchronized修饰的对象有四种:

  1. 修饰代码块,作用于调用的对象;
  2. 修饰方法,作用于调用的对象;
  3. 修饰静态方法,作用于所有对象;
  4. 修饰类,作用于所有对象。
  • Lock

Lock和syncronized的区别:

  1. synchronized是Java语言的关键字, Lock是一个类。
  2. synchronized不需要用户去手动释放锁,发生异常或者线程结束时自动释放锁;Lock则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象。
  3. lock可以配置公平策略,实现线程按照先后顺序获取锁。
  4. 提供了trylock方法 可以试图获取锁,获取到或获取不到时,返回不同的返回值 让程序可以灵活处理。
  5. lock()和unlock()可以在不同的方法中执行,可以实现同一个线程在上一个方法中lock()在后续的其他方法中unlock(),比syncronized灵活的多。

公平锁与非公平锁

公平锁:就是很公平,在并发环境中,每个线程在获取锁时会先查看此锁维护的等待队列,如果为空,或者当前线程线程是等待队列的第一个,就占有锁,否则就会加入到等待队列中,以后会按照FIFO的规则从队列中取到自己。

非公平锁:比较粗鲁,上来就直接尝试占有锁,如果尝试失败,就再采用类似公平锁那种方式。

  • ReentrantLock--重入锁

ReentrantLock是Lock接口的实现类,是非公平锁。

可见性

volatile

这种方式可以保证每次取数直接从主存取,它只能保证内存的可见性,无法保证原子性。它不需要加锁,比 synchronized 更轻量级,不会阻塞线程,不会被编译器优化。然而要求对这个变量做原子操作,否则还是会有问题。虽然 volatile 是轻量级,但是它也需要保证 读写的顺序不乱序,所以可以有优化点,比如在单例实现方式中的双重校验中,使用 临时变量 降低 volatile 变量的访问。

synchronized

Synchronized 能够实现原子性和可见性;在 Java 内存模型中,synchronized规 定,线程在加锁时,先清空工作内存 → 在主内存中拷贝最新变量的副本到工作内存 → 执行完代码 → 将更改后的共享变量的值刷新到主内存中 → 释放互斥锁。

所以如果无法用 volatile 做可见性,则可以考虑用 synchronized 可以做可见性的保证。

有序性

在程序执行时,为了提高性能,编译器和处理器常常会对指令做重排序。重排序分3中:

  • 编译器优化的重排序
  • 指令级并行的重排序
  • 内存系统的重排序

java程序的有序性可以归结为一句话:在单线程内,所有的操作都是有序的;如果在一个线程中看另一个线程,所有的操作都是无序的。前半句是指as-if-serial,意思是:不管编译器和处理器怎么重排序,单线程的执行结果不会改变。后半句是指指令重排序现象和主内存和工作内存有延迟的现象。

Java中保证有序性方法:

  1. 使用volatile关键字修饰变量flag就可以解决重排序问题,禁止重排序的规则如下:
    第一:当第二个操作是volatile变量写时,不论第一个操作是什么,都不允许重排序
    第二:当的第一个操作是volatile读时,不论第二个操作是什么,都不能重排序
    第三:当第一个操作是volatile写,而二个操作是volatile读时,不能重排序。
  2. Synchronized以及锁操作,保证只有一个线程访问。

相关文章

轻松掌握Java多线程 - 第四章:线程安全问题

学习目标1. 什么是线程安全1.1 线程安全的定义1.2 线程安全的重要性2. 共享资源访问的竞态条件2.1 什么是竞态条件2.2 竞态条件示例2.3 竞态条件的类型3. 线程安全问题的表现形式3.1...

如何在Java中实现线程安全?总结如下

在Java中,线程安全是指在多线程环境下,多个线程可以安全地访问共享资源或数据,而不会出现不一致或意外的结果。以下是一些实现线程安全的常用方法:1、使用synchronized关键字: 通过在方法或代...

Java多线程与锁机制详解:打造高效安全的并发世界

Java多线程与锁机制详解:打造高效安全的并发世界在当今这个数据处理量爆炸的时代,单线程程序已经难以满足高性能需求。Java作为一门优秀的编程语言,提供了强大的多线程支持,而锁机制正是保证多线程安全的...

Java集合框架的线程安全性:多线程编程的守护者

Java集合框架的线程安全性:多线程编程的守护者在Java的世界里,集合框架是所有开发者都绕不开的重要组成部分。无论是处理数据的存储还是操作,集合类几乎无处不在。然而,当我们把目光投向多线程编程的时候...

Java 线程安全思路

线程安全1、先来了解一下:为什么多线程并发是不安全的?****在操作系统中,线程是不拥有资源的,进程是拥有资源的。而线程是由进程创建的,一个进程可以创建多个线程,这些线程共享着进程中的资源。所以,当线...