对《Java虚拟机并发编程》这本书真的是相见恨晚。以前对并发编程只是懂个皮毛,这本书让我对并发编程有了一个全新的认识。所以把书上的知识点做下笔记,以便以后复习使用。

并发与并行

  仔细说来,并发和并行是两个不同的概念。但随着多核处理器的普及,并发程序的不同的线程往往会被编译器分配到不同处理器核心上,所以说并发编程与并行对于上层程序员来说是一样的。

并发的风险

饥饿

  当一个线程等待某个需要运行时间很长或者永远无法完成的时间发发生,那么这个线程就会陷入饥饿状态。通常饥饿也会被叫做活锁。

  解决饥饿的方法是设计一个等待超时策略,让线程等待有限的时间

死锁

  两个或多个线程互相等待对方释放所占用的资源或执行的某些动作。

竞争条件

  竞争条件指两个线程竞争使用相同的资源或者数据。

  造成竞争条件的主要原因是:Just-In-Time(JIT)编译器优化以及Java内存模型。

内存栅栏

  内存栅栏(Memory Barrier)指从本地缓存(或寄存机)或工作内存到主存之间的拷贝动作。在程序运行过程中,所有的变更会先在寄存器或本地cache中完成,然后才会被拷贝到主存以跨越内存栅栏。这种跨越顺序称为happens-before。

  写操作必须happens-before读操作,即写操作需要在所有读线程跨越内存栅栏之前完成自己的跨越动作,其所作的变更才能对其他线程可见。

  Java并发API中由很多操作都隐含有跨越内存栅栏的含义:volatile、sychronized、Thread中的start()和interrupt()、ExecutorService中的函数以及向CountDown这样的同步工具。

volidate

  关键字volidate的作用是告知JIT编译器不要对标记变量执行任何可能影响其访顺序的优化,而且对该变量的读写访问都需要忽略本地cache并直接对内存进行操作。但会使每次变更访问都要跨越内存栅栏并最终导致程序性能下降。此外在多个字段被多个线程并发访问得场景下,由于针对每个volidate字段的访问都是各自独立处理的,并且无法将这些访问统一协调成一次访问,所以volatile关键字无法保证整体操作的原子性。

  解决方法是屏蔽对变量的直接访问,并将所有访问都引导为通过同步的getter和setter方法。

并发策略

确定线程数

程序所需的线程的总数:

  线程数=CPU可用核心数/(1-阻塞系数)

其中阻塞系数取值在0和1之间。计算密集型任务的阻塞系数为0,而IO密集型任务的阻塞系数则接近1。

确定任务数

实践证明,在解决问题的过程中使处理器一直处于忙碌状态比将负载均摊到每个子任务要轻松的多。从处理器的角度来看,只要保证“只要还有待完成的任务,就不可能有空闲的处理器核心”就行了。因此,与其斤斤计较如何将负载平摊到每个子任务上,不如将任务拆分的比线程数多,以是处理器一直不停的工作。

设计方法

共享可变性(shared mutability)

创建的变量允许所有线程在可控的模型下修改。编程虽然简单,但会导致同步问题。必须保证代码在合适的时间穿越内存栅栏,并是变量具有良好的可见性。

隔离可变性(isolated mutability)

变量是可变的,但在任意时刻只有不超过一个线程可以看到该变量。

纯粹不变性(pure mutability)

所有事物都是不允许更改的。

不可变链表

通过把新节点插入链表的头,来是新旧链表共存。

持久化的Tries

通过高分支因子和前缀树的特定来组织状态的存储。

可扩展性和安全性

用ExecutorService管理线程

每个ExecutorService都代表一个线程池,其作用是将线程的创建与执行过程分离开来,而不是将线程的生命周期管理和任务的执行过程绑在一起。用线程池来管理大量线程,增加线程的重用。

可以按需配置线程池的类型,单线程的、带缓存的、基于优先级的、按预定时间调度/周期调度的、固定大小的。

线程协作

使用Callable接口和ExecutorService的invokeAll()或submit()函数来获取线程任务的返回值

线程池诱发的死锁(Pool Induced Deadlock):先创建的线程由于等待其他线程的结果而阻塞,但仍占用线程池,而由于线程池大小限制,无法创建新的线程来产生结果,造成了死锁。

使用CounDownLatch实现线程的协作

