栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

【笔记】【Java并发编程实战】2线程安全

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

【笔记】【Java并发编程实战】2线程安全

注:本文为笔者阅读《JAVA并发编程实战》(Brian Goetz等注)一书的学习笔记,如有错漏,敬请指出。

文章目录
  • 重要概念摘录:
    • 概述
    • 原子性
  • 一些问题的解决方案
    • 在没有正确同步的情况下,若多个线程访问同一个变量,如何修复程序隐患?
  • 一些示例代码:
    • 线程安全示例:无状态对象
    • 充分考虑活跃度和性能

重要概念摘录: 概述
  • 线程安全的界定:当多个线程访问一个类时,如果不用考虑这些线程在运行时环境下的调度和交替执行,并且不需要额外的同步、在调用代码方无须作其他协调,这个类的行为依然是正确的,称这个类是线程安全的。(我的理解:找不出并行执行与串行执行结果相异的情况)

  • 构建并发程序要正确使用线程和锁。编写线程安全的代码,本质上是管理对状态的访问,而且通常是共享、可变的状态。

  • 一般而言,一个对象的状态就是它的数据(存储在状态变量中),还包括其他附属对象的域,包含任何会对它外部可见行为产生影响的数据。

  • 线程安全的性质取决于程序如何使用对象,而不是对象完成了什么。

原子性
  • 自增操作(++count)并不是原子操作,它实际上是三个离散操作的简写形式:获取当前值,加1,返回新值 (read-modify-write)。若两个线程缺乏同步,会引发问题。
  • 计数上的轻微错误在基于Web的服务中是可接受的,但若计数器用于生成序列或对象唯一的标识符,多重调用返回相同的结果会导致严重的数据完整性问题。
  • 在一些偶发时段里,出现错误结果的可能性对于并发程序而言非常重要,这称为竞争条件。
一些问题的解决方案 在没有正确同步的情况下,若多个线程访问同一个变量,如何修复程序隐患?
  • 不要跨线程共享变量
  • 使状态变量为不可变的(我的理解:final)
  • 在任何访问状态变量的时候使用同步
    (2和3选一个)

注意:

  • 一开始就将一个类设计成是线程安全的,比在后期重新修复它更容易。
  • 访问特定变量的代码越少,越容易确保使用恰当的同步,越容易推断出访问一个变量所需的条件。
  • 对程序的状态封装得越好,程序越容易实现线程安全,也有助于维护者保持这种线程安全性。
一些示例代码: 线程安全示例:无状态对象

以下是利用Servlet进行简单的因数分解操作的程序。

public class StatelessFactorizer implements Servlet {
    public void service(ServletRequest req, ServletResponse resp){
        BigInteger i=extractFromRequest(req);
        BigInteger[] factors=factor(i);
        encodeIntResponse(resp,factors);
    }
}

(注:这段代码似乎要自己编写extractFromRequest()和encodeIntResponse()方法)

  • StatelessFactorizer像大多数Servlet一样是无状态的;它不包含域也没有引用其他类的域。一次特定计算的瞬时状态,会唯一地存在本地变量中,这些本地变量存储在栈中,只有执行线程才会被访问。
  • 一个访问StatelessFactorizer的线程不会影响访问同一个Servlet的其他线程的计算结果。因为两个线程不共享状态,它们如同在访问不同的实例。
充分考虑活跃度和性能

缓存最新请求和结果的servlet

import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import net.jcip.annotations.GuardedBy;

import java.math.BigInteger;

public class CachedFactorizer {
    @GuardedBy("this") private BigInteger lastNumber;
    @GuardedBy("this") private BigInteger[] lastFactors;
    @GuardedBy("this") private long hits;
    @GuardedBy("this") private long cacheHits;

    public synchronized long getHits(){return hits;}
    public synchronized double getCacheHitRatio(){
        return (double) cacheHits/(double)hits;
    }

    public void service(ServletRequest req, ServletResponse resp){
        BigInteger i=extractFromRequest(req);
        BigInteger [] factors=null;
        synchronized (this){
            ++hits;
            if(i.equals(lastNumber)){
                ++cacheHits;
                factors=lastFactors.clone();
            }
        }

        if(factors==null){
            factors=factor(i);
            synchronized (this){
                lastNumber=i;
                lastFactors=factors.clone();
            }
        }
        encodeIntResponse(resp,factors);
    }
}
  • 特点:平衡了简单(同步整个方法)与并发(同步尽可能短的代码路径)
  • 考虑因素:
    • 请求与释放锁需要开销,故不要讲锁块分解得过于琐碎。
    • 不要过早为了性能牺牲简单性(可能引发安全问题)
    • 耗时的计算或操作,比如网络或控制台IO,执行期间不要占有锁。
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/992434.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号