欢迎来到我的个人网站。交流请加我好友: 919201148。欢迎关注公众号或视频号:蜗牛互联网
Solo  当前访客:2 开始使用

白色蜗牛的互联网心得

我要一步一步往上爬,在最高点乘着叶片往前飞

标签: Java并发 (18)

15.原子变量与非阻塞同步机制

2016-05-10 02:26:31 huayonglun
0  评论    216  浏览

java.util.concurrent包中的许多类,比如Semaphore和ConcurrentLinkedQueue,都提供了比使用synchronized更好的性能和可伸缩性。这一章,我们来学习这些性能提升的原始来源:原子变量和非阻塞的同步机制。 近来很多关于并发算法的研究都聚集在非阻塞算法上,这种算法使用低层原子化的机器指令取代锁,比如比较并交换,从而保证数据在并发访问下的一致性。非阻塞算法广泛用于操作系统和JVM中的线程和进程调度、垃圾回收以及实现锁和其他并发数据结构。 与基于锁的方案相比,非阻塞算法的设计和实现都要复杂得多,但是它们在可伸缩性或活跃度上占有很大的优势。因为非阻塞算法可以让多个线程在竞争相同资源时不会发生阻塞,所以它能在更精化的层面上调整粒度,并能大大减少调度的开销。 进一步而言,它们对死锁和其他活跃度问题具有免疫性。在基于锁的算法中,如果一个线程在持有锁的时候休眠,或者停滞不前,那么其他线程就都不可能前进了,而非阻塞算法不会受到单个线程失败的影响。 在java 5.0 中,使用原子变量类,比如AtomicInteger和AtomicReference,能够高....

, ,

14.构建自定义的同步工具

2016-05-09 02:26:31 huayonglun
0  评论    216  浏览

类库中包含了大量依赖于状态(state-dependent)的类——这些类拥有基于状态的先验条件——FutureTask、Semaphore和BlockingQueue。例如,你不能从一个空队列中移除条目,也不能从一个尚未结束的任务中获取结果;在这些操作可以执行前,你必须等到队列进入“非空”状态,任务进入“完成”状态。 创建状态依赖类最简单的方法通常是将它构建于已有的状态依赖库之上。如果类库没有提供你需要的功能,你也可以使用语言和类库提供的底层机制,包括内部条件队列、显示的Condition对象和AbstractQueuedSynchronizer框架,构建属于自己的Synchronizer。这一章探究一些实现状态依赖性时要面对的不同选择,以及使用平台提供的状态依赖性机制时需要遵守的不同规则。 14.1 管理状态依赖性 生产者-消费者的设计经常会使用ArrayBlockingQueue这种有限缓存。一个有限缓存提供的put和take操作,每一个都有先验条件:你不能从空缓存中获取元素,也不能把元素置入已满的缓存中。如果依赖于状态的操作在处理先验条件时失败,可以抛出异常或者返回错误状态....

, ,

13.显式锁

2016-05-08 02:26:31 huayonglun
0  评论    214  浏览

在Java 5.0之前,用于调节共享对象访问的机制只有synchronized和volatile Java 5.0 提供了新的选择:ReentrantLock。 ReentrantLock并不是作为内部锁的替代,而是当内部锁被证明受到局限时,提供可选择的高级特性。 13.1 Lock 和 ReentrantLock Lock接口,定义了一些抽象的锁操作 与内部加锁机制不同,Lock提供了 无条件 可轮询 定时 可中断的锁获取操作 所有加锁和解锁的方法都是显式的。 public interface Lock { void lock(); void lockInterruptibly() throws InterruptedException; boolean tryLock(long time, TimeUnit unit) throws InterruptedException; void unlock(); Condition newCondition(); } ReentrantLock实现了Lock接口,提供了与synchronized相同的互斥和内存可见....

, ,

11.性能和可伸缩性

