`

<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 方法后手动执行一段代码实现相同的功能,但执行者为主线程。
前者在每个子线程上都进行阻塞,然后一起放行;后者仅在主线程上阻塞一次。
前者可以重复使用;后者的倒计数器归零后就作废了。
两者的内部实现完全不同。

分享到:
评论

相关推荐

    java并发编程实践pdf笔记

    Java并发编程实践是Java开发中不可或缺的一个领域,它涉及到如何高效、正确地处理多线程环境中的任务。这本书的读书笔记涵盖了多个关键知识点,旨在帮助读者深入理解Java并发编程的核心概念。 1. **线程和进程的...

    <<JAVA学习笔记>>实例源代码

    本压缩包中包含的"Example"文件夹,极有可能是《JAVA学习笔记》一书中的实例源代码,旨在帮助读者深入理解书中讲解的Java编程原理和实践技巧。下面我们将对这些源代码进行详细解读,以便更好地掌握Java编程。 1. **...

    java并发编程实践笔记

    ### Java并发编程实践笔记知识点详解 #### 一、保证线程安全的方法 1. **不要跨线程访问共享变量:** 当多个线程共享某个变量时,若其中一个线程修改了该变量,其他线程若没有正确同步,则可能读取到错误的数据。...

    java并发编程实践笔记资料.pdf

    Java并发编程实践笔记 Java并发编程实践笔记是一份关于Java并发编程的实践笔记,涵盖了多种关于线程安全、并发编程的实践经验和原则。下面是从笔记中总结的知识点: 1. 保证线程安全的三种方法:不要跨线程访问...

    Java并发编程学习笔记.rar

    这本"Java并发编程学习笔记"可能是作者在深入研究Java并发特性、工具和最佳实践过程中积累的心得体会。下面,我们将根据这个主题,探讨一些关键的Java并发编程知识点。 1. **线程与进程**:在多任务环境中,线程是...

    Java并发编程笔记

    ### Java并发编程知识点详解 #### 一、线程状态与管理 在Java中,线程具有多种状态,这些状态的变化反映了线程在其生命周期中的不同阶段。理解这些状态及其转换对于编写高效、健壮的并发程序至关重要。 - **NEW**...

    读书笔记-Java并发编程实战-基础篇

    在Java并发编程中,数据的封装与访问控制、线程安全性的考量、同步机制的使用是重要的基础概念和技巧。以下是从给出的文件内容中提取出的详细知识点: 1. 数据封装与访问控制:确保内部私有数据不被轻易访问,并且...

    [原]Java并发编程实践-读书笔记

    《Java并发编程实践》这本书是Java开发者深入理解并发编程的重要参考。以下是对书中关键知识点的总结: 1. **线程和进程的区别** - **线程**:是程序执行的最小单位,一个进程中可以有多个线程,它们共享同一块...

    JAVA并发编程实践-线程执行-学习笔记

    总的来说,Java并发编程实践中的任务执行是一个涉及线程调度、线程池管理、任务生命周期控制、结果处理等多个方面的复杂主题。理解和掌握这些概念和技术,能够帮助开发者编写出更加高效、可靠的并发程序。

    JAVA并发编程实践-线程池-学习笔记

    Java并发编程实践中的线程池是一个关键的概念,它在多线程编程中扮演着至关重要的角色,有效地管理和调度线程资源,以提高系统的性能和效率。线程池通过复用已存在的线程来减少线程的创建和销毁开销,避免了频繁的上...

    JAVA并发编程实践-线程安全-学习笔记

    在Java并发编程中,线程安全是一个至关重要的概念,它涉及到多线程环境下对共享数据的正确管理和访问。线程安全意味着当多个线程同时访问一个对象或数据时,对象的状态能够保持一致性和完整性,不会因为并发导致数据...

    JAVA并发编程实践-构建执行程序块-学习笔记

    JAVA并发编程实践-构建执行程序块-学习笔记 JAVA并发编程实践是指在JAVA编程语言中,使用多线程、并发编程来实现高效、可靠的程序执行。构建执行程序块是指在并发编程中,使用线程安全的类来管理状态,以确保程序的...

    java并发编程实践

    Java是最先支持多线程的开发的语言之一,Java从一开始就支持了多线程能力,因此Java开发者能常遇到上面描述的...这也是我想为Java并发技术而写这篇系列的原因。作为对自己的笔记,和对其他Java开发的追随者都可获益的。

    Java并发实践-学习笔记

    1. **Java并发基础**:首先,笔记可能会介绍Java并发的基础概念,包括线程的创建(通过`Thread`类或实现`Runnable`接口)、线程的状态(新建、运行、阻塞、等待、死亡)以及线程的生命周期。 2. **同步机制**:Java...

    java--夜未眠<过来人的心得>

    "Java--夜未眠&lt;过来人的心得&gt;"这个标题暗示了一位经验丰富的Java开发者在深夜编程时积累的心得体会,可能涵盖了他在Java学习和实践过程中的各种经验和教训。这可能包括了错误调试、性能优化、设计模式、并发编程等多...

    JAVA并发编程实践-线程的关闭与取消-学习笔记

    在Java并发编程中,线程的关闭和取消是一项重要的任务,因为不正确的处理可能导致数据不一致、资源泄漏等问题。在Java中,强制停止线程并不是一个推荐的做法,因为这可能会导致系统状态的不稳定。传统的`Thread.stop...

    JAVA并发编程实践-线程对象与组合对象-学习笔记

    使用java.util.concurrent类库构造安全的并发应用程序的基础。共享其实就是某一线程的数据改变对其它线程可见,否则就会出现脏数据。

    实战Java高并发程序设计-试读

    《实战Java高并发程序设计》是一本专注于Java并发编程实践的书籍,试读版提供了前两章的内容,为读者提供了一个初步了解并发编程基础的窗口。在Java领域,并发编程是构建高性能、高效率系统的关键技术,对于软件开发...

    Java高并发视频教学,并带实战java高并发程序设计,高并发面试题目

    "java高并发.txt"可能是一份文档或笔记,涵盖了Java并发编程的核心概念和技术。它可能详细解释了线程的生命周期、线程安全问题(如数据竞争、活锁、死锁)、并发工具类(如CountDownLatch、CyclicBarrier、Semaphore...

Global site tag (gtag.js) - Google Analytics