《Java并发编程实战》读书笔记
Subsections
线程安全(Thread safety)
锁(lock)
共享对象
对象组合
基础构建模块
任务执行
取消和关闭
线程池的使用
性能与可伸缩性
并发程序的测试
显示锁
原子变量和非阻塞同步机制
public class Instance() {private Instance in = null;public Instance getInstance() {if(in == null) {in= new Instance();}return in;}
}
原理:每个运行的线程都会有一个类型为ThreadLocal.ThreadLocalMap的map,这个map就是用来存储与这个线程绑定的变量,map的key就是ThreadLocal对象,value就是线程正在执行的任务中的某个变量的包装类Entry.
ThreadLocal有四个方法:
public T get() {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null) {ThreadLocalMap.Entry e = map.getEntry(this);if (e != null)return (T)e.value;}return setInitialValue();}
//如果是第一次调用,需要初始化。
private T setInitialValue() {T value = initialValue();Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);elsecreateMap(t, value);return value;}
//将此线程局部变量的当前线程副本中的值设置为指定值
public void set(T value) {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);elsecreateMap(t, value);}
//移除此线程局部变量的值。
public void remove() {ThreadLocalMap m = getMap(Thread.currentThread());if (m != null)m.remove(this);}
数据库连接管理类,转载:http://blog.csdn.net/ghsau/article/details/15732053
public class ConnectionManager {/** 线程内共享Connection,ThreadLocal通常是全局的,支持泛型 */private static ThreadLocal<Connection> threadLocal = new ThreadLocal<Connection>();public static Connection getCurrConnection() {// 获取当前线程内共享的ConnectionConnection conn = threadLocal.get();try {// 判断连接是否可用if(conn == null || conn.isClosed()) {// 创建新的Connection赋值给conn(略)// 保存ConnectionthreadLocal.set(conn);}} catch (SQLException e) {// 异常处理}return conn;}/*** 关闭当前数据库连接*/public static void close() {// 获取当前线程内共享的ConnectionConnection conn = threadLocal.get();try {// 判断是否已经关闭if(conn != null && !conn.isClosed()) {// 关闭资源conn.close();// 移除ConnectionthreadLocal.remove();conn = null;}} catch (SQLException e) {// 异常处理}}
}
public class PersonSet{private final Set<Person> mySet = new HashSet<Person>();public sychronized void addPersion(Person p) {mySet.add(p)}public sychronized boolean containsPerson(Person p) {return mySet.contains(p);}}
public class privateLock {private final Object myLock = new Object();private int weight;void someMethod() {synchronized(myLock) {//访问weight}}}
使用私有锁对象比使用对象的内置锁有许多优点。私有锁可以将锁封装起来,客户代码无法得到锁。但客户可以通过公有方法来访问锁。以便参与到同步策略中去。
监视器好比一做建筑,它有一个很特别的房间,房间里有一些数据,而且在同一时间只能被一个线程占据,进入这个建筑叫做"进入监视器",进入建筑中的那个特别的房间叫做"获得监视器",占据房间叫做"持有监视器",离开房间叫做"释放监视器",离开建筑叫做"退出监视器".
如上图所示,一个线程通过1号门进入Entry Set(入口区),如果在入口区没有线程等待,那么这个线程就会获取监视器成为监视器的owner,然后执行监视区域的代码。如果在入口区中有其它线程在 等待,那么新来的线程也会和这些线程一起等待。线程在持有监视器的过程中,有两个选择,一个是正常执行监视器区域的代码,释放监视器,通过5号门退出监视 器;还有可能等待某个条件的出现,于是它会通过3号门到Wait Set(等待区)休息,直到相应的条件满足后再通过4号门进入重新获取监视器再执行。
注意:当一个线程释放监视器时,在入口区和等待区的等待线程都会去竞争监视器,如果入口区的线程赢了,会从2号门进入;如果等待区的线程赢了会从4 号门进入。只有通过3号门才能进入等待区,在等待区中的线程只有通过4号门才能退出等待区,也就是说一个线程只有在持有监视器时才能执行wait操作,处于等待的线程只有再次获得监视器才能退出等待状态。
信号量(Semaphore):用来控制同时访问某个特定资源的操作数量。通过 acquire() 获取一个许可,如果没有就等待,而 release() 释放一个许可。Semaphore允许线程获取许可, 未获得许可的线程需要等待.这样防止了在同一时间有太多的线程执行。Semaphore实现的功能就类似厕所有5个坑,假如有10个人要上厕所,那么同时只能有多少个人去上厕所呢?同时只能有5个人能够占用,当5个人中 的任何一个人让开后,其中等待的另外5个人中又有一个人可以占用了。另外等待的5个人中可以是随机获得优先机会,也可以是按照先来后到的顺序获得机会,这取决于构造Semaphore对象时传入的参数选项。单个信号量的Semaphore对象可以实现互斥锁的功能,并且可以是由一个线程获得了“锁”,再由另一个线程释放“锁”,这可应用于死锁恢复的一些场合。
package my.concurrent.semaphore;import java.util.concurrent.Semaphore;public class Car implements Runnable {private final Semaphore parkingSlot;private int carNo;/*** @param parkingSlot* @param carName*/public Car(Semaphore parkingSlot, int carNo) {this.parkingSlot = parkingSlot;this.carNo = carNo;}public void run() {try {parkingSlot.acquire();parking();sleep(300);parkingSlot.release();leaving();} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}private void parking() {System.out.println(String.format("%d号车泊车", carNo));}private void leaving() {System.out.println(String.format("%d号车离开车位", carNo));}private static void sleep(long millis) {try {Thread.sleep(millis);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}package my.concurrent.semaphore;import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;public class ParkingCars {private static final int NUMBER_OF_CARS = 30;private static final int NUMBER_OF_PARKING_SLOT = 10;public static void main(String[] args) {/** 采用FIFO, 设置true*/Semaphore parkingSlot = new Semaphore(NUMBER_OF_PARKING_SLOT, true);ExecutorService service = Executors.newCachedThreadPool();for (int carNo = 1; carNo <= NUMBER_OF_CARS; carNo++) {service.execute(new Car(parkingSlot, carNo));}sleep(3000);service.shutdown();/** 输出还有几个可以用的资源数*/System.out.println(parkingSlot.availablePermits() + " 个停车位可以用!");}private static void sleep(long millis) {try {Thread.sleep(millis);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}
4号车泊车
9号车泊车
2号车泊车
8号车泊车
10号车泊车
3号车泊车
12号车泊车
14号车泊车
6号车泊车
2号车离开车位
4号车离开车位
6号车离开车位
1号车离开车位
9号车离开车位
3号车离开车位
5号车泊车
8号车离开车位
10号车离开车位
11号车泊车
7号车泊车
12号车离开车位
13号车泊车
14号车离开车位
16号车泊车
17号车泊车
20号车泊车
19号车泊车
18号车泊车
15号车泊车
5号车离开车位
20号车离开车位
18号车离开车位
22号车泊车
11号车离开车位
7号车离开车位
13号车离开车位
15号车离开车位
21号车泊车
26号车泊车
23号车泊车
28号车泊车
25号车泊车
16号车离开车位
27号车泊车
17号车离开车位
30号车泊车
24号车泊车
29号车泊车
19号车离开车位
25号车离开车位
24号车离开车位
22号车离开车位
26号车离开车位
28号车离开车位
30号车离开车位
21号车离开车位
23号车离开车位
27号车离开车位
29号车离开车位
10 个停车位可以用!
class singleThreadWebServer {ServerSocket socket = new ServerSocket(80) ;while (true) {Socket connection = socket.accept();handleRequest(connection);}
}
class MultiThreadWebServer {ServerSocket socket = new ServerSocket(80) ;while (true) {final Socket connection = socket.accept();Runnable task = new Runnable() {public void run() {handleRequest(connection);}}new Thread(task).start();}
}
任务是一组逻辑工作单元,而线程则是使任务异步执行的逻辑单元。Executor为灵活且强大的异步任务执行框架提供了基础,还提供了对生命周期的支持,以及统计信息、应用管理机制和性能监视等机制。
Executor 基于生产者-消费者模式。提交任务相当是生产者,执行任务相当是消费者
a、执行策略:
任务在什么(What)线程中执行
任务以什么(What)顺序执行(FIFO/LIFO/优先级等)
同时有多少个(How Many)任务并发执行
允许有多少个(How Many)个任务进入执行队列
系统过载时选择放弃哪一个(Which)任务,如何(How)通知应用程序这个动作
任务执行的开始、结束应该做什么(What)处理
b、线程池:
线程池和工作者队列密切相关,工作者线程的任务:从工作队列中获取一个任务,执行任务,然后返回线程池并等待下一个任务。
Executors类里面提供了一些静态工厂,生成一些常用的线程池。
newSingleThreadExecutor:创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
newFixedThreadPool:创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。
newCachedThreadPool:创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。
newScheduledThreadPool:创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。
newSingleThreadScheduledExecutor:创建一个单线程的线程池。此线程池支持定时以及周期性执行任务的需求。
c、线程池Executor任务拒绝策略
java.util.concurrent.RejectedExecutionHandler描述的任务操作。
第一种方式直接丢弃(DiscardPolicy)
第二种丢弃最旧任务(DiscardOldestPolicy)
第三种直接抛出异常(AbortPolicy)
第四种任务将有调用者线程去执行(CallerRunsPolicy)
d、生命周期
java.util.concurrent.ExecutorService 接口对象来执行任务,该接口对象通过工具类java.util.concurrent.Executors的静态方法来创建。 Executors此包中所定义的 Executor、ExecutorService、ScheduledExecutorService、ThreadFactory 和 Callable 类的工厂和实用方法。
ExecutorService扩展了Executor并添加了一些生命周期管理的方法。一个Executor的生命周期有三种状态,运行 ,关闭 ,终止。Executor创建时处于运行状态。当调用ExecutorService.shutdown()后,处于关闭状态,isShutdown()方法返回true。这时,不应该再想Executor中添加任务,所有已添加的任务执行完毕后,Executor处于终止状态,isTerminated()返回true。
shutdown():执行平缓的关闭过程,不再接受新的任务,同时等待已经提交的任务执行完成。
shutdownNow();执行粗暴的关闭过程,尝试取消所有运行中的任务,并且不再启动队列中尚未开始启动的任务。
awaitTermination: 这个方法有两个参数,一个是timeout即超时时间,另一个是unit即时间单位。这个方法会使线程等待timeout时长,当超过timeout时间后,会监测ExecutorService是否已经关闭,若关闭则返回true,否则返回false。一般情况下会和shutdown方法组合使用。
ExecutorService service = Executors. newFixedThreadPool(3);for ( int i = 0; i < 10; i++) {System. out.println( "创建线程" + i);Runnable run = new Runnable() {@Overridepublic void run() {System. out.println( "启动线程");}};// 在未来某个时间执行给定的命令service.execute(run);}// 关闭启动线程service.shutdown();// 每隔1秒监测一次ExecutorService的关闭情况.service.awaitTermination(1, TimeUnit. SECONDS);System. out.println( "all thread complete");System. out.println(service.isTerminated());
访问共享对象可以使用的机制有synchronized,volatile,ReentrantLock。
有了synchronized 为啥JSR 166 小组花了这么多时间来开发 java.util.concurrent.lock 框架呢?答案很简单-同步是不错,但它并不完美。它有一些功能性的限制 —— 它无法中断一个正在等候获得锁的线程,也无法通过投票得到锁,如果不想等下去,也就没法得到锁。同步还要求锁的释放只能在与获得锁所在的堆栈帧相同的堆栈帧中进行,多数情况下,这没问题(而且与异常处理交互得很好),但是,确实存在一些非块结构的锁定更合适的情况。
Lock 和ReentrantLock
Lock接口中定义了一组抽象的加锁操作。Lock提供了一种无条件的、可轮询的、定时的以及可中断的锁后去操作。ReentrantLock 类实现了 Lock ,它拥有与 synchronized 相同的并发性和内存语义,但是添加了类似锁投票、定时锁等候和可中断锁等候的一些特性。在确实需要一些 synchronized 所没有的特性的时候,比如时间锁等候、可中断锁等候、无块结构锁、多个条件变量或者锁投票。
格式:
Lock lock = new ReentrantLock();
lock.lock();
try {
// update object state
}
finally {
lock.unlock();
}
必须在finally 中来释放Lock。
定时锁和轮询锁是为了避免死锁的发生。如果不能获取所需要的锁,可以使用定时的或者轮询的锁获取方式,从而使你重新获的控制权。
(1)、轮询锁( tryLock())
(2)、定时锁(tryLock(timeout, NANOSECONDS))。如果操作不能在指定的时间内给出结果,那么就会使程序提前结束
(3)、定时以及可中断的锁(lockInterruptibly)
(4)、读写锁(ReadWriteLock)。可以被多个读者访问或者被一个写者访问。
ReentrantLock 每次只能一个线程访问加锁的数据,从而达到维护数据完整性的目的。通过这种策略可以避免写/写和写/读冲突。但是也同时避免了读/读冲突。但是有些时候读操
作是可以被并发进行的。所以需要读写锁。
eg:实现一个简单的缓存。
现在的处理器(包括 Intel 和 Sparc 处理器)使用的最通用的方法是实现名为“比较并交换(Compare And Swap)”或 CAS 的原语。
public class CASCount implements Runnable {private SimilatedCAS counter = new SimilatedCAS(); @Overridepublic void run() {for (int i = 0; i < 10000; i++) { System.out.println(this.increment()); } }public int increment() {int oldValue = counter.getValue();int newValue = oldValue + 1;while (!counter.compareAndSwap(oldValue, newValue)) { //如果CAS失败,就去拿新值继续执行CASoldValue = counter.getValue(); newValue = oldValue + 1; }return newValue;}public static void main(String[] args) { Runnable run = new CASCount(); new Thread(run).start(); new Thread(run).start(); new Thread(run).start(); new Thread(run).start(); new Thread(run).start(); new Thread(run).start(); new Thread(run).start(); new Thread(run).start(); new Thread(run).start(); new Thread(run).start(); } }class SimilatedCAS {private int value;public int getValue() {return value;}// 这里只能用synchronized了,毕竟无法调用操作系统的CAS public synchronized boolean compareAndSwap(int expectedValue, int newValue) { if (value == expectedValue) { value = newValue; return true; } return false; }
}
JDK 5.0引入底层CAS支持,java.util.concurrenent.atomic.AtomicXXX,使用底层的JVM支持为数字和引用类型提供一种高效的CAS操作。
eg:AtomicInteger,AtomicIntegerArray,AtomicLong,AtomicLongArray
原子变量能够支持原子的有条件的,读-改-写操作。
eg:使用原子变量类 实现一个计数器。
public class AtomicCounter implements Runnable{//AtomicInteger采用了系统的CASprivate AtomicInteger value = new AtomicInteger(); @Overridepublic void run() {for (int i = 0; i < 10000; i++) { System.out.println(value.incrementAndGet()); } }public static void main(String[] args) { Runnable run = new AtomicCounter(); new Thread(run).start(); new Thread(run).start(); new Thread(run).start(); new Thread(run).start(); new Thread(run).start(); new Thread(run).start(); new Thread(run).start(); new Thread(run).start(); new Thread(run).start(); new Thread(run).start(); } }
public class ConcurrentStack {class Node {public final int item;public Node next;public Node(int item) {this.item = item;}}private AtomicReference<Node> top = new AtomicReference<ConcurrentStack.Node>();//入栈public void push(int item) {Node newNode = new Node(item);Node oldHead;do {oldHead = top.get();newNode.next = oldHead;} while (!top.compareAndSet(oldHead, newNode));}//出栈public int pop() {Node oldHead;Node newHead;do {oldHead = top.get();if(oldHead == null ) {return -1;}newHead = oldHead.next; //Atomically sets the value to the given updated value if the current value {@code ==} the expected value.} while (!top.compareAndSet(oldHead, newHead));return oldHead.item;}}
《Java并发编程实战》读书笔记相关推荐
- 读书笔记 | 墨菲定律
1. 有些事,你现在不做,永远也不会去做. 2. 能轻易实现的梦想都不叫梦想. 3.所有的事都会比你预计的时间长.(做事要有耐心,要经得起前期的枯燥.) 4. 当我们的才华还撑不起梦想时,更要耐下心来 ...
- 读书笔记 | 墨菲定律(一)
1. 有些事,你现在不做,永远也不会去做. 2. 能轻易实现的梦想都不叫梦想. 3.所有的事都会比你预计的时间长.(做事要有耐心,要经得起前期的枯燥.) 4. 当我们的才华还撑不起梦想时,更要耐下心来 ...
- 洛克菲勒的38封信pdf下载_《洛克菲勒写给孩子的38封信》读书笔记
<洛克菲勒写给孩子的38封信>读书笔记 洛克菲勒写给孩子的38封信 第1封信:起点不决定终点 人人生而平等,但这种平等是权利与法律意义上的平等,与经济和文化优势无关 第2封信:运气靠策划 ...
- 股神大家了解多少?深度剖析股神巴菲特
股神巴菲特是金融界里的传奇,大家是否都对股神巴菲特感兴趣呢?大家对股神了解多少?小编最近在QR社区发现了<阿尔法狗与巴菲特>,里面记载了许多股神巴菲特的人生经历,今天小编简单说一说关于股神 ...
- 2014巴菲特股东大会及巴菲特创业分享
沃伦·巴菲特,这位传奇人物.在美国,巴菲特被称为"先知".在中国,他更多的被喻为"股神",巴菲特在11岁时第一次购买股票以来,白手起家缔造了一个千亿规模的 ...
- 《成为沃伦·巴菲特》笔记与感想
本文首发于微信公众帐号: 一界码农(The_hard_the_luckier) 无需授权即可转载: 甚至无需保留以上版权声明-- 沃伦·巴菲特传记的纪录片 http://www.bilibili.co ...
- 读书笔记002:托尼.巴赞之快速阅读
读书笔记002:托尼.巴赞之快速阅读 托尼.巴赞是放射性思维与思维导图的提倡者.读完他的<快速阅读>之后,我们就可以可以快速提高阅读速度,保持并改善理解嗯嗯管理,通过增进了解眼睛和大脑功能 ...
- 读书笔记001:托尼.巴赞之开动大脑
读书笔记001:托尼.巴赞之开动大脑 托尼.巴赞是放射性思维与思维导图的提倡者.读完他的<开动大脑>之后,我们就可以对我们的大脑有更多的了解:大脑可以进行比我们预期多得多的工作:我们可以最 ...
- 读书笔记003:托尼.巴赞之思维导图
读书笔记003:托尼.巴赞之思维导图 托尼.巴赞的<思维导图>一书,详细的介绍了思维发展的新概念--放射性思维:如何利用思维导图实施你的放射性思维,实现你的创造性思维,从而给出一种深刻的智 ...
- 产品读书《滚雪球:巴菲特和他的财富人生》
作者简介 艾丽斯.施罗德,曾经担任世界知名投行摩根士丹利的董事总经理,因为撰写研究报告与巴菲特相识.业务上的往来使得施罗德有更多的机会与巴菲特亲密接触,她不仅是巴菲特别的忘年交,她也是第一个向巴菲特建 ...
最新文章
- hive 中文comment乱码解决
- SAP PP COR1事务里下达工单,保存时报错:No data was found for the input values
- 论c++/java/c 与python的语法上的区别
- 全球及中国膀胱癌药物行业“十四五”专项规划及市场调研分析报告2021-2027年
- 初中计算机学情分析,初中信息技术教学计划
- JAVA自学笔记21
- Linux命令行与Shell脚本编程大全读书笔记
- 最长升序子串1231
- SRM 212 Div II Level One: YahtzeeScore
- oracle 10g 配置asm,在Oracle Linux 4.7上安装配置Oracle 10g ASM数据库
- echarts年龄饼图_echarts自定义饼图
- free源码分析---1
- 切换电脑计算机名称软件,多电脑切换器
- Netgear WNDR3800 用 LAN口 替换 WAN口
- 连接mysql数据库失败问题
- 【linux】md5sum 命令详解
- 《数据挖掘概念与技术》学习笔记-第二章
- 因计算机而强大在线读,读书分享会丨《因计算机而强大:计算机如何改变我们的思考与学习》...
- 张一鸣没动手,王兴不紧张
- android qq 禁用字体,在qq中如何设置界面字体大小图文教程
热门文章
- 一个妹子写给程序员男友的情书
- MacBookpro直连校园网,显示连接却无法打开网页(已解决)
- navicate搭建主从数据库
- Lenovo联想笔记本电脑小新Pro-13 2019(AMD平台API版)原装出厂Win10系统恢复原厂OEM系统
- MySQL比较时间(datetime)大小
- ZYNQ UltraScale+ MPSoC OpenAMP 2018.3
- 32.java_注解(Annotation)
- python高级编程第3版_Python高级编程(第2版)
- 使用ReactNative构建移动应用程序中的AR应用程序:了解ReactNative中的AR开发生态系统
- 操作系统学习同步与互斥例题:理发师问题