数据交换

想要在线程之间互发多组数据,可以用BlockingQueue接口。如果队列里没有空间,则插入操作被阻塞;如果队列里没有可用数据,则删除操作被阻塞。

如果想使插入和删除操作一一对应,可以使用SynchronousQueue类。

如果希望数据可以根据某种优先级在队列中上下浮动,则可以使用PriorityBlockQueue操作

如果只是想要一个简单的阻塞队列,可以选择链表实现LinkedBlockingQueue或者ArrayBlockingQueue

Fork-Join API

ForkJoinPool类根据可用的处理器数量和任务需求动态对线程进行管理。Fork-join使用了work-stealing策略,即线程在完成自己的任务之后,发现其他线程还有任务没有完成,就主动帮助其他线程。不但提升了API的性能,而且还有助于提高线程利用率。

在Fork-join API中,活动任务(Active task)所创建的子任务都是由创建主任务所不同的另一套函数来负责调度。通常一个应用程序中会使用一个fork-join池来调度任务,而且由于该线程池使用了守护线程,所以无需执行关闭操作。

ForkJoinTask有两个子类:RecursiveAction(执行不需要返回值的任务)和RecursiveTask(用于执行需要返回值得任务)

Fork-join API非常适合解决那些可以递归分解至小到足以顺序运行的问题。通过使用ForkJoinPool管理线程,多个较小的分解任务可以被同时执行。

可扩展集合类

同步集合类(synchronized collections)实现了线程安全的特性,而并发集合类(Concurrent collections)则保证了线程安全的同时,也兼顾了并发访问得性能。

同步集合类在执行遍历的时候会持有一个互斥锁,即悲观锁(pessimistic lock)。这种做法虽然增加了线程安全性,但同时也将显著降低并发访问性能。

并发集合类允许交叉读写,吞吐量比同步版本好。这是由于并发集合类并不是只通过一个互斥锁来进行同步管理的,而是将整个集合数据分成若干bulk,并允许更新操作和读操作在多个块上同时进行。在使用并发集合类时,读操作并非总能看到最新的数据值,这是因为如果读操作在某一个bulk的更新期间发生,则这个读操作不会为了等待整个更新操作完成而被阻塞,这就意味着只能看到集合数据的部分变化。

Synchronized和Lock

sychronized关键字可以显式获取对象的monitor/锁,并通过“先获取monitor并在代码块结尾处释放掉”的方式来帮助线程穿越内存栅栏。无法设置sychronized关键字在获取锁时等待的时间,可能会造成活锁。

Lock接口保证了对其方法的调用或跨越内存栅栏的同时,获得了更强的控制能力。lock()、unlock()、tryLock()

共享可变性

共享可变性不等于public

如果一个变量可以被多个线程读写,则该变量是可访问的且共享的。另一方面,如果一个变量仅能被一个线程访问,则称该变量是隔离的且非共享的。必须保证所有被被当做参数传递给其他函数的那些变量都是线程安全的。因为需要假定调用的参数将会从多个线程里访问这些参数。因此向函数传递一个非线程安全的变量将会导致意想不到的结果。同时对于函数的返回值也存在这个问题。