2016-05-06 02:26:31 huayonglun
0  评论    235  浏览

使用线程最主要的原因是提高性能。使用线程可以使程序更加充分的发挥出闲置的处理能力,从而更好的利用资源;并能够使程序在现有任务正在运行的情况下立刻开始着手处理新的任务,从而提高系统的响应性。 11.1 性能的思考 改进性能意味着用更少的资源做更多的事情。 资源包括CPU周期、内存、网络带宽、I/O带宽、数据库请求、磁盘空间、以及其他一些资源。 当活动的运行因某个特定资源受阻时,我们称之为受限于该资源:受限于CPU,受限于数据库。 虽然目标是提高性能,但是使用多线程总会引入一些性能开销:与协调线程相关的开销(加锁、信号、内存同步),增加的上下文切换,线程的创建和消亡,以及调度的开销。当线程被过度使用后,这些开销会超过吞吐量响应性和计算能力带来的补偿。 另一方面一个没有经过良好并发设计的应用程序,甚至比相同功能的顺序的程序性能更差。 为了实现更好的性能,我们需要更有效的利用现有的处理资源,使CPU尽可能处于忙碌状态。 11.1.1 性能“遭遇”可伸缩性 应用程序可以从很多个角度来衡量: 服务时间、等待时间、(衡量有多快) 吞吐量、生产量、可伸缩性、(衡量有多少) 效率、 可....

, ,

08.应用线程池

2016-05-02 22:23:53 huayonglun
0  评论    211  浏览

本章关注在配置和调整线程池时用的高级选项,讲述了使用任务执行框架的过程中需要注意的危险,还提供了一些使用Executor更高级的例子。 8.1 任务与执行策略间的隐性耦合 前面我们声称Executor框架可以将任务的提交与执行策略解耦,其实有点言过其实了。尽管Executor框架为制定和修改执行策略提供了相当大的灵活性,但是并非所有的任务都能适合所有的执行策略。有些类型的任务需要明确地指定一个执行策略,包括: 依赖性任务。多数运行良好的任务是独立的:它们不依赖于时序,或者其他任务的结果与边界效应。当线程池中运行的任务都是独立的时候,你就可以随意地改变池的长度和配置,这样做不会影响到性能以外的任何事情。另一方面,如果你提交给线程池的任务要依赖于其他的任务,你就隐式地给执行策略带来了约束,这样你必须仔细地管理执行策略以避免活跃度的问题。 采用线程限制的任务。单线程化的Executor相比于任意的线程池,可以对同步作出更强的承诺。它可以保证任务不会并发地执行,允许你放宽任务代码对线程安全的要求。可以把对象限制带任务线程中,这使得任务线程所执行的任务在访问该对象时,不需要同步,即使那些....

, ,

05.构建块

2016-04-29 20:06:51 huayonglun
0  评论    216  浏览

