线程安全性

线程安全

要编写线程安全的代码,其核心在于要对状态访问操作进行管理,特别是对共享和可变状态的访问。

从非正式的意义来讲,对象的状态是指存储在状态变量(例如实例或静态域)中的数据,对象的状态可能包含其它依赖对象的域。

一个对象是否需要实现线程安全,取决于它是否会被多个线程访问。要使得对象是线程安全的,需要采取同步机制来协同对对象可变状态的访问。

Java同步机制:关键字synchronized、volatile类型的变量、显式锁(Lock)、原子变量。

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

原子性

竞态条件(Race Condition):计算的正确性取决于多个线程的交替执行时序时,就会发生竞态条件。例如“读取-修改-写入”操作和“先检查后执行”操作。

  • “读取-修改-写入”操作:最经典的就是自增操作。例如count++操作,该操作是非原子性的,实际上它包含三个操作:读取count的值,将值加一,将计算出的结果写入count。如果此时多个线程都访问count并++,那么不能保证最后结果正确。
  • “先检查后执行”操作:经典的例子就是单例模式

复合操作:要避免竞态条件问题就要保证在某个线程修改变量时,通过某种方式阻止其他线程使用该变量。“读取-修改-写入”操作和“先检查后执行”操作统称为复合操作:包含了一组必须以原子方式执行的操作以确保线程安全性。

加锁机制是Java中用于确保原子性的内置机制。

加锁机制

内置锁:

同步代码块(Synchronized Block)。内置锁可以支持原子性和可见性。同步代码块包含两部分:

  • 一个作为锁的对象引用;
  • 一个作为由这个锁保护的代码块;

其中该同步代码块的锁就是方法调用所在的对象。静态的synchronized方法以class对象作为锁。

Java的内置锁相当于一种互斥体(或互斥锁),这意味着最多只有一个线程能够持有这种锁。

重入:

当某个线程请求一个其他线程持有的锁时,就会阻塞。因为内置锁是可重入的,如果某个线程试图获得一个已经被自己占有的锁,就会成功。“重入”意味着获取锁的操作的粒度是“线程”,而不是“调用“。

重入的一种实现方法是为每一个锁设置一个计数器,同一个线程再次获取这个锁,计数值加一,而当线程退出同步代码块时,计数值减一。

如果内置锁是不可重入的,下面的代码将会死锁:

public class Widget{
    public synchronized void doSomeThing(){ ... }
}

public class LoggingWidget extends Widget{
    public synchronized void doSomeThing(){
        //如果是非重入的锁,获取Widget上的锁时就会发生死锁
        super.doSomeThing();
        ...
    }
}

锁的使用规则:

  • 对于可能被多个线程同时访问的可变状态变量,在访问它时都需要持有同一个锁。这种情况也可以成为该变量是由这个锁保护的。
  • 每个共享的和可变的状态变量都应该只由一个锁来保护,从而使维护人员知道是哪一个锁。
  • 对于每个包含多个变量的不变性条件,其中涉及的所有变量都需要由同一个锁来保护。

注意:

对象的内置锁和对象的状态之间没有内在的关联。当获取对象关联的锁时,并不能阻止其他线程访问该对象,只能阻止其他线程获取同一个锁。

可以使用@GuardBy标签标注使用的是哪一个锁。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 线程封闭和实例封闭

    SuperHeroes
  • Java--五态模型&控制线程

    SuperHeroes
  • Java--进程和线程基本概念

    SuperHeroes
  • 面试官:为什么Java线程没有Running状态?

    Java虚拟机层面所暴露给我们的状态,与操作系统底层的线程状态是两个不同层面的事。具体而言,这里说的 Java 线程状态均来自于 Thread 类下的 Stat...

    用户4143945
  • 为什么 Java 线程没有 Running 状态?

    Java虚拟机层面所暴露给我们的状态,与操作系统底层的线程状态是两个不同层面的事。具体而言,这里说的 Java 线程状态均来自于 Thread 类下的 Stat...

    Java技术栈
  • 面试官问:为什么 Java 线程没有 Running 状态?我懵了

    有人常觉得 Java 线程状态中还少了个 running 状态,这其实是把两个不同层面的状态混淆了。对 Java 线程状态而言,不存在所谓的running 状态...

    Java团长
  • Java线程为何没有Running状态?我猜你不知道。

    Java虚拟机层面所暴露给我们的状态,与操作系统底层的线程状态是两个不同层面的事。具体而言,这里说的 Java 线程状态均来自于 Thread 类下的 Stat...

    黄泽杰
  • 005.多线程-线程的生命周期

    新建状态: 当创建Thread类的一个实例(对象)时,此线程进入新建状态(未被启动)。 如:ThreadTest thread2 = new ThreadT...

    qubianzhong
  • 并发基础篇(一): 线程介绍

    好好学java
  • Java并发基础,不怕你看不懂!

    当我们使用计算机时,可以同时做许多事情,例如一边打游戏一边听音乐。这是因为操作系统支持并发任务,从而使得这些工作得以同时进行。

    Java3y

扫码关注云+社区

领取腾讯云代金券