《Java虚拟机并发编程》学习笔记的更多相关文章

  1. js学习笔记:webpack基础入门(一)

    之前听说过webpack,今天想正式的接触一下,先跟着webpack的官方用户指南走: 在这里有: 如何安装webpack 如何使用webpack 如何使用loader 如何使用webpack的开发者 ...

  2. PHP-自定义模板-学习笔记

    1.  开始 这几天,看了李炎恢老师的<PHP第二季度视频>中的“章节7:创建TPL自定义模板”,做一个学习笔记,通过绘制架构图.UML类图和思维导图,来对加深理解. 2.  整体架构图 ...

  3. PHP-会员登录与注册例子解析-学习笔记

    1.开始 最近开始学习李炎恢老师的<PHP第二季度视频>中的“章节5:使用OOP注册会员”,做一个学习笔记,通过绘制基本页面流程和UML类图,来对加深理解. 2.基本页面流程 3.通过UM ...

  4. 2014年暑假c#学习笔记目录

    2014年暑假c#学习笔记 一.C#编程基础 1. c#编程基础之枚举 2. c#编程基础之函数可变参数 3. c#编程基础之字符串基础 4. c#编程基础之字符串函数 5.c#编程基础之ref.ou ...

  5. JAVA GUI编程学习笔记目录

    2014年暑假JAVA GUI编程学习笔记目录 1.JAVA之GUI编程概述 2.JAVA之GUI编程布局 3.JAVA之GUI编程Frame窗口 4.JAVA之GUI编程事件监听机制 5.JAVA之 ...

  6. seaJs学习笔记2 – seaJs组建库的使用

    原文地址:seaJs学习笔记2 – seaJs组建库的使用 我觉得学习新东西并不是会使用它就够了的,会使用仅仅代表你看懂了,理解了,二不代表你深入了,彻悟了它的精髓. 所以不断的学习将是源源不断. 最 ...

  7. CSS学习笔记

    CSS学习笔记 2016年12月15日整理 CSS基础 Chapter1 在console输入escape("宋体") ENTER 就会出现unicode编码 显示"%u ...

  8. HTML学习笔记

    HTML学习笔记 2016年12月15日整理 Chapter1 URL(scheme://host.domain:port/path/filename) scheme: 定义因特网服务的类型,常见的为 ...

  9. DirectX Graphics Infrastructure(DXGI):最佳范例 学习笔记

    今天要学习的这篇文章写的算是比较早的了,大概在DX11时代就写好了,当时龙书11版看得很潦草,并没有注意这篇文章,现在看12,觉得是跳不过去的一篇文章,地址如下: https://msdn.micro ...

  10. ucos实时操作系统学习笔记——任务间通信(消息)

    ucos另一种任务间通信的机制是消息(mbox),个人感觉是它是queue中只有一个信息的特殊情况,从代码中可以很清楚的看到,因为之前有关于queue的学习笔记,所以一并讲一下mbox.为什么有了qu ...

随机推荐

  1. IEnumerable 使用foreach 详解

    自己实现迭代器 yield的使用 怎样高性能的随机取IEnumerable中的值 我们先思考几个问题: 为什么在foreach中不能修改item的值? 要实现foreach需要满足什么条件? 为什么L ...

  2. Struts(八):动态方法调用

    动态方法调用:通过url动态调用action中的方法. 默认情况下,Struts的动态方法调用处于禁用状态. 测试定义一个action类: package com.dx.actions; public ...

  3. mac上mysql乱码问题解决

    一.mysql出现乱码问题: 出现的问题是下图这样的乱码问题,我是使用java在做练习的时候发现出现字符集编码问题的: 当时是使用jdbc来添加的数据,我的jdbc包括web前端后端的编码都是设置的是 ...

  4. 如何快速建立一个测试资源Web服务器及异步获取资源(Unity3D)

    背景 1.最近看了几位专栏作家的文章,几篇提到了资源通过网络的动态获取.如何建立一个快速的测试环境,不免是一个问题,也就最简单的就是假设http服务器了,微软系的当然首选的IIS了,别的也能用阿帕奇或 ...

  5. linux 查看磁盘剩余命令

    df -hl命令查看各磁盘的大小,使用情况,挂载点等信息. du -bs dir_name查看具体目录的空间大小

  6. Mysql----------的一些常用命令

    1.查询一张表中某个字段重复值的记录 select id,cert_number from (select id,cert_number,count(*)as n from 表明 group by c ...

  7. 总结一下在ASP.NET中开发网站的一般步骤

    1.打开Miscosoft Visual Studio2010 2.新建网页添加新项 3.设计网页 4.添加窗体控件 5.优化页面 6.调试网页

  8. RobotFrameWork WebService Soap接口测试 (一)

    在做完基于http协议的接口测试之后,开始弄soap协议了,之前有过开发java webservice自动化框架的经验,所以我想着应该并不会很难.对于webservice的简介,服务器端和客户端的开发 ...

  9. Repeater控件三层嵌套-内层Repeater添加绑定事件

    用Repeater三层嵌套,最外层Repeater可以生成自己的ItemCommand事件.但接下来中间层因为是嵌套了的,所以无法在属性窗口中生成自己的事件.如果手动敲入则无效. 解决办法是需要通过编 ...

  10. CRM 2011 Install Errors - Tips and Tricks continued(转)

    The more I get to install/upgrade to CRM 2011 in different environment the more I come across differ ...