资讯专栏INFORMATION COLUMN

第二章 线程安全性

fsmStudy / 992人阅读

摘要:第二章线程安全性线程安全性的理解定义某个类的行为与其规范完全一致原子性竞态条件理解当操作的正确的结果取决于多个线程的交替执行时序,就会发生竞态条件。

第二章 线程安全性 2.1 线程安全性的理解

定义:某个类的行为与其规范完全一致

2.2 原子性 2.2.1 竞态条件

理解:当操作的正确的结果取决于多个线程的交替执行时序,就会发生竞态条件。常见的竞态条件类型是
”先检查后执行“,首先观察到某个条件为真再去采取下一步的动作(然而在这两个操作之间其实观察结果可能失效)

一个很常见的例子就是延迟初始化。当一个线程根据观察结果为空进行初始化时,一个线程可能已经建立了初始化。
@NotThreadSafe
public class LazyInitRace {
    private SampleClass sample;

    public SampleClass getInstance() {
        if (instance == null) {
            instance = new SampleClass();
        }
        return instance;
    }
}
2.2.3 复合操作

原子操作:对于访问同一个状态的所有操作(包括该操作本身)来说,这个操作是以一个原子方式来执行的操作,也即不可分割的操作。如这些操作:先检查后执行,读取-修改-写入等复合操作,包含了一组必须以原子方式执行的操作以确保其线程安全性。

2.3 加锁机制

线程安全性的定义中要求,多个线程之间无论采取何种执行时序或交替方式,都要保证不变性条件不被破坏。当在不变性条件中涉及多个策略时,各个变量之间不是彼此独立的话,某个变量的值就会对其他变量的值产生约束。要保持状态的一致性,就需要在单个原子操作中更新所有相关的状态变量。

2.3.1 内置锁

Java提供的一种内置锁机制:同步代码块。同步代码块包括两个部分:有个作为锁的对象引用,一个作为由这个锁保护的代码块,常见的有synchronized关键字:

synchronized (lock) {
    // TODO
}

附:
每个Java对象都可以用作一个实现同步的锁,这些锁被称为内置锁或监视器锁。

2.3.2 重入

当某个线程请求一个由其他线程持有的锁时,发出请求的线程就会被阻塞。而如果这个线程试图获取已经由它自己持有的锁,则这个请求就会成功。否则,就会导致死锁的发生。简单示例如下:

public class Parent {
    public synchronized void parentMethod { ... }
}

public class Child extends Parent {
    public synchronized void childMethod { ... }
}
2.4 用锁来保护状态

首先了解一个概念,共享状态:这里指各个线程共享的有状态对象等。通过来构造一些协议,以实现对共享状态的独占访问,只要始终遵循这些协议,就能确保状态的一致性
当复合操作访问共享状态时,如读取-修改-写入,必须是原子操作以避免产生竞态条件。
还需要理解的几个点:1、仅仅将复合操作封装到一个同步代码块中是不够的。如果用同步来协调对某个变量的访问,那么在访问这个变量的所有位置上都需要同步。2、当使用锁来协调对某个变量的访问时,在访问变量的所有位置上都要使用同一个锁。3、对于可能被多个线程同时访问的可变状态变量,在访问它时都需要持有同一个锁,在这种情况下,我们称状态变量是由这个锁来保护的。4、每个共享的和可变的变量都应该只有一个锁来保护,从而使维护人员知道是哪一个锁。
一种加锁的约定是:使所有的可变状态都封装在对象内部,并通过对象的内置锁对所有访问可变状态的代码路径·进行同步,使得该对象上不会发生同步,如Vector类等。
当类的不变性涉及到多个状态变量时,那么还有另外一个需求:在不变性条件中的每个变量都必须由同一个锁来保护。

2.5 活跃性与性能

不良并发程序:可同时调用的数量,不仅受到可用处理资源的限制,还受到应用程序本身的限制。一种改良做法是(书中给出的),要确保同步代码块不要过小,且不要将本应是原子的操作拆分到多个同步代码中。应尽量将不影响共享状态且执行时间较长的操作从同步代码中分离出去,从而在这些操作的执行过程中,其他线程可以访问共享状态。

