Java并发编程的艺术读书笔记

这几天听Di哥的推荐在看Java并发编程的艺术这本书.索性把读书笔记也写到博客里...

1并发编程的挑战

(上下文切换,死锁,资源限制)

(1)

上下文切换的挑战

○用并行执行和串行执行的小实验对比证明并行并不是一定比串行快的,这里我也自己写了一遍书中的代码并亲自测试了一下.先贴代码和结果.

package test;


public class testConcurrencyAndSerial {
    static long count = 1000000L;

    public static void concurrency() throws InterruptedException {
        long start = System.currentTimeMillis();
        Thread thread = new Thread(() -> {
            long a = 0;
            for (long i = 0; i < count; i++) {
                a += 5;
            }
        });
        thread.start();
        long b = 0;
        for (long i = 0; i < count; i++) {
            b--;
        }
        thread.join();
        long end = System.currentTimeMillis();
        System.out.println("con time" + (end - start) + "   a b" + " " + b);
    }

    public static void serial() {
        long start = System.currentTimeMillis();

        long a = 0;
        for (long i = 0; i < count; i++) {
            a += 5;
        }

        long b = 0;
        for (long i = 0; i < count; i++) {
            b--;
        }
        long end = System.currentTimeMillis();
        System.out.println("serial time" + (end - start) + "   a b" + a + " " + b);
    }

    public static void main(String[] args) throws InterruptedException {
        concurrency();
        serial();
    }

}

 其中 最后的数字是count 前面是时间 可以清晰的看出 并不是所有情况都是多线程快的.

可以用lmbench测量上下文切换时长 使用vmstat测量上下文切换的次数 (留flag 装完虚拟机拿来玩一下)

减少上下文切换:无锁并发编程 CAS算法 使用最少线程 使用携程

无锁并发编程:多线程竞争锁会导致上下文切换,可以想办法替代锁,比如将数据的ID按hash算法取模分段,不同的线程处理不同段的数据.

CAS算法:Java的Atomic包使用CAS算法更新数据不用加锁(其实就是拿主存中的值和副本比 相等就修改 不相等就不改)AtomicInteger类中有valueOffset value expect update

使用最少线程:避免大量线程处于等待状态

协程:单线程实现多任务调度 在单线程中维持多个任务的调度

减少切换线程实战1.1.4(留flag 要做)

(2)

死锁

死锁代码展示,我也试着写了一个.

package test;



public class DeadLock {
    static String A="1";
    static String B="2";

