- 浏览: 98769 次
- 性别:
- 来自: 北京
文章分类
最新评论
<java并发编程实践>(1~5)读书笔记
- 博客分类:
- 线程相关
第一章:介绍
进程的资源比如内存,文件句柄,安全证书,由操作系统分配。进程通过Socket,信号处理,共享内存,信号量通信。
线程共享进程的资源,每个线程有自己的程序计数器,栈(stack)和本地变量。
第二章:线程安全
编写正确的并发程序的关键在于对共享的,可变的状态进行访问管理
synchronized,一方面保证操作的原子性,一方面保证操作的可见性。
耗时的计算或操作,比如网络或控制台I/O,难以快速完成,执行这些操作期间不要占有锁。
第三章:共享对象
在没有同步的情况下,编译器,处理器,运行时安排操作的执行顺序可能完全出人意料。在没有进行适当同步的多线程程序中,尝试推断那些“必然”发生在内存中的动作时,你总是会判断错误。
锁可以保证可见性和原子性,而volatile变量只能保证可见性。
不安全的发布对象,导致this溢出。
public class ThisEscape { public ThisEscape(EventSource source) { source.registerListener( new EventListener() { public void onEvent(Event e) { doSomething(e); } }); } }
|
第四章 组合对象
设计线程安全的类的过程包括三个基本要素:
1)确定对象状态是由哪些变量构成的。
2)确定限制状态变量的不变约束。
3)制定一个管理并发访问对象状态的策略。
如果一个类由多个彼此独立的线程安全状态变量组成,并且类的操作不包含任何无效状态转换时,可以将线程安全委托给这些状态变量。
线程安全
public class VisualComponent { private final List<KeyListener> keyListeners = new CopyOnWriteArrayList<KeyListener>(); private final List<MouseListener> mouseListeners = new CopyOnWriteArrayList<MouseListener>(); public void addKeyListener(KeyListener listener) { keyListeners.add(listener); } public void addMouseListener(MouseListener listener) { mouseListeners.add(listener); } public void removeKeyListener(KeyListener listener) { keyListeners.remove(listener); } public void removeMouseListener(MouseListener listener) { mouseListeners.remove(listener); } }
|
非线程安全
public class NumberRange { // INVARIANT: lower <= upper private final AtomicInteger lower = new AtomicInteger(0); private final AtomicInteger upper = new AtomicInteger(0); public void setLower(int i) { // Warning -- unsafe check-then-act if (i > upper.get()) throw new IllegalArgumentException( "can't set lower to " + i + " > upper"); lower.set(i); } public void setUpper(int i) { // Warning -- unsafe check-then-act if (i < lower.get()) throw new IllegalArgumentException( "can't set upper to " + i + " < lower"); upper.set(i); } public boolean isInRange(int i) { return (i >= lower.get() && i <= upper.get()); } }
|
前五章是并发编程的理论基础,我读的很痛苦
在第五章的最后介绍了几种JDK新加的synchronizer。
1)阻塞队列
2)闭锁,latch:他可以延迟线程的进度直到线程到达终止(terminal)状态。一个闭锁工作起来就像一道大门:直到闭锁达到终点状态之前,门一直是关闭的,没有线程能够通过,在终点状态到来的时候,门开了,允许所有线程都通过。一旦闭锁到达了终点状态,他就不能够再改变状态了,所以他会永远保持敞开状态,闭锁可以用来确保特定活动直到其他的活动完成后才发生,比如:
1、确保一个计算不会执行,直到他需要的资源被初始化。
2、确保一个服务不会开始,直到他依赖的其他服务都已经开始。
3、等待,直到活动的所有部分都为继续处理做好充分准备。
场景:一个服务中包含一个相关的两元闭锁,开启服务S会首先开始等待闭锁S中所依赖的其他服务,在启动结束后,会释放闭锁S,这样所依赖S的服务也开始处理了。
实例:CountDownLatch,FutureTask
java.util.concurrent.CountDownLatch它是一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。用给定的计数初始化 CountDownLatch。在调用countDown() 方法,使当前计数减一,且当前计数到达零之前,await 方法会一直受阻塞。当前计数到达零之后,会释放所有等待的线程,await 的所有后续调用都将立即返回。这种现象只出现一次——计数无法被重置。如果需要重置计数,请考虑使用 CyclicBarrier。 CountDownLatch 是一个通用同步工具,它有很多用途。将计数 1 初始化的 CountDownLatch 用作一个简单的开/关锁存器,或入口:在通过调用 countDown() 的线程打开入口前,所有调用 await 的线程都一直在入口处等待。用 N 初始化的 CountDownLatch 可以使一个线程在 N 个线程完成某项操作之前一直等待,或者使其在某项操作完成 N 次之前一直等待。 CountDownLatch的一个有用特性是,它不要求调用 countDown 方法的线程等到计数到达零时才继续,调用countDown 方法的线程并不会阻塞。CountDownLatch调用await方法将阻塞当前线程,直到其他线程调用countDown 方法,使其计数到达零时才继续。 CountDownLatch主要有以下三种用法 用法1:将计数1初始化的 CountDownLatch 用作一个简单的开/关锁存器,或入口:在通过调用 countDown() 的线程打开入口前,所有调用 await 的线程都一直在入口处等待。 用法2:用N初始化的 CountDownLatch 可以使一个线程在 N 个线程完成某项操作之前一直等待,或者使其在某项操作完成 N 次之前一直等待。 用法3:它不要求调用 countDown 方法的线程等到计数到达零时才继续,而在所有线程都能通过之前,它只是阻止任何线程继续通过一个await。注意2:用法3其实对用法1和用法2的混合使用。它的实质是一个线程来阻止其他线程通过(用法1), 但是该线程要完成N个操作(用法2),才让其他线程通过。注意3:理解的CountDownLatch含义,可以灵活的使用它,而不用拘于上述三种形式。 注意4:计数无法被重置。如果需要重置计数,请考虑使用 CyclicBarrier。 示例1 startSignal是一个启动信号,在 driver 为继续执行 worker 做好准备之前,它会阻止所有的 worker 继续执行。 doneSignal是一个完成信号,它允许 driver 在完成所有 worker 之前一直等待。 class Driver { // ... void main() throws InterruptedException { CountDownLatch startSignal = new CountDownLatch(1); CountDownLatch doneSignal = new CountDownLatch(N); for (int i = 0; i < N; ++i) // create and start threads new Thread(new Worker(startSignal, doneSignal)).start(); doSomethingElse(); // don't let run yet startSignal.countDown(); // let all threads proceed doSomethingElse(); doneSignal.await(); // wait for all to finish } } class Worker implements Runnable { private final CountDownLatch startSignal; private final CountDownLatch doneSignal; Worker(CountDownLatch startSignal, CountDownLatch doneSignal) { this.startSignal = startSignal; this.doneSignal = doneSignal; } public void run() { try { startSignal.await(); doWork(); doneSignal.countDown(); } catch (InterruptedException ex) {} // return; } void doWork() { ... } } 另一种典型用法是,将一个问题分成N个部分,用执行每个部分并让锁存器倒计数的Runnable来描述每个部分,然后将所有 Runnable 加入到 Executor 队列。当所有的子部分完成后,协调线程就能够通过 await。(当线程必须用这种方法反复倒计数时,可改为使用 CyclicBarrier。) class Driver2 { // ... void main() throws InterruptedException { CountDownLatch doneSignal = new CountDownLatch(N); Executor e = ... for (int i = 0; i < N; ++i) // create and start threads e.execute(new WorkerRunnable(doneSignal, i)); doneSignal.await(); // wait for all to finish } } class WorkerRunnable implements Runnable { private final CountDownLatch doneSignal; private final int i; WorkerRunnable(CountDownLatch doneSignal, int i) { this.doneSignal = doneSignal; this.i = i; } public void run() { try { doWork(i); doneSignal.countDown(); } catch (InterruptedException ex) {} // return; } void doWork() { ... } } 内存一致性效果:线程中调用 countDown() 之前的操作 happen-before 紧跟在从另一个线程中对应 await() 成功返回的操作。主要函数 public CountDownLatch(int count) 构造一个用给定计数初始化的 CountDownLatch。 参数: count - 在线程能通过 await() 之前,必须调用 countDown() 的次数 抛出: IllegalArgumentException - 如果 count 为负 public void await() throws InterruptedException 使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断。 如果当前计数为零,则此方法立即返回。 如果当前计数大于零,则出于线程调度目的,将禁用当前线程,且在发生以下两种情况之一前,该线程将一直处于休眠状态: * 由于调用 countDown() 方法,计数到达零;或者 * 其他某个线程中断当前线程。 如果当前线程: * 在进入此方法时已经设置了该线程的中断状态;或者 * 在等待时被中断, 则抛出 InterruptedException,并且清除当前线程的已中断状态。 抛出: InterruptedException - 如果当前线程在等待时被中断注意:它会抛出InterruptedException,关于Interrupted,请参阅《JAVA线程的interrupt》 public boolean await(long timeout,TimeUnit unit) throws InterruptedException 使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断或超出了指定的等待时间。 如果当前计数为零,则此方法立刻返回 true 值。 如果当前计数大于零,则出于线程调度目的,将禁用当前线程,且在发生以下三种情况之一前,该线程将一直处于休眠状态: * 由于调用 countDown() 方法,计数到达零;或者 * 其他某个线程中断当前线程;或者 * 已超出指定的等待时间。 如果计数到达零,则该方法返回 true 值。 如果当前线程: * 在进入此方法时已经设置了该线程的中断状态;或者 * 在等待时被中断, 则抛出 InterruptedException,并且清除当前线程的已中断状态。 如果超出了指定的等待时间,则返回值为 false。如果该时间小于等于零,则此方法根本不会等待。 参数: timeout - 要等待的最长时间 unit - timeout 参数的时间单位。 返回: 如果计数到达零,则返回 true;如果在计数到达零之前超过了等待时间,则返回 false 抛出: InterruptedException - 如果当前线程在等待时被中断 public void countDown() 递减锁存器的计数,如果计数到达零,则释放所有等待的线程。 如果当前计数大于零,则将计数减少。如果新的计数为零,出于线程调度目的,将重新启用所有的等待线程。 如果当前计数等于零,则不发生任何操作。 public long getCount() 返回当前计数。 此方法通常用于调试和测试。 返回: 当前计数
3)信号量 (计数信号量 Counting semaphore)用来控制能够同时访问某特定资源的活动的数量,或者同时执行某一给定操作的数量。计数信号量可以用来实现资源池或者给一个容器限定边界。
一个Semaphore管理一个有效的许可集;许可的初始量通过构造函数传递给Semaphore。活动能够获得许可,并在使用之后释放许可。
简单来说,信号量维护了一个许可集。通过acquire()来获得一个许可,如果有许可可用,就返回,否则阻塞直到有许可可用。通过release() 来释放一个许可。
import java.util.concurrent.Semaphore; public class SemaphoreDemo { public static void main(String[] args) { Pool sem = new Pool(); try { Object o1 = sem.getItem(); Object o2 = sem.getItem(); Object o3 = sem.getItem(); System.out.println("availablePermits:" + sem.availablePermits() + " before reduce"); sem.putItem(o1); System.out.println("availablePermits:" + sem.availablePermits() + " after reduce"); sem.putItem(o2); System.out.println("availablePermits:" + sem.availablePermits() + " after realese two"); sem.putItem(o3); System.out.println("availablePermits:" + sem.availablePermits() + " after realese another one"); } catch (InterruptedException e) { } } } class Pool { private static final int MAX_AVAILABLE = 10; private final Semaphore available = new Semaphore(MAX_AVAILABLE, true); public Pool() { for (int i = 0; i < MAX_AVAILABLE; i++) { items[i] = new String("" + i); } } public Object getItem() throws InterruptedException { available.acquire(); return getNextAvailableItem(); } public void putItem(Object x) { if (markAsUnused(x)) { available.release(); } } public int availablePermits() { return available.availablePermits(); } // Not a particularly efficient data structure; just for demo // whatever kinds of items being managed protected Object[] items = new String[MAX_AVAILABLE]; protected boolean[] used = new boolean[MAX_AVAILABLE]; protected synchronized Object getNextAvailableItem() { for (int i = 0; i < MAX_AVAILABLE; ++i) { if (!used[i]) { used[i] = true; return items[i]; } } return null; // not reached } protected synchronized boolean markAsUnused(Object item) { for (int i = 0; i < MAX_AVAILABLE; ++i) { if (item == items[i]) { if (used[i]) { used[i] = false; return true; } else { return false; } } } return false; } }
4)关卡 Barrier 类似于闭锁,他们都能够阻塞一组线程,直到某些事件发生。其中关卡和闭锁关键的不同在于,所有线程必须同时到达关卡点,才能继续处理。闭锁等待的是事件;关卡等待的是其他线程。关卡实现的协议,就像一些家庭成员指定商场中的集合地点:“我们6:00在麦当劳见,不见不散,之后我们再决定接下来做什么”。
public class CyclicBarrierDemo { public static void main(String[] args) { int number = 50; CyclicBarrier barrier = new CyclicBarrier(number); try { for (int i = 0; i < number; i++) { new CyclicBarrierDemoThread("thread" + i, barrier).start(); Thread.sleep(100); } } catch (InterruptedException e) { e.printStackTrace(); } try { for (int i = 0; i < number; i++) { new CyclicBarrierDemoThread("【new】" + i, barrier).start(); Thread.sleep(100); } } catch (InterruptedException e) { e.printStackTrace(); } } } class CyclicBarrierDemoThread extends Thread { private CyclicBarrier barrier; CyclicBarrierDemoThread(String name, CyclicBarrier barrier) { super(name); this.barrier = barrier; } public void run() { try { System.out.println(getName() + " has arrived"); int val = barrier.await(); System.out.println(getName() + " has crossd the barrier " + val); } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } } }
CyclicBarrier 和 CountDownLatch 的主要异同:
两者在构造的时候都必须指定线程数量,而且该数量在构造后不可修改。
前者可以传入一个 Runnable 对象,在任务完成后自动调用,执行者为某个子线程;后者可在 await 方法后手动执行一段代码实现相同的功能,但执行者为主线程。
前者在每个子线程上都进行阻塞,然后一起放行;后者仅在主线程上阻塞一次。
前者可以重复使用;后者的倒计数器归零后就作废了。
两者的内部实现完全不同。
发表评论
-
多线程开发基础问答
2012-05-04 00:33 6211.为什么及什么时候要使用多线程? 在多核时代,我们要充分 ... -
线程状态总结
2012-04-14 09:46 746转自:http://kyfxbl.iteye.co ... -
<java并发编程实践>第六章读书笔记
2012-02-08 22:00 868大多数并发应用程序是 ... -
Timer的混乱行为
2012-02-02 12:51 667import java.util.Timer; import ... -
<转载>多线程编程 基础篇 (二)
2011-11-25 22:45 690基础篇(二)在进入java平台的线程对象之前,基于基础知识(一 ... -
java线程基础
2011-10-28 13:02 689wait 线程在对象上执行wait方法时,释放对对象的锁定,并 ...
相关推荐
java并发编程实践笔记java并发编程实践笔记java并发编程实践笔记java并发编程实践笔记
java并发编程实战pdf学习笔记 总结了重要的知识点
java并发编程实践笔记资料.pdf
《Java并发编程实践》一书的个人读书笔记。主要列举包括各个章节的关键知识点,便于反复阅读和知识复习掌握。
的管理,这种分离还在不同事务间划分了自然的分界线,在程序出现错误时可以很方便地进行恢复,还有利于提高程序的并发性。围绕任务执行来管理应用程序时,第一步要指明一个清晰的任务边界,理想情况下,任务是 独立...
线程安全就是对共享的、可变的状态进行管理,对象的状态就是它的数据,换句话说就是在不可控制的并发访问中保护数据。
当调用 start 启动线程时 Java 虚拟机会调 用该类的 run方法。 那么该类的 run() 方法中就是调用了 Runnable 对象的 run() 方法。 我 们可以继承重写Thread 类,在其 start 方法中添加不断循环调用传递过来的 ...
Java是最先支持多线程的开发的语言之一,Java从一开始就支持了多线程能力,因此Java开发者能常遇到上面描述的...这也是我想为Java并发技术而写这篇系列的原因。作为对自己的笔记,和对其他Java开发的追随者都可获益的。
使用java.util.concurrent类库构造安全的并发应用程序的基础。共享其实就是某一线程的数据改变对其它线程可见,否则就会出现脏数据。
在实践中,委托是创建线程安全类最有效的策略之一:用已有的线程安全类来管理所 有状态即可。
java中没有提供任何机制,来安全是强迫线程停止手头的工作,Thread.stop和 Thread.suspend方法存在严重的缺陷,不能使用。程序不应该立即停止,应该采用中断这种协作机制来处理,正确的做法是:先清除当前进程中的...
Java 并发学习笔记: 进程和线程, 并发理论, 并发关键字, Lock 体系, 原子操作类, 发容器 & 并发工具, 线程池, 并发实践 Java是一种面向对象的编程语言,由Sun Microsystems于1995年推出。它是一种跨平台的...
Linux面试专题及答案+ActiveMQ消息中间件面试专题+Java基础面试题+MySQL性能优化的21个最佳实践+微服务面试专题及答案+深入理解java虚拟机+设计模式面试专题及答案+开源框架面试专题及答案+并发编程及答案+Spring...
Java语言从第一版本开始内置了对多线程的支持,这一点在当年是非常了不起的,但是当我们对并发编程有了更深刻的认识和更多的实践后,实现并发编程就有了更多的方案和更好的选择。本文是对并发编程的核心理论做了下小...
Java并发编程.pdf JAVA核心知识点整理.pdf Java高级架构知识点整理.pdf Java高级架构面试知识点整理.pdf JVM与性能优化知识点整理.pdf MySQL性能调优与架构设计解析文档.pdf Nginx入门到实战.pdf springCloud笔记....
学习线程介绍Java多线程学习PDF格式Java并发编程的艺术.pdf JAVA并发编程实践.pdf图解Java多线程设计模式-第2版.pdf代码code1是《 Java并发编程的艺术》的源码ThreadDesignPatterns是《图解Java多线程设计模式》第1...
Java并发体系知识导图笔记.xmind JVM和性能优化.xmind JVM面试专题及答案.pdf kafka知识导图笔记.xmind Kafka面试专题及答案.pdf Linux面试专题及答案.pdf memcached面试专题及答案.pdf MongoDB面试专题及答案.pdf ...
2022java面试题、JVM面试题、多线程面试题、并发编程、Redis面试题、MySQL面试题、Java2022面试题、Netty面试题 一、内容概览 本次分享的资源涵盖了Java面试的各个方面,从基础知识到高级技术,从数据库到框架应用...
│ │ 9.JAVA并发编程之多线程并发同步业务场景与解决方案.wmv │ │ │ ├─10.微服务架构之Spring Cloud Eureka 场景分析与实战 │ │ 10.微服务架构之Spring Cloud Eureka 场景分析与实战.wmv │ │ │ ├─11....