欢迎来看我的网络日志,交流请加我好友: 919201148。欢迎关注公众号或视频号:蜗牛互联网
Solo  当前访客:3 开始使用

蜗牛学编程

微信搜索「蜗牛互联网」,回复 1024 有惊喜!!! 程序员 | 互联网 | 科技 | Java | 架构 | 职场进阶 | 财富进阶

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....

,

11.性能和可伸缩性

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

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

, ,

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占有的资源,如果图最后连成了一个环路,那么死锁也就产生了。) 数据库系统的设计就针对了监测死锁,以及从死锁中恢复。一个事务可能需要取得许多锁,并可能一直持有这些锁,直到所有事务提交。如此说来两个事务非常有可能发生死锁,但这却并不....

, ,

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....

, ,

08.应用线程池

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

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

, ,

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)。 独立有利于并发性,如果能得到相应的处理器资源,独立的任务还可以并行执行。 为了使调度与负载均衡并具有更好的灵活性,每项任务只占用处理器的一小部分资源。 在正常的负载下,服务器应用程序应该兼具良好的吞吐量和快速的响应性。 应用程序应该在负荷过载时平缓地劣化,而不应该负载一高就简单地以失败告终。为了达到这些目的,你要选择一个清晰的任务边界,并配合一个明确的任务执行策略 大多数服务器应用程序都选择了下面这个自然的任务边界: ....

, ,

part_1

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

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

,

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....

, ,

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类型的域,可以简化我们对对象的可能状态进行分析 不....

, ,

03.共享对象

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

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

, ,

02.线程安全

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

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

, ,

01.介绍

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

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

, ,

剑指Offer之三十--最小的K个数

2016-04-19 04:01:13 huayonglun
0  评论    225  浏览

题目描述 输入n个整数,找出其中最小的K个数。 例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4。 解法1 对数组排序,取前边k个数存到list集合中 该解法时间复杂度依赖于排序 代码实现 public ArrayList<Integer> getLeastNumbers(int [] input, int k) { ArrayList<Integer> list = new ArrayList<Integer>(); if(input == null || input.length == 0 || input.length < k || k <= 0){ return list; } Arrays.sort(input); for(int i = 0; i < k; i++){ list.add(input[i]); } return list; } 解法2 基于partition函数 只有当我们可以修改输入的数组时可用 代码实现 public ArrayList<Intege....

, , ,

剑指Offer之二十九--数组中出现次数超过一半的数字

2016-04-18 21:20:35 huayonglun
0  评论    217  浏览

