编写线程安全的 代码,核心在与对共享的和可变的对象的状态的访问。

如果多个线程访问一个可变的对象时没有使用同步,那么就会出现错误。在这种情况下,有3中方式可以修复这个问题:

  • 不在线程之间共享该状态变量
  • 将状态变量修改为不可变的变量
  • 在访问状态变量时使用同步

线程安全性的定义:

在多个线程访问某个类时,不管运行环境采用何种调度方式或者这些线程将如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么我们就说这个类是线程安全的。

无状态对象:

不包含任何域,也不包含对其他类中域的引用。

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

比如我们平时写的Servlet。

PS:Servlet中可能包含一个Dao域,但是在和Dao本身也是无状态的,我们本身是可以将Dao的代码直接分解到Servlet中,不需要Dao。但是为了实现代码解耦的效果,加入了Dao。

竞态条件

竞态条件(race condition),从多进程间通信的角度来讲,是指两个或多个进程对共享的数据进行读或写的操作时,最终的结果取决于这些进程的执行顺序。

常见的竞态条件如:先检查后执行。

原子操作:

一些简单的竞态条件,可以通过原子操作,解决。

如通过AtomicInteger的incrementAndGet()解决计数器的竞态条件

加锁机制:

复杂的竞态条件需要使用锁来解决。

每个java对象都有一个内置的锁,这个锁被称为内置锁或者监视锁。在这个对象内部的方法上(非static方法)的使用synchronized就是默认使用的这个锁。

内置锁是互斥的,可重入的。

用锁来保护状态

对于可能被多个线程同时访问的可变状态变量,在访问它时都需要持有同一个锁,这种状态下,我们成状态变量时由这个锁保护的。

活跃性和性能

一些概念:

安全性是指"永远不会发生糟糕的事情",我们使用锁和同步就是为了保证安全性。

活跃性是指"某件正确的事情最终会发生",当某个操作不能执行下去,就会出现活跃性问题,如死循环。

性能问题:响应时间,吞吐率,资源消耗,伸缩性等。

我们可以通过锁来解决安全问题,但是也要兼顾活跃性和性能。

重要的是不要粗暴的直接在一个复杂的方法上加synchronize,而是分解。我们看一个例子:

  1. package com.zjf;
  2.  
  3. import java.math.BigInteger;
  4. import java.util.concurrent.ExecutorService;
  5. import java.util.concurrent.Executors;
  6. import java.util.concurrent.TimeUnit;
  7.  
  8. /**
  9.  * 一个获取用于计算整数的平方的类
  10.  * 实现了缓存
  11.  * @author hadoop
  12.  *
  13.  */
  14. public class PowerCache {
  15.  
  16.    //使用全局变量 作为竞争资源
  17.    public static final PowerCache pc = new PowerCache();
  18.    //记录上一个传入的数值 为多个线程共享
  19.    private BigInteger lastNumber;
  20.    //记录上一个计算的平方结果 为多个线程共享
  21.    private BigInteger lastPower;
  22.    //记录power方法调用了多少次 为多个线程共享
  23.    private long hits;
  24.    //记录命中缓存的次数 为多个线程共享
  25.    private long cacheHits;
  26.    //访问了共享数据 注意标注方法为synchronized
  27.    public synchronized long getHits()
  28.    {
  29.       return hits;
  30.    }
  31.    //访问了共享数据 注意标注方法为synchronized
  32.    public synchronized long getCacheHit()
  33.    {
  34.       return cacheHits;
  35.    }
  36.    //命中缓存的概率 访问了共享数据 注意标注方法为synchronized
  37.    public synchronized double getCacheHitRatio()
  38.    {
  39.       return (double)cacheHits / (double) hits;
  40.    }
  41.  
  42.  
  43.    public BigInteger power(BigInteger i)
  44.    {
  45.       BigInteger power = null;
  46.       //分割对比和命中缓存部分为一个synchronized
  47.       synchronized(this) {
  48.          hits++;
  49.          if(i.equals(lastNumber))
  50.          {
  51.             power = lastPower;
  52.             cacheHits++;
  53.          }
  54.       }
  55.       if(power == null)
  56.       {
  57.          //将计算这种耗时逻辑放在synchronized外部
  58.          power = i.pow(2);
  59.          //分割的第二个synchronized块 用于没有命中的结果 需要记录进缓存
  60.          synchronized (this) {
  61.             lastNumber = i;
  62.             lastPower = new BigInteger(power.toString());//复制一份 这一步骤有问题 其实不用复制 这是多此一举 因为BigInteger是不可变对象
  63.          }
  64.       }
  65.       return power;
  66.    }
  67.  
  68.    //测试逻辑
  69.    public static void main(String[] args) throws InterruptedException {
  70.       PowerCache pc = new PowerCache();
  71.       ExecutorService es = Executors.newCachedThreadPool();
  72.       for(int i = 0; i < 100; i++)
  73.       {
  74.          es.execute(new Runnable() {
  75.             public void run() {
  76.                BigInteger bi = BigInteger.valueOf((long)(Math.random() * 10));
  77.                System.out.println(bi + ":" + PowerCache.pc.power(bi));
  78.             }
  79.          });
  80.       }
  81.       es.shutdown();
  82.       TimeUnit.MILLISECONDS.sleep(1000);
  83.       System.out.println(PowerCache.pc.getHits());
  84.       System.out.println(PowerCache.pc.getCacheHit());
  85.       System.out.println(PowerCache.pc.getCacheHitRatio());
  86.    }
  87. }

要判断同步代码块的合理大小,需要在各种设计之间进行权衡,包括安全性,简单性,和性能。

