前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >线程安全和锁机制(一)总述

线程安全和锁机制(一)总述

作者头像
提莫队长
发布2021-03-03 14:31:44
7580
发布2021-03-03 14:31:44
举报
文章被收录于专栏:刘晓杰

一、线程安全的定义

当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方法进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那么这个对象是线程安全的。

二、Java语言中的线程安全

(1)不可变

不可变(Immutable)的对象一定是线程安全的。

如果共享数据是一个基本数据类型,定义时使用final关键字修饰可保证它不可变。

如果共享数据是一个对象,那就需要保证对象的行为不会对其状态产生任何影响才行。其中最简单的是把对象中带有状态的变量都声明为final,这样在构造函数结束后,它就是不可变的。

Java API中符合不可变要求的类型:java.lang.String/java.lang.Number部分子类,枚举类。

(2)绝对线程安全

绝对安全的线程的类,完全符合线程安全的定义,但在Java API中标注自己是线程安全的类,大多数都不是绝对的线程安全,如Vector。

举例:

代码语言:javascript
复制
public class MainTest {
    
    private static Vector<Integer> vector = new Vector<Integer>();

    public static void main(String[] args) throws Exception {
        while (true) {
            for (int i = 0; i < 10; i++) {
                vector.add(i);
            }
            
            Thread removeThred = new Thread(new Runnable() {
                
                @Override
                public void run() {
                    for (int i = 0; i < vector.size(); i++) {
                        vector.remove(i);
                    }
                }
            });
            Thread printThred = new Thread(new Runnable() {
                
                @Override
                public void run() {
                    for (int i = 0; i < vector.size(); i++) {
                        System.out.println(vector.get(i));
                    }
                }
            });
            removeThred.start();
            printThred.start();
            
            while (Thread.activeCount() >= 20);
        }
    }
}

这段代码会报错。虽然 vector 是线程安全的,但是可能出现都进入了for循环的最后一个元素,但是前者刚刚 remove 后者就是 get 出错。解决方案是在 for 循环外面加 synchronized

(3)相对线程安全

是通常意义上的线程安全,它需要保证对这个对象单独的操作是线程安全的,在调用的时候不需要做额外的保证措施。

(4)线程兼容

线程兼容是指对象本身不是线程安全的,但是可以通过在调用端正确地使用同步手段来保证对象在并发环境中是可以安全使用的。Java API中的大部分的类都是属于线程兼容的,如ArrayList和HashMap等。

(5)线程对立

线程对立是指无论调用端是否采取了同步措施,都无法在多线程环境中使用的代码。线程对立这种排斥多线程的代码是很少出现的,通常都是有害的,应当避免。如Thread类的suspend()和resume()方法。如果两个线程同时持有一个线程对象,两个线程并发对该线程对象执行suspend()和resume()方法,无论是否采用了同步,都存在死锁风险。

三、线程安全的实现方法

(1)互斥同步

互斥同步是常见的一种并发正确性保障手段。同步是指在多个线程并发访问共享数据时,保证共享数据在同一个时刻只被一个(或者是一些,使用信号量的时候)线程使用。而互斥是实现同步的一种手段,临界区(Critical Section)、互斥量(Mutex)和信号量(Semaphore)都是主要的互斥实现方式。

  • synchronized关键字(最基本的互斥同步手段)
  • 重入锁ReentrantLock

互斥同步最主要的问题是进行线程阻塞和唤醒时带来的性能问题,这种同步也称为阻塞同步Blocking Synchronization。从处理问题的方式来说,互斥同步属于一种悲观的并发策略:总是认为只要不去做正确的同步措施(加锁),那就肯定会出问题。无论共享数据是否真的会出现竞争,它都进行加锁、用户态核心态转换、维护锁计数器、检查是否有被阻塞的线程需要唤醒等操作。

(2)非阻塞同步

非阻塞同步是一种基于冲突检测的乐观并发策略的同步操作:先进行操作,如果没有其他线程争用共享数据,那操作就成功;如果共享数据有争用,产生了冲突,就在采取其他的补偿措施(比如不断的重试,直到成功)。这种乐观并发策略的很多实现都不需要把线程挂起,因此称为非阻塞同步。

乐观并发策略需要硬件指令集的发展,因为上述过程中的操作和冲突检测这两个步骤需要具备原子性,而这种原子性保证如果使用互斥手段实现就失去意义,所以只能靠硬件通过一条处理器指令来完成这种从语义上看起来需要多次操作的行为。这里的非阻塞同步进行的操作主要涉及CAS(Compare And Swap)这条指令。使用该指令完成的操作具备原子性,称为CAS操作。

CAS指令执行时,需要3个操作数:内存位置(V)、旧的预期值(A)、新值(B)。当且仅当V符合旧预期值A时,处理器用新值B更新V的值,否则它就不执行更新。但是无论是否更新了V的值,都会返回V的旧值。

CAS的逻辑漏洞——ABA问题:如果一个变量V初次读取的时候是A值,并且在准备赋值的时候检查到它仍然为A值,此时并不能说它的值没有被其他线程修改过,有可能在这期间它的值先被改成了B,后又被改为了A,而CAS操作就会认为它从来没有改变过。大部分情况下ABA情况不会影响程序并发的正确性,如果需要解决ABA问题(JDK通过引入AtomicStampedReference来保证CAS的正确性),改用传统的互斥手段可能会比原子类更高效。

AtomicStampedReference多了一个 stamp 参数。字面上是时间戳,实际上它可以是任何一个整数。当AtomicStampedReference对应的数值被修改时,除了更新数据本身外,还必须要更新 stamp

(3)无同步方案

同步只是保证共享数据争用时的正确性的手段,要保证线程安全,并不是一定就要进行同步。

  • 可重入代码 可重入代码的特征:不依赖存储在堆上的数据和公用的系统资源、用到的状态量都由参数传入、不调用非可重入的方法等。 判断代码是否具有可重入性:对于一个方法,如果输入了相同的数据,就都能返回相同的结果,那它就满足可重入性的要求,也是线程安全的。
  • 线程本地存储 如果能保证共享数据的代码在同一个线程中执行,就把共享数据的可见范围限制在同一个线程内,就无须同步也能保证线程间不出现数据争用的问题。 通过java.lang.ThreadLocal类来实现线程本地存储的功能。每一个线程的Thread对象中都有一个ThreadLocalMap对象,这个对象存储了一组以ThreadLocal.threadLocalHashCode为键,以本地线程变量为值的K-V值对,ThreadLocal对象就是当前线程的ThreadLocalMap的访问入口,每一个ThreadLocal对象都包含了一个独一无二的threadLocalHashCode值,使用这个值就可以在线程K-V值对中找回对应的本地线程变量。

四、对应的介绍blog

线程安全和锁机制(二)谈谈volatile
线程安全和锁机制(三)synchronized和Lock
线程安全和锁机制(四)谈谈 ThreadLocal 和 Handler
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、线程安全的定义
  • 二、Java语言中的线程安全
  • 三、线程安全的实现方法
  • 四、对应的介绍blog
相关产品与服务
对象存储
对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档