public class CachedFactorizer implements Servlet {
    private BigInteger lastNumber;
    private BigInteger[] lastFactors;
    private long hits;
    private long cacheHits;

    public synchronized long getHits() {
        return hits;
    }

    public synchronized double getCachedHits() {
        return (double)cacheHits / (double)hits;
    }

    public void service(ServletRequest req, ServletReponse resp) {
        BigInteger i = extractFromRequest(req);
        BigInteger[] factors = null;

        synchronized(this) {
            ++hits;
            if (i.equals(lastNumber)) {
                ++cacheHits;
                factors = lastFactors.clone();
            }
        }

        if (factors == null) {
            factors = getFactor(i);
            synchronized(this) {
                lastNumber = i;
                lastFactors = factors.clone();
            }
        }

        encodeIntoResponse(resp, factors);
    }
}

要判断同步代码的和李大霄,需要在各个设计需求之间进行权衡,包括安全性、简单性和性能。如果持有锁的时间过长,那么会带来活跃性或性能问题:当执行时间较长的计算或者可能无法快速完成的操作时(例如,网络I/O或控制态I/O,一定不要持有锁。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/68321.html

相关文章

  • 【Java并发编程的艺术】二章读书笔记之synchronized关键字

    摘要:在之前的文章中学习了关键字,可以保证变量在线程间的可见性,但他不能真正的保证线程安全。线程执行到指令时,将会尝试获取对象所对应的的所有权,即尝试获得对象的锁。从可见性上来说,线程通过持有锁的方式获取变量的最新值。 在之前的文章中学习了volatile关键字,volatile可以保证变量在线程间的可见性,但他不能真正的保证线程安全。 /** * @author cenkailun *...

    GT 评论0 收藏0
  • 单例模式五种实现

    摘要:前言文章介绍了单例模式五种实现的方式,分别是懒汉,饿汉,静态内部类,双重检验锁以及枚举实现方式,并主要关心加载时机以及线程安全。 前言 文章介绍了单例模式五种实现的方式,分别是懒汉,饿汉,静态内部类,双重检验锁以及枚举实现方式,并主要关心加载时机以及线程安全。首先,通俗点讲,饿汉就是这个类还没被使用到的时候,实例已经创建好了;而懒汉是使用到的时候才创建对应的实例。线程安全方面主要考虑实...

    Jrain 评论0 收藏0
  • 实战java高并发程序设计二章

    摘要:线程的基本状态线程的基本操作与内存模型线程组守护线程线程优先级线程安全与隐蔽错误线程的基本状态线程的生命周期线程的基本操作新建线程终止线程立即终止线程所有活动方法在结束线程时会直接终止线程并立即释放这个线程所持有的锁可能引起数据不一致强烈建 1.线程的基本状态 2.线程的基本操作 3. volatile与java内存模型 4.线程组 5.守护线程(Daemon) ...

    Imfan 评论0 收藏0
  • ArrayList源码和多线程安全问题分析

    摘要:源码和多线程安全问题分析在分析线程安全问题之前,我们线对此类的源码进行分析,找出可能出现线程安全问题的地方,然后代码进行验证和分析。即当多线程调用方法的时候会出现元素覆盖的问题。 1.ArrayList源码和多线程安全问题分析 在分析ArrayList线程安全问题之前,我们线对此类的源码进行分析,找出可能出现线程安全问题的地方,然后代码进行验证和分析。 1.1 数据结构 ArrayLi...

    genedna 评论0 收藏0
  • Java设计模式-单例模式(Singleton Pattern)

    摘要:如果需要防范这种攻击,请修改构造函数,使其在被要求创建第二个实例时抛出异常。单例模式与单一职责原则有冲突。源码地址参考文献设计模式之禅 定义 单例模式是一个比较简单的模式,其定义如下: 保证一个类仅有一个实例,并提供一个访问它的全局访问点。 或者 Ensure a class has only one instance, and provide a global point of ac...

    k00baa 评论0 收藏0

发表评论

0条评论

fsmStudy

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<