题目描述 数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。 例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。 由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。 如果不存在则输出0。 解法1 使用map,键存元素,值存出现的次数 发现有某个元素的次数超过数组长度的一半,则返回该元素 代码实现 public int moreThanHalfNum(int [] array) { if(array == null || array.length == 0){ return 0; } int flag = 0; Map<Integer,Integer> map = new HashMap<Integer,Integer>(); int len = array.length; for(int i = 0; i < len; i++){ if(map.containsKey(array[i])){ map.put(array[i], map.get(array[i]) + 1); }else{ map.put(ar....

, ,

剑指Offer之二十八--字符串的排列

2016-04-18 19:13:05 huayonglun
0  评论    212  浏览

题目描述 输入一个字符串,按字典序打印出该字符串中字符的所有排列。 例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。 结果请按字母顺序输出。 输入描述: 输入一个字符串,长度不超过9(可能有字符重复),字符只包括大小写字母。 思路: 先不考虑是否出现重读字符,要对一个字符进行全排列,可以把第一个字符和后面的字符看成两部分 而第一个字符后面的字符又可以看成第一个字符和后面两部分,是一个递归过程 只要第一个字符的位置没有到达字符串的末尾就分别将第一个字符与后面的字符进行交换 注意: 第一个字符与后面的某个位置的字符发生交换后,需要再次发生交换,不然顺序就会被打乱 举个例子,在字符串abc中,在把第一个字符看成是a,后面的字符b,c,看成是一个整体的时候,abc这个相对的顺序不能改变 所以当a与b发生交换变成bac之后,需要再次交换两个字符,重新回到abc 代码实现 public ArrayList<String> permutation(String str) { ArrayList<S....

, ,

剑指Offer之二十七--二叉搜索树与双向链表

2016-04-18 18:48:28 huayonglun
0  评论    216  浏览

二叉树结构 class TreeNode { int val = 0; TreeNode left = null; TreeNode right = null; public TreeNode(int val) { this.val = val; } } 题目描述 输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。 要求不能创建任何新的结点,只能调整树中结点指针的指向。 思路: 由于二叉搜索树的中序遍历就是排序的,如果是构造单链表,只需要一次中序遍历就可以了 但现在需要构造双链表,也就是在中序遍历的过程中需要设置每个结点的left与right指针,现在问题是如何设置这两个指针 二叉搜索树有一个特点,就是根结点的左子树上所有结点都比根结点的值小,而右子树上的所有结点的值都比根结点的值大 利用这个性质,当遍历根结点的左孩子的时候,可以继续把其当做左子树的根结点,右孩子可以当做右子树的根结点,从而使用递归完成 步骤 以左子树为例,依次访问结点的左孩子,当遍历到叶子节点的时候,递归结束,并把该叶子结点设为左子树转换的双链表的第一个结点 然后把其父结点链在其右边,设置lef....

, , ,

有理数四则运算

2016-04-12 21:41:05 huayonglun
0  评论    213  浏览

题目描述 本题要求编写程序,计算2个有理数的和、差、积、商。 输入描述: 输入在一行中按照“a1/b1 a2/b2”的格式给出两个分数形式的有理数, 其中分子和分母全是整型范围内的整数,负号只可能出现在分子前,分母不为0。 输出描述: 分别在4行中按照“有理数1 运算符 有理数2 = 结果”的格式顺序输出2个有理数的和、差、积、商。 注意输出的每个有理数必须是该有理数的最简形式“k a/b”,其中k是整数部分,a/b是最简分数部分; 若为负数,则须加括号; 若除法分母为0,则输出“Inf”。 题目保证正确的输出中没有超过整型范围的整数。 输入例子: 5/3 0/6 2/3 -4/2 输出例子: 1 2/3 + 0 = 1 2/3 1 2/3 - 0 = 1 2/3 1 2/3 * 0 = 0 1 2/3 / 0 = Inf 2/3 + (-2) = (-1 1/3) 2/3 - (-2) = 2 2/3 2/3 * (-2) = (-1 1/3) 2/3 / (-2) = (-1/3) 思路 分解分数 将分数形式的字符串,分解成整数+最简真分数形式的字符串 整数....

, , ,

剑指Offer之二十六--复杂链表的复制

2016-04-07 22:21:14 huayonglun
0  评论    208  浏览

题目描述 输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点)。 复制一个复杂链表 结点定义 class RandomListNode { int label; RandomListNode next = null; RandomListNode random = null; public RandomListNode(int label){ this.label = label; } } 解析 遍历链表,每个结点后边复制相同的结点,设置next指针 复制结点的特殊指针。如果原始链表上的结点N的random指向S,则它对应的复制结点N的random指向S的下一个结点S 抽取复制结点,连起来得到复制链表 代码实现 public RandomListNode clone(RandomListNode pHead){ cloneNodes(pHead); connectRandomNodes(pHead); return reconnectNodes(pHead); } //复制原始链表的任意结点N并创建新结点N,再把N链....

, ,

剑指Offer之二十五--二叉树中和为某一值的所有路径

2016-04-07 21:10:27 huayonglun
0  评论    214  浏览

题目描述 输入一颗二叉树和一个整数,打印出二叉树中结点值的和为输入整数的所有路径。 路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。 解析 先序遍历,将遍历过的结点放到A集合中 当遍历到叶子结点并且和恰好是目标值时,将遍历经过的所有结点放到B集合中,B则是满足题意的一条路径 如果遍历到叶子结点和仍然不等于目标值,那么就移除A集合中添加的结点,修改和,切换到右孩子结点重新计算 如果没有遍历到叶子结点就从孩子结点中继续寻找这样的路径 二叉树定义 class TreeNode { int val = 0; TreeNode left = null; TreeNode right = null; public TreeNode(int val) { this.val = val; } } 代码实现 public ArrayList<ArrayList<Integer>> findPath(TreeNode root,int target) { /* * currentSum 记录当前和 * pathNodes 保存当前路径扫描过的结点 * ....

, , ,
TOP