【牛客网有书共读】《Java并发编程实战》第一章+第二章

作者:塑料假树
链接:https://www.nowcoder.com/discuss/85271?type=0&order=0&pos=8&page=0
来源:牛客网

第一章
操作系统需要多个程序同步执行,也就是需要并发编程,利用多线程,可以发挥多处理器的强大能力,降低建模的难度,将异步事件简化处理,也能响应更灵敏的用户界面。但是线程也会带来一定的风险,错误的多线程代码会破坏程序的安全性和活跃性,还有可能降低机器的性能。

第二章
编写线程安全的代码,核心在于对状态访问操作进行管理,特别是共享和可变的状态。如果多个线程访问同一个可变状态变量没有合适的同步就会出现错误,有三种方式修复该问题,1、不在线程之间共享该状态变量;2、将状态变量修改为不可变的变量;3、在访问状态变量时使用同步。在大型的程序中,状态的封装越好,就越容易实现程序的线程安全性,在设计线程安全的类时,良好的面向对象技术、不可修改性、明晰的不变性规范都能有一定的帮助作用。

2.1线程安全性
线程安全性的定义中,核心概念是正确性,即某各类的行为与其规范完全一致。当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些线程如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都表现出正确的行为,那么就称这个类时线程安全的。由于线程访问无状态对象的行为不会影响其他线程中操作的正确性,因此无状态对象一定是线程安全的。

2.2原子性
在并发编程中由于不恰当的执行时序出现不正确的结果,称之为竞态条件,最常见的竞态条件类型是“先检查后执行”,即通过一个可能失效的观测结果来决定下一步的动作。举例来说,

public class LazyInitRace{
    private ExpensiveObject instance = null;

    public ExpensiveObject getInstance(){
        if(instance==null){
            instance = new ExpensiveObject();   
        }   
        return instance;
    }
}

这段代码是延迟初始化中的竞态条件,有点类似单例模式,但是会破坏类的正确性,两个线程同时执行getInstance()方法,哪个线程创建实例则取决于不可预测的执行时序和线程调方式。要避免竞态条件问题,就必须在某个线程修改该变量时,通过某种方式防止其他线程使用这个变量。
假定有两个操作A和B,如果从执行A的线程来看,当另一个线程执行B时,要么将B完全执行完,要么完全不执行B,那么A和B对彼此来说就是原子的。原子操作是指,访问同一个状态的所有操作来说,这个操作是一个以原子方式执行的操作。为了确保线程安全性,“先检查后执行”和“读取-修改-写入”等操作必须是原子的。在实际情况中,应尽可能使用现有的线程安全对象来管理类的状态,与非线程安全的对象相比,判断线程安全对象的可能状态及其状态转换情况要更为容易。
2.3加锁机制
加锁机制是Java中用于确保原子性的内置机制,要保持状态的一致性,就需要在单个原子操作中更新所有相关的状态变量。
每个Java对象都可以用作一个实现同步的锁,称为内置锁或监视器锁,相当于一个互斥体,每次只能有一个线程执行内置锁保护的代码块,因此这个锁保护的代码块会以原子的方式执行,多个线程执行时不会相互干扰。具体实现是在代码中添加synchronized关键字,比如

synchronized(lock){...}

或者

public synchronized void service(ServletRequest request){...}

当某个线程请求一个由其他线程持有的锁是,发出的请求会阻塞,但内置的锁是可重入的,某个线程试图获得一个已经由它自己持有的锁那么请求就会成功。重入的一种实现方法是为每个锁关联一个获取计数值和一个所有者线程,当线程请求一个未被持有的锁时,JVM记下锁的持有者,并将计数器置为1,同一个线程再次获取这个锁计数值递增,线程退出同步代码块时计数器递减,计数值为0时,这个锁将释放。

2.4用锁来保护状态
对于可能被多个线程同时访问的可变状态变量,在访问时都需要持有同一个锁,这种情况下称状态变量是由这个锁保护的。每个共享和可变的变量都应该只有一个锁来保护,从而使维护人员知道是哪一个锁。一种常见的加锁约定是,将所有可变状态都封装在对象内部,并通过对象的内置锁对所有访问可变状态的代码路径进行同步,使得在该对象上不会发生并发访问。许多线程安全类都使用这种模式,比如Vector。
同步可以避免竞态条件问题,但滥用synchronized会出现过多的同步。此外,虽然synchronized方法可以确保单个操作的原子性,但如果要把多个操作合并为一个复合操作,还需要额外的加锁机制。

2.5活跃性和性能
设计同步代码块需要在安全性、简单性和性能上进行权衡,安全性必须满足,有时候简单性和性能之间会发生冲突,存在制约因素。实现同步策略时,一定不要盲目为了性能而牺牲简单性,可能会破坏安全性。
使用锁时应该清楚代码中实现的功能,以及在执行该代码是否需要长时间。执行计算密集的操作或者某个可能阻塞的操作,如果持有锁的时间过长,那么会带来活跃性或性能的问题。当执行时间较长的计算或者可能无法快速完成的操作,比如网络或控制台I/O时,一定不要持有锁。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值