    public static void main(String[] args) {
        new DeadLock().showDeadLock();
    }
    public void starta(){
        Thread thread=new Thread(()->{
            synchronized (A){
                System.out.println("a has A");
                try{
                    Thread.currentThread().sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (B){
                    System.out.println("i can do it");
                }
            }
        });
        thread.start();
    }
    public void startb(){
        Thread thread=new Thread(()->{
            synchronized (B){
                System.out.println("b has B");
                try{
                    Thread.currentThread().sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (A){
                    System.out.println("i can do it");
                }
            }
        });
        thread.start();
    }

    private void showDeadLock() {
        starta();
        startb();
    }

}

避免死锁: 避免在一个线程中获取多个锁 避免在一个锁中获取多个资源 使用定时锁 加解锁在同一个数据库连接中

(3)

资源限制

并发时由于资源受限,如下载的带宽 也包含软件资源如Socket连接数和数据库连接数

若受资源限制导致并发变串行会更慢 因为有上下文的切换

硬件限制改硬件,用hadoop或自己搭建服务器集群 

软件限制考虑软件资源复用 将socket和数据库连接复用

2java并发机制的底层实现原理

(volatile synchronized)

(1)

volatile

volatile是轻量级的synchrond 它在多处理器开发中保证了共享变量的可见性.volatile比synchronized成本低 不引起线程上下文切换和调度,下面分析在硬件上如何实现的volatile

定义:为确保共享变量准确和一致更新,线程应该确保通过排他锁单独获取这个变量.java线程内存模型保证所有线程看到这个变量的值一致.

术语:memory barriers实现对内存操作的顺序限制      cache line缓存可以分配的最小单位

X86下通过获取JIT生成的汇编指令查看volatile变量被赋值(这里我的老是无法正确bi生成汇编指令 明天去问下d哥东伟哥炳杰哥富豪哥和阿昌哥)

先贴书里的汇编

 指令前被加了lock,查IA-32架构软件开发者手册(明天整一本瞅瞅),作者知道了Lock前缀指令在多核处理器下引发两件事:将当前处理器缓存行数据写回系统内存 ,这个写会内存操作使其它CPU里缓存了该内存地址的数据无效.

这里作者还写了一段操作系统中的知识,应该是怕读者操作系统课走神没听缓存(为了提高处理速度,cpu快 读内存慢 中间搞点高速缓存,因为很多时候访问的数据就在访问过的地方附近),声明volatile之后如果更新值,JVM加Lock指令,让变量所在缓存行回写内存,为了防止已经读了该变量的处理器乱来实现了缓存一致性协议.:每个处理器嗅探总线上传播的数据检查自己缓存的值是不是过期了,当处理器发现自己缓存行对应内存地址修改,就将当前处理器缓存行设置无效.然后修改的话就重读

LOCK前缀引起处理器缓存回写内存:Lock前缀指令导致指令执行期间 声言处理器的Lock#信号 多处理器环境中 Lock#信号确保声言该信号期间 处理器独占任何共享内存.在最近的处理器中不锁总线 锁缓存  Intel486和pentium总是在总线上声言Lock信号 如果已经缓存  P6和目前的处理器锁定这块内存区域的缓存并回写内存 使用缓存一致性机制确保阻止同时修改两个以上处理器缓存的数据

一个处理器的缓存回写内存会导致其它处理器的缓存无效IA32和Intel64处理器使用MESI控制协议去维护内部缓存和其它处理器缓存的一致性.

volatile的使用优化

大佬Doug lea在JDK7中新增了一个队列集合类Linked-TransferQueue,它使用volatile变量时,用增加字节的方式来优化出入队列的性能.其实就是增加了十五个变量变成了64字节,因为许多处理器的L1L2L3高速缓存是64字节宽 不支持部分填充缓存行  如果不添加字节的话很多节点会被丢到同一缓存行中  而volatile又会导致整个缓存行被锁定 而多线程情况下队列出入队经常修改头尾节点,因此影响效率.

当然 如果缓存行不是64 或者本来就不存在高频繁读写就不应该这么做了

(2)

synchronized

实现原理与应用

对于普通同步方法 锁是当前实例对象

对于静态同步方法 锁是当前类的class对象

对于同步方法快 锁是synchronized 括号里配置的对象

锁存在哪里 锁里存什么信息

synchronized用的锁是存在对象头中的,这里需要看一下Hotspot中java对象的内存模型。

MarkWord—》(size)-》class指针-》对象实际数据-》(字节对齐)

数组的对象头是12字节(3字宽)就是size的原因

其中这里关注的是MarkWord 它不是一个固定的数据结构 它可以动态的去代表重锁 轻锁 偏向锁 无锁 GC等

对于锁的内容来说

JVM基于进入退出Monitor对象实现方法同步和代码块同步 

代码块同步是使用monitorenter和monitorexit实现

方法同步是另外一种规范

monitorenter插入同步代码块开始位置

monitorexit插入方法结束处和异常处

monitor被持有之后进入锁定状态

然后看锁的特点

首先是偏向锁 统计发现 很多情况下锁都由同一线程获得,这种情况下频繁加解锁造成了浪费,因此设计了偏向锁,使用时,当一个线程访问同步块时,会在相应对象头和栈帧中的锁记录(了解)存锁偏向的线程id,以后线程进入同步块只需验证对象头中偏向的线程id是不是自己。

是,则获得锁

不是,则检查MarkWord中偏向锁是否设置成1,不是则CAS竞争锁,是则尝试将它指向自己

轻量锁加锁,首先线程在栈帧中开辟锁记录空间,然后复制对象头中的markword到锁记录中,然后尝试CAS将对象头中的MarkWord替换为指向栈帧中锁记录的指针,成功则获得,失败则自旋

轻量锁解锁,尝试CAS将栈帧中MarkWord替换回对象头中,成功解锁,失败说明有竞争,升级为重量锁(这里很多人对轻量锁是否存在抢夺时自旋存在争议 mark一下读一下源码解决一下)

实现原子操作

1.锁总线,多个处理器进行i++操作的经典场景,是因为多个cpu的不同缓存导致的,因此可以通过cpu 在总线声言Lock#信号从而独占共享内存实现原子操作

2.锁缓存,频繁使用的内存回缓存在L1L2L3中因此原子操作可以直接在缓存中进行,内存区域如果被缓存且在Lock处理期间被锁定(这里要专门看一眼lock# lock)则执行锁操作回写内存时,不声言Lock#信号,而修改内部的内存地址并允许它的缓存一致性保证操作原子性,其它处理器回写缓存行时会让缓存行无效。

操作数据跨多缓存行/无法缓存时 无法使用锁缓存 不支持缓存锁定无法锁缓存

循环cas实现原子操作

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
完整版:https://download.csdn.net/download/qq_27595745/89522468 【课程大纲】 1-1 什么是java 1-2 认识java语言 1-3 java平台的体系结构 1-4 java SE环境安装和配置 2-1 java程序简介 2-2 计算机中的程序 2-3 java程序 2-4 java类库组织结构和文档 2-5 java虚拟机简介 2-6 java的垃圾回收器 2-7 java上机练习 3-1 java语言基础入门 3-2 数据的分类 3-3 标识符、关键字和常量 3-4 运算符 3-5 表达式 3-6 顺序结构和选择结构 3-7 循环语句 3-8 跳转语句 3-9 MyEclipse工具介绍 3-10 java基础知识章节练习 4-1 一维数组 4-2 数组应用 4-3 多维数组 4-4 排序算法 4-5 增强for循环 4-6 数组和排序算法章节练习 5-0 抽象和封装 5-1 面向过程的设计思想 5-2 面向对象的设计思想 5-3 抽象 5-4 封装 5-5 属性 5-6 方法的定义 5-7 this关键字 5-8 javaBean 5-9 包 package 5-10 抽象和封装章节练习 6-0 继承和多态 6-1 继承 6-2 object类 6-3 多态 6-4 访问修饰符 6-5 static修饰符 6-6 final修饰符 6-7 abstract修饰符 6-8 接口 6-9 继承和多态 章节练习 7-1 面向对象的分析与设计简介 7-2 对象模型建立 7-3 类之间的关系 7-4 软件的可维护与复用设计原则 7-5 面向对象的设计与分析 章节练习 8-1 内部类与包装器 8-2 对象包装器 8-3 装箱和拆箱 8-4 练习题 9-1 常用类介绍 9-2 StringBuffer和String Builder类 9-3 Rintime类的使用 9-4 日期类简介 9-5 java程序国际化的实现 9-6 Random类和Math类 9-7 枚举 9-8 练习题 10-1 java异常处理 10-2 认识异常 10-3 使用try和catch捕获异常 10-4 使用throw和throws引发异常 10-5 finally关键字 10-6 getMessage和printStackTrace方法 10-7 异常分类 10-8 自定义异常类 10-9 练习题 11-1 Java集合框架和泛型机制 11-2 Collection接口 11-3 Set接口实现类 11-4 List接口实现类 11-5 Map接口 11-6 Collections类 11-7 泛型概述 11-8 练习题 12-1 多线程 12-2 线程的生命周期 12-3 线程的调度和优先级 12-4 线程的同步 12-5 集合类的同步问题 12-6 用Timer类调度任务 12-7 练习题 13-1 Java IO 13-2 Java IO原理 13-3 流类的结构 13-4 文件流 13-5 缓冲流 13-6 转换流 13-7 数据流 13-8 打印流 13-9 对象流 13-10 随机存取文件流 13-11 zip文件流 13-12 练习题 14-1 图形用户界面设计 14-2 事件处理机制 14-3 AWT常用组件 14-4 swing简介 14-5 可视化开发swing组件 14-6 声音的播放和处理 14-7 2D图形的绘制 14-8 练习题 15-1 反射 15-2 使用Java反射机制 15-3 反射与动态代理 15-4 练习题 16-1 Java标注 16-2 JDK内置的基本标注类型 16-3 自定义标注类型 16-4 对标注进行标注 16-5 利用反射获取标注信息 16-6 练习题 17-1 顶目实战1-单机版五子棋游戏 17-2 总体设计 17-3 代码实现 17-4 程序的运行与发布 17-5 手动生成可执行JAR文件 17-6 练习题 18-1 Java数据库编程 18-2 JDBC类和接口 18-3 JDBC操作SQL 18-4 JDBC基本示例 18-5 JDBC应用示例 18-6 练习题 19-1 。。。
完整版:https://download.csdn.net/download/qq_27595745/89522468 【课程大纲】 1-1 什么是java 1-2 认识java语言 1-3 java平台的体系结构 1-4 java SE环境安装和配置 2-1 java程序简介 2-2 计算机中的程序 2-3 java程序 2-4 java类库组织结构和文档 2-5 java虚拟机简介 2-6 java的垃圾回收器 2-7 java上机练习 3-1 java语言基础入门 3-2 数据的分类 3-3 标识符、关键字和常量 3-4 运算符 3-5 表达式 3-6 顺序结构和选择结构 3-7 循环语句 3-8 跳转语句 3-9 MyEclipse工具介绍 3-10 java基础知识章节练习 4-1 一维数组 4-2 数组应用 4-3 多维数组 4-4 排序算法 4-5 增强for循环 4-6 数组和排序算法章节练习 5-0 抽象和封装 5-1 面向过程的设计思想 5-2 面向对象的设计思想 5-3 抽象 5-4 封装 5-5 属性 5-6 方法的定义 5-7 this关键字 5-8 javaBean 5-9 包 package 5-10 抽象和封装章节练习 6-0 继承和多态 6-1 继承 6-2 object类 6-3 多态 6-4 访问修饰符 6-5 static修饰符 6-6 final修饰符 6-7 abstract修饰符 6-8 接口 6-9 继承和多态 章节练习 7-1 面向对象的分析与设计简介 7-2 对象模型建立 7-3 类之间的关系 7-4 软件的可维护与复用设计原则 7-5 面向对象的设计与分析 章节练习 8-1 内部类与包装器 8-2 对象包装器 8-3 装箱和拆箱 8-4 练习题 9-1 常用类介绍 9-2 StringBuffer和String Builder类 9-3 Rintime类的使用 9-4 日期类简介 9-5 java程序国际化的实现 9-6 Random类和Math类 9-7 枚举 9-8 练习题 10-1 java异常处理 10-2 认识异常 10-3 使用try和catch捕获异常 10-4 使用throw和throws引发异常 10-5 finally关键字 10-6 getMessage和printStackTrace方法 10-7 异常分类 10-8 自定义异常类 10-9 练习题 11-1 Java集合框架和泛型机制 11-2 Collection接口 11-3 Set接口实现类 11-4 List接口实现类 11-5 Map接口 11-6 Collections类 11-7 泛型概述 11-8 练习题 12-1 多线程 12-2 线程的生命周期 12-3 线程的调度和优先级 12-4 线程的同步 12-5 集合类的同步问题 12-6 用Timer类调度任务 12-7 练习题 13-1 Java IO 13-2 Java IO原理 13-3 流类的结构 13-4 文件流 13-5 缓冲流 13-6 转换流 13-7 数据流 13-8 打印流 13-9 对象流 13-10 随机存取文件流 13-11 zip文件流 13-12 练习题 14-1 图形用户界面设计 14-2 事件处理机制 14-3 AWT常用组件 14-4 swing简介 14-5 可视化开发swing组件 14-6 声音的播放和处理 14-7 2D图形的绘制 14-8 练习题 15-1 反射 15-2 使用Java反射机制 15-3 反射与动态代理 15-4 练习题 16-1 Java标注 16-2 JDK内置的基本标注类型 16-3 自定义标注类型 16-4 对标注进行标注 16-5 利用反射获取标注信息 16-6 练习题 17-1 顶目实战1-单机版五子棋游戏 17-2 总体设计 17-3 代码实现 17-4 程序的运行与发布 17-5 手动生成可执行JAR文件 17-6 练习题 18-1 Java数据库编程 18-2 JDBC类和接口 18-3 JDBC操作SQL 18-4 JDBC基本示例 18-5 JDBC应用示例 18-6 练习题 19-1 。。。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值