本文来自http://blog.csdn.net/liuxian13183/ ,引用必须注明出处!
并发产生的大环境:所有事情都是顺序执行的,但总有一些事情需要同时进行,这种现象就叫并发。
一般情况下,多核处理器机器处理快,相当于几个人在同时做几件事,而单核相当于一个人在同时做几件事,通常情况下,我们要提高性能,都是在单核上进行操作的,毕竟单核提高,多核更快。单核程序,增加了事情间切换的代价。
实现并发最直接的方式是在操作系统级别使用进程。进程:运行在它自己的地址空间内的自包容程序。多任务操作系统可以通过周期性地将CPU从一个进程切换到另一个进程,来实现同时运行多个进程。编写多线程程序最基本的困难在于协调不同线程驱动的任务之间对这些资源的使用,以使得这些资源不会同时被多个任务访问。咱们的电脑,每个软件都存在一个单独的进程中,虽然我们看到每个软件都在执行,其实中间是有停顿的,如果单启动一个软件会跑的更快,就是这个原因。
并发编程使我们可以将程序划分为多个分享的,独立运行的任务,程序使得每个任务都好像有其自己的CPU一样。其底层机制是切分CPU时间,但通常你不需要考虑它。如果程序运行的太慢,为机器增添一个CPU就能很容易地回忆程序的运行速度,多线程和多任务是使用多处理器系统的最合理方式,但如果你的公司要求你在单核机器上做出双核的速度,那也相当挑战了,但是不是不可以实现!
使用Thread启动新的线程,使用Executor来管理线程,作为客户端与任务执行之间的一个间接层,允许异步执行任务,而且无须地显式的管理线程的生命周期。通过new 一个CachedThreadPool或者FixedThreadPool,在程序执行过程中通常会创建与所需数量相同的线程,然后在它回收旧线程时停止创建新线程,一般发生问题时才切换成后者;如果使用SingleThreadExecutor,那么这些任务将排队,每个任务会在下一个任务开始之前运行结束,所有的任务将使用相同的线程。
如果你希望任务在完成时能够返回同一个值,那么可以实现Callable接口而不是Runnable接口。关于优先级,一般只使用MAX_PRIORITY,NORM_PRIORITY,MIN_PRIORITY三种;后台线程,就是daemon线程,是指在程序运行时在后台提供一种通用服务的线程,并且这种线程并不属于程序中不可或缺的部分,当所有非后台线程结束时,程序也就终止,同时杀死进程中的所有后台线程,标注:必须在线程启动前调用setDaemon()方法,才能把它设置为后台线程,当最后一个非后台线程终止时,后台线程会突然终止,因此一旦main()退出,JVM就会立即关闭所有的后台进程,而不会有任何你希望出现的确认形式。
一个线程可以在其他线程之上调用join()方法,其效果是等待一段时间直接到第二个线程结束才继续执行,thread.isAlive返回为false,它才执行,意思是顺接执行。做并发,最大的问题,就是资源共享,基本上所有的并发模式在解决线程冲突问题的时候,都是采用序列化访问共享资源的方案,这意味着在给定时刻只允许一个任务访问共享资源,可以使用yield放弃资源使用权利,setPriority给其他线程设置优先级,只要同等的就可以让给其他线程来处理。最常用的当然是加锁,如果是方法就需要定义为private,可以规避其他任务直接访问,避免冲突。一个任务可以调用一个对象的几个方法,那么就是多次获得对象的锁,每当任务离开一个sychronized方法,计数递减,当计数为零的时候,锁被完全释放,此时别的任务就可以使用些资源。
再介绍一种方法,Lock,它必须被显式的创建、锁定、释放,使用Lock.lock和unlock即可,需要注意的是,如果有try..catch,要确保return语句在try子句中出现,以确保unlock不会过早发生,从而将数据暴露给第二个任务。一般还是用sychronized,特殊问题才使用显式的Lock对象,如sychronized不能尝试着获取锁且最终获取锁会失败,或者尝试着获取锁一段时间,然后放弃它。
原子操作:对域中的值做赋值和返回操作,自增减是线程不安全的,所以Java SE5中引入诸如AtomicInteger、AtomicLong、AtommicReference等特殊原子性变量类。
原子性可以应用于除long和double之外的所有基本类型之上的“简单操作”。对于读取和定稿除long和double之外的基本类型变量这样的操作,可以保证它们会被当作不可分的操作来操作内存,但JVM可以将64位的读取和定稿当作两个分享的32位操作来执行,就产生了在一个读取和定稿操作中间发生上下文切换,从而导致不同的任务可以看到不正确结果的可能性,但当你使用volatile关键字,就会获得原子性。原子操作由线程机制来保证其不可中断,专家级的程序员可以利用这一点来编写无锁的代码,这些代码不需要被同步。在多处理器系统上,相对于单处理器系统而言,可视性问题远比原子性问题多的多,volatile关键字确保了应用中的可视性,将一个域声明为volatile,那么只要对这个域产生写操作,那么所有的读操作就可以看到这个修改,volatile域会立即被写入主存中,而读取操作就发生在主存中,如果多个任务访问这个域,那么它就是volatile的,同步会导致主存刷新,如果用sychronized可不用volatile,当然第一选择当然是sychronized。如果一个域可能会被多个任务同时访问,或者这些任务中至少有一个是写入任务,那么你就应该将这个域设置为volatile,意思是告诉编译器不要执行任何移动读取和写入操作的优化,目的是用线程中的局部变量维护对这个域的精确同步。
创建和管理本地存储可由ThreadLocal类来实现,防止任务在共享资源上产生冲突,为使用相同变量的每个不同线程都创建不同的存储。
线程状态:新建(New),就绪(Runnable)、阻塞(Blocked)、死亡(Dead),调用 sleep休眠若干时间后重新运行,wait则使线程挂起,notify或notifyAll才会重运行,interrupted方法可以终止被阻塞的任务,中断状态将被复位,譬如sleep,但不能中断试图获得sychronized锁或者试图执行I/O操作的线程。调用sleep、yield未释放锁,而调用wait将释放锁。
死锁原理:A-等>B-等>C-等>D-等>A,资源不能得到释放。当以下四个条件同时满足时,就会发生死锁:
1、互斥条件,任务中使用的资源至少有一个是不能共享的
2、至少有一个任务它必须持有一个资源且正在等待下一个资源
3、资源不能被任务抢占,任务必须把资源释放当作普通事件
4、必须有循环等待
所以打破循环最容易的是第1个和最后一个,第1个设计时可注意,第4个设置超时时间即可。
PriorityBlockingQueue是一个很基础的优先级队列,它具有可阻塞的读取操作;ScheduledThreadPoolExecutor可以设置一个在预定时间运行的任务;Semaphore的设计是通过计数信号量允许N个任务同时访问一个资源,谁先改完签回,那么其他任务修改则无效;Exchanger是两个任务之间交换对象的栅栏,调用exchanger()时,它将阻塞直至对方任务调用它自己的exchange方法,那时这两个exchanger方法将全部完成。
在一定数据量有线程内,sychronized比Lock或Atomic更高效,之后便不行,所以编程切勿盲目迷信,在性能调优时要打破常规,调试出最合适的方案来。CopyOnWriteArrayList写入将导致创建整个底层数据的副本,而源数组将保留在原地,使得复制的数组在被修改时,读取操作可以安全地执行。Future,可用来代替runnable来执行超时停止操作。
并发要理解:
1、可以运行多个独立的任务
2、必须考虑当这些任务关闭时,可能出现的所有问题
3、任务可能会在共享资源上彼此干涉。互斥(锁)是用来防止这种冲突的基本工具。
4、如果任务设计得不够仔细,就可能会死锁
所以要明白什么时候应该使用并发,什么时候应该避免使用并发
1、要处理很多任务,它们交织在一起
2、要能够更好的组织代码
3、要更便于用户使用
主要缺陷:
1、等待共享资源的时候性能降低
2、需要处理线程的额外CPU花费
3、糟糕的程序设计导致不必要的复杂度
4、有可能产生些病态行为如竞争、死锁
5、不同平台导致的不一致性
OK,本阶段的读书笔记告一段落。