当执行时间较长的计算或者无法快速完成的操作时,如网络IO或者控制台IO。一定不要持有锁。

Java并发编程实战 第2章 线程安全性的更多相关文章

  1. java并发编程实战:第二章----线程安全性

    一个对象是否需要是线程安全的取决于它是否被多个线程访问. 当多个线程访问同一个可变状态量时如果没有使用正确的同步规则,就有可能出错.解决办法: 不在线程之间共享该变量 将状态变量修改为不可变的 在访问 ...

  2. 《Java并发编程实战》第二章 线程安全性 读书笔记

    一.什么是线程安全性 编写线程安全的代码 核心在于要对状态訪问操作进行管理. 共享,可变的状态的訪问 - 前者表示多个线程訪问, 后者声明周期内发生改变. 线程安全性 核心概念是正确性.某个类的行为与 ...

  3. Java并发编程实战 第8章 线程池的使用

    合理的控制线程池的大小: 下面内容来自网络.不过跟作者说的一致.不想自己敲了.留个记录. 要想合理的配置线程池的大小,首先得分析任务的特性,可以从以下几个角度分析: 任务的性质:CPU密集型任务.IO ...

  4. 《Java并发编程实战》第二章 线程安全 札记

    一个.什么是线程安全 编写线程安全的代码 其核心是管理国事访问的操作. 共享,可变的状态的訪问 - 前者表示多个线程訪问, 后者声明周期内发生改变. 线程安全性 核心概念是正确性.某个类的行为与其规范 ...

  5. 读书笔记-----Java并发编程实战(一)线程安全性

    线程安全类:在线程安全类中封装了必要的同步机制,客户端无须进一步采取同步措施 示例:一个无状态的Servlet @ThreadSafe public class StatelessFactorizer ...

  6. java并发编程笔记(三)——线程安全性

    java并发编程笔记(三)--线程安全性 线程安全性: ​ 当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些进程将如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现 ...

  7. Java并发编程实战---第六章:任务执行

    废话开篇 今天开始学习Java并发编程实战,很多大牛都推荐,所以为了能在并发编程的道路上留下点书本上的知识,所以也就有了这篇博文.今天主要学习的是任务执行章节,主要讲了任务执行定义.Executor. ...

  8. 《Java并发编程实战》学习笔记 线程安全、共享对象和组合对象

    Java Concurrency in Practice,一本完美的Java并发参考手册. 查看豆瓣读书 推荐:InfoQ迷你书<Java并发编程的艺术> 第一章 介绍 线程的优势:充分利 ...

  9. Java并发编程实战 第16章 Java内存模型

    什么是内存模型 JMM(Java内存模型)规定了JVM必须遵循一组最小保证,这组保证规定了对变量的写入操作在何时将对其他线程可见. JMM为程序中所有的操作定义了一个偏序关系,称为Happens-Be ...

随机推荐

  1. apache禁止指定的user_agent访问

    user_agent:也就是浏览器标识#禁止指定user_agent <IfModule mod_rewrite.c> RewriteEngine on RewriteCond %{HTT ...

  2. layer的iframe弹框中父子元素的传值

    项目中,左侧导航树,右侧是 iframe 嵌套的页面,在右侧页面中又有layer弹框,可以说是有两层 iframe 框架. 所以查询网上的parent什么的方法都不能用.自己摸索的下面的方法: 1.父 ...

  3. 【DVWA】Brute Force(暴力破解)通关教程

    日期:2019-08-01 14:49:47 更新: 作者:Bay0net 介绍:一直以为爆破很简单,直到学习了 Burp 的宏录制和匹配关键词,才发现 burp 能这么玩... 0x01. 漏洞介绍 ...

  4. LoadRunner 技巧之 检查点

    LoadRunner 技巧之 检查点 判断脚本是否执行成功是根据服务器返回的状态来确定的,如果服务器返回的HTTP状态为 200 OK ,那么VuGen 就认为脚本正确地运行了,并且是运行通过的.在绝 ...

  5. 想使用 MongoDB ,你应该了解这8个方面!

    想使用 MongoDB ,你应该了解这8个方面! 应用性能高低依赖于数据库性能,MongoDB 是一个基于分布式文件存储的数据库.由 C++ 语言编写,旨在为 WEB 应用提供可扩展的高性能数据存储解 ...

  6. 将训练好的tensorflow模型移植到android应用中

    具体步骤如下: 1.  TFLiteConverter保存模型 修改网络模型代码,将模型通过TFLiteConverter转化成为 TensorFlow Lite FlatBuffer即为.tflit ...

  7. svn导出项目到myeclipse,运行报ClassNotFoundException

    一开始以为是 这样的svn导出项目到myeclipse,运行报ClassNotFoundException 后来不行 又看了一下  还不行 以为是这样的MyEclipse2014报错java.lang ...

  8. java:Oracle(table的增删改查,data的增删改查)

    1.oracle命名规范:和Java的命名规范很像 1.严格区分大小写 2.所有的sql语句都要以';'结尾 3.所有的sql 都要使用空格区分:sqlplus空格/空格as空格sysdba回车 4. ...

  9. 汇编语言——用DOSBox的debug查看CPU和内存 & 用机器指令和汇编指令编程

    实验一 查看CPU和内存,用机器指令和汇编指令编程   实验目的 了解什么是Debug,以及Debug中需要用的一些功能 R:查看.改变CPU寄存器的内容 D:查看内存中的内容 E:改写内存中的内容 ...

  10. Java基础/Java异常

    Java异常 1.异常的分类: ① 非运行时异常(Checked Exception) Java中凡是继承自Exception但不是继承自RuntimeException的类都是非运行时异常 ② 运行 ...