5.1 同步容器 同步容器类包括两部分: Vector和Hashtable,早期JDK的一部分 以上两个的同系容器,在JDK1.2中才被加入的同步包装(wrapper)类 这些类由Collections.synchronizedXXX工厂方法创建 这些类通过封装它们的状态,并对每一个公共方法进行同步而实现了线程安全,这样一次只有一个线程能访问容器的状态 5.1.1 同步容器出现的问题 同步容器都是线程安全的,但对于复合操作,可能需要使用额外的客户端加锁(client-side locking)进行保护。这些复合操作包括: 迭代(反复获取元素,直到获得容器中的最后一个元素) 导航(navigation,根据一定的顺序寻找下一个元素) 条件运算,比如"缺少即加入",检查Map中是否存在关键字K,如果没有,就加入mapping(K,V)。 操作Vector的复合操作可能导致混乱的结果 public static Object getLast(Vector list) { int lastIndex = list.size() - 1; return list.ge....

, ,

09.GUI应用程序

2016-05-03 18:40:37 huayonglun
0  评论    215  浏览

几乎所有的GUI工具集都实现为单线程化子系统(single-threaded),意味着所有GUI的活动都被限制在一个单独的线程中,这其中就包括了Swing和SWT。 9.1 为什么GUI是单线程化的 早期的GUI应用程序就是单线程化的,GUI在“主事件循环”进行处理。现代的GUI框架使用了一个略微不同的模型:模型创建了一个专门的线程,**事件派发线程(event dispatch thread,EDT)**来处理GUI事件。 9.1.1 顺序事件处理 任务依次处理,不会交迭 不利的一面:如果一个任务处理时间长,其他任务必须等。 9.1.2 Swing中的线程限制 所有的Swing组件(比如JButton和JTable)和数据模型(TableModel和TreeModel)都被限制于事件线程中,所以任何访问它们的代码必须在事件线程中运行。GUI对象不用同步,仅仅依靠线程限制来保持一致性。 Swing的单线程规则:Swing的组件和模型只能在事件分派线程中被创建、修改和请求。 //使用Executor实现的SwingUtillities public class Swi....

, ,

03.共享对象

2016-04-26 00:29:12 huayonglun
0  评论    221  浏览

  同步有两个重要方面:原子操作或者划定临界区;内存可见性。我们不仅希望能够避免一个线程修改其他线程正在使用的对象的状态,而且希望确保当一个线程修改了对象的状态后,其他线程能够真正的看到改变。但是没用同步,这些可能都不会发生。你可以使用显式的同步,或者利用内置于类库中的同步机制,来保证对象的安全发布。 3.1 可见性   在没有同步的情况下,编译器,处理器,运行时安排操作的执行顺序可能完全出人意料。在没有进行适当同步的多线程程序中,尝试推断那些“必然”发生在内存中的动作时,你总是判断错误。有一个简单的方法来避免这些复杂的问题:只要数据需要被跨线程共享,就进行恰当的同步 3.1.1 过期数据 没有恰当同步的程序,它能够引起意外的后果:过期数据 当读线程检查ready变量时,它可能看到一个过期的值 除非每一次访问变量都是同步的,否则很可能得到变量的过期值 更坏的情况是,过期既不会发生在全部变量上,也不会完全不出现:一个线程可能会得到一个变量最新的值,但是也可能得到另一个变量先前写入的过期值 过期数据的危害 引起安全错误,导致打印错误数据,或者程序无法终止 使对象引用中的数据更加复杂,比....

, ,

并发下锁的同步

2018-09-13 02:26:31 huayonglun
0  评论    211  浏览

前言 “锁”在我们日常的生活工作中经常会用到,比如离开寝室会锁房门,不用手机会将屏幕锁定,这充分保证了个人财产安全和隐私安全。同样,在程序的世界里,也有一把锁,保证程序不会崩溃,保证我们手机钱包里的钱不会无缘无故变多变少。 锁(lock)作为用于保护临界区(critical section)的一种机制,被广泛应用在多线程程序中。比如 Java 应用程序出现的 synchronized 关键字,就是锁实现的一种方式。下面我们从一个计数器的应用说起,分析下锁在 Java 程序中的应用。 一个计数器 非线程安全,value++ 不是原子性操作,状态不稳定,需要同步。 同步方法 锁定实例 线程安全,锁的对象是当前 SafeCounter 的实例,不会影响到另一个 SafeCounter 实例的 getNext() 方法。 锁定类 线程安全,锁的对象是 SafeCounterWithClass 类,任何实例都会受锁的影响,一般不这么用。 lock 线程安全,锁的对象是 lock,可以灵活控制,只锁真正需要同步的代码。注意 lock 不能是 null,也不能改变。 更好的 lock 线....

, , ,

02.线程安全

2016-04-24 19:39:07 huayonglun
0  评论    208  浏览

一些概念 定义: 要求无论是多线程中的时序或交替操作,都要保证不破坏哪些不变约束 编写线程安全的代码,本质上就是 管理对状态的访问,而且通常都是共享的、可变的状态 一个对象的状态: 就是它的数据,存储在状态变量中,比如实例域或静态域 共享 一个变量可以被多个线程访问 可变 变量的值在其生命周期内可以改变 无论何时,只要有多于一个线程访问给定的状态变量,而且其中的某个线程会写入该变量,此时必须使用同步来协调线程对该变量的访问 在没有正确同步的情况下,如果多个线程访问了同一个变量,你的程序就存在隐患。有三种方法修复它: 不要跨线程共享变量 使状态变量变为不可变的 在任何访问状态变量的时候使用同步 2.1 什么是线程安全性 一个类是线程安全的,是指在被多个线程访问时,类可以持续进行正确的行为 线程安全的类封装了任何必要的同步,因此客户不需要自己提供 无状态对象永远是线程安全的 多数Servlet都可以实现为无状态的,这一事实极大地降低了确保Servlet线程安全的负担,只有当Servlet要为不同的请求记录一些信息时,才会将线程安全的需求提到日程上....

, ,

10.避免活跃度危险

2016-05-04 20:00:00 huayonglun
0  评论    214  浏览

安全性和活跃度通常相互牵制。我们使用锁来保证线程安全,但是滥用锁可能引起锁顺序死锁(lock-ordering deadlock)。 我们使用线程池和信号量来约束资源的使用,但是却不能知晓那些管辖范围内的活动可能形成的资源死锁(resource deadlock)。 这一章将讲述一些引发活跃度失败的原因,以及避免发生这些失败的方法。 10.1 死锁 经典的“哲学家进餐问题”很好的解释了死锁。 当一个线程永远占有一个锁,而其他线程尝试去获得这个锁,那么它们将永远被阻塞。当线程A占有锁L时,想要获得锁M,但是同时,线程B持有M,并尝试获得L,两个线程将永远等待下去。这种情况是死锁最简单的形式(或称致命的拥抱,deadly embrace),发生在多个线程因为环路的锁依赖关系而永远等待的情况下。(把这些线程假想为有向图的节点,图的边表现了这个关系:线程A等待线程B占有的资源,如果图最后连成了一个环路,那么死锁也就产生了。) 数据库系统的设计就针对了监测死锁,以及从死锁中恢复。一个事务可能需要取得许多锁,并可能一直持有这些锁,直到所有事务提交。如此说来两个事务非常有可能发生死锁,但这却并不....

, ,

01.介绍

2016-04-24 19:37:21 huayonglun
0  评论    214  浏览

并发的历史 顺序编程模型 线程允许程序控制流的多重分支同时存在于一个进程中,共享进程内资源 线程有时候被成为轻量级进程,大多数现代操作系统时序调度的基本单元 线程的优点 降低开发和维护的开销,提高复杂应用的性能 把异步工作流转化为顺序流,使程序模拟人类工作和交互变得更加容易 把复杂、难以理解的代码转化为直接、简洁的代码,易读写及维护 在GUI应用中改进用户接口的响应性 在服务器应用中提高资源的利用率和吞吐量 简化JVM的实现,垃圾收集器通常运行于一个或多个持续工作的线程之间 大部分至关重要的Java应用都依赖于线程,某种程度上是因为它们的组织结构需要这样 线程的风险 安全危险 常见的并发危险 竞争条件(race condition) 活跃度的危险 线程的使用引入了又一形式的活跃度失败(liveness failure) 活跃度意味着好的事情终究会发生 当一个活动进入某种它永远无法再继续执行的状态时,活跃度失败发生 活跃度失败包括: 死锁(deadlock) 饥饿(starvation) 活锁(livelock) 性能危险 性能:希望好的事情尽快发生 性能问....

, ,

16.Java存储模型

2016-05-11 02:26:31 huayonglun
0  评论    216  浏览

高层的设计问题,比如安全发布、规约、以及遵守同步策略等等。它们的安全性得益于JMM,当你理解了这些机制会如此工作后,能发现可以更容易有效地使用它们。 16.1 什么是存储模型,要它何用 假设一个线程为变量a赋值:a = 3; 存储模型要回答这个问题:“在什么条件下,读取a的线程会看到3这个值?” 编译器生成指令的次序,可以不同于源代码所暗示的“显然”的版本,而且编译器还会把变量存储在寄存器,而不是内存中;处理器可以乱序或者并行地执行指令;缓存会改变写入提交到主内存的变量的次序;最后,存储在处理器本地缓存中的值,对于其他处理器并不可见。这些因素都会妨碍一个线程看到一个变量的最新值,而且会引起内存活动在不同的线程中表现出不同的发生次序——如果你没有适当同步的话。 Java语言规范规定了JVM要维护内部线程类似顺序化语意:只要程序的最终结果等同于它在严格的顺序化环境中执行的结果,那么上述所有行为是允许的。 最近几年,重新排序后的指令使得程序在计算性能上得到了很大的提升。对性能提升作出贡献的,除了越来越高的时钟频率,还有不断提升的并行性——管道超标量体系结构执行单元,动态指令调度,试探性执....

, ,

07.取消和关闭

2016-05-01 23:16:49 huayonglun
0  评论    210  浏览

启动任务和线程都很容易。大多数时候,我们通常允许它们在结束任务后自行停止。但是,有时候我们希望任务或线程自然结束之前就停止它们,可能因为用户取消了操作,或者应用程序需要快速关闭。 要做到安全、快速、可靠的停止任务或线程并不容易。Java没有提供任何机制,来安全地强迫线程停止手头的工作。它提供中断——一个协作机制,使一个线程能够要求另一个线程停止当前的工作。 这种协作方法是必须的,因为我们很少需要一个任务、线程或者服务立即停止,立即停止会导致共享的数据结构处于不一致的状态。任务和服务可以这样编码:当要求它们停止时,它们首先会清除当前进程中的工作,然后再终止。这提供了更好的灵活性,因为任务代码本身比发出取消请求的代码更明确应该清除什么。 7.1 任务取消 当外部代码能够在活动自然完成之前,把它更改为完成状态,那么这个活动被称为可取消的(cancellable)。我们可能会因为很多原因取消一个活动: 用户请求的取消。用户点击程序界面上的“cancel”按钮,或者通过管理接口请求取消,比如JMX(Java Management Extensions)。 **限时活动。**一个应用程序,需....

, ,

06.任务执行

2016-04-30 02:39:53 huayonglun
0  评论    204  浏览

大多数并发应用程序是围绕执行**任务(task)**进行管理的。所谓任务就是抽象、离散的工作单元(unit of work)。把一个应用程序的工作分离到任务中,可以简化程序的管理; 这种分离还在不同事务间划分了自然的分界线,可以方便程序在出现错误时进行恢复; 同时这种分离还可以为并行工作提供一个自然的结构,有利于提高程序的并发性。 6.1 在线程中执行任务 围绕执行任务来管理应用程序时 第一步要指明一个清晰的任务边界(task boundaries) 理想情况下,任务是独立的活动:它的工作并不依赖于其他任务的状态。结果或者边界效应(side effect)。 独立有利于并发性,如果能得到相应的处理器资源,独立的任务还可以并行执行。 为了使调度与负载均衡并具有更好的灵活性,每项任务只占用处理器的一小部分资源。 在正常的负载下,服务器应用程序应该兼具良好的吞吐量和快速的响应性。 应用程序应该在负荷过载时平缓地劣化,而不应该负载一高就简单地以失败告终。为了达到这些目的,你要选择一个清晰的任务边界,并配合一个明确的任务执行策略 大多数服务器应用程序都选择了下面这个自然的任务边界: ....

, ,

12.测试并发程序

2016-05-07 02:26:31 huayonglun
0  评论    207  浏览

并发类的测试基本分为两类: 安全性测试:什么坏事都没有发生过 活跃性测试:好的事情终究会发生 与活跃度测试相关的是性能测试。性能可以通过很多方式来测量,其中包括: 吞吐量:在一个并发任务集里,已完成任务所占的比例; 响应性:从请求到完成一些动作之间的延迟(也被称作等待时间); 可伸缩性:增加更多的资源(通常是指CPU),就能提高(或者缓解短缺)吞吐量。 12.1 测试正确性 SemaphoreBoundedBuffer实现一个基于数组的定长队列 put和take方法可阻塞并受控于一对计数信号量 availableItems代表可以从缓存中删除的元素的个数,初始值为0 availableSpaces代表可以插入到缓存的元素的个数,初始值为缓存的大小 take操作首先请求一个许可(permit),这个许可从availableItems中获得。 如果缓存不为空,请求立即成功,否则请求会被阻塞,直到缓存不空为止。 一旦获得了许可,take会删除缓存中的下一个元素,同时还释放一个许可给availableSpaces信号量。 //利用Semaphore实现的有限缓存 @Th....

,

part_1

2016-04-29 20:12:48 huayonglun
0  评论    210  浏览

主要概念和规则 可变状态   所有并发问题都归结为如何协调和访问并发状态,可变状态越少,保证线程安全月容易 尽量将域声明为final类型,除非它们的需要是可变的 不可变对象天生是线程安全的。 不可变对象极大地减轻了并发编程的压力。它们简单而且安全,可以在没有锁或者防御性复制的情况下自由地共享。 封装使管理复杂度变得更可行。 你固然可以存储于全局变量的数据来写一个线程安全类。但是你为什么要这样做? 在对象中封装数据,让他们能够更加容易的保持不变; 在对象中封装同步,使它能够更容易地遵守同步策略 用锁来守护每一个可变变量 对同一不变约束中的所有变量都使用相同的锁 在运行复合操作期间持有锁 在非同步的多线程情况下,访问可变变量的程序是存在隐患的。 不要依赖于可以需要同步的小聪明 在设计过程中就考虑线程安全。或者在文档中明确地说明它不是线程安全的。 文档化你的同步策略 欢迎关注微信公众号,技术,思维,心理,带给你认知的全方位成长。 你的关注,就是对我最大的肯定,我会努力产出的,我们一起成长~ 本文由 永伦的小屋 原创。 转载请注明作者及出处,本文作者为 永伦的小屋。

,

04.组合对象

2016-04-27 02:11:13 huayonglun
0  评论    212  浏览

4.1 设计线程安全的类 设计线程类的过程应该包括下面3个基本要素: 确定对象状态是由哪些变量构成的; 确定限制状态变量的不变约束; 制定一个管理并发访问对象状态的策略 对象的状态首先要从它的域说起。 如果对象的域都是基本类型的,那么这些域就组成了对象的完整状态 一个对象有n个基本域,它的状态就是域值组成的n元组(n-tuple) 如果一个对象的域引用了其他对象,那么它的状态也同时包含了被引用对象的域。比如,LinkedList的状态包括了所有存储在链表中的节点对象的状态 同步策略(synchronization policy)定义了对象如何协调对其状态的访问,并且不会违反它的不变约束或后验条件 它规定了如何把不可变性、线程限制和锁结合起来,从而维护线程的安全性,还指明了哪些锁保护哪些变量。 为了保证开发者与维护者可以分析并维护类,应该将类的同步策略写入文档 4.1.1 收集同步需求 不变约束——状态空间(state space) 对象与变量可能处于的状态的范围 状态空间越小,越容易判断它们 尽量使用final类型的域,可以简化我们对对象的可能状态进行分析 不....

, ,
TOP