Java并发编程实践

整理文档很辛苦,赏杯茶钱您下走!

免费阅读已结束,点击下载阅读编辑剩下 ...

阅读已结束,您可以下载文档离线阅读编辑

资源描述

黄兴海线程进程一个程序运行单元独立的内存空间不同的进程拥有不同的代码和数据空间可以申请和拥有系统资源线程轻量级进程一个进程可以有多个线程共享相同的内存空间程序计数器、堆栈等是不同的创建线程Thread封装了线程的行为。优先级:1~10,默认为5优先级越大,越会优先执行守护线程(daemon)守护线程不会影响JVM的生命周期一般当所有非守护线程退出后,JVM才会退出Thread(ThreadGroupgroup,Runnabletarget,Stringname,longstackSize)group:线程组,将多个线程封装成一个线程组,可以在线程组中查看线程的数目、线程的列表等信息target:待运行的任务name:线程的名称(以便于标识线程)stackSize:线程栈的大小。控制线程具体见java.lang.Thread.State初始(New)已经创建,但是未运行可运行(Runnable)表示正在JVM中执行可能正在等待处理器等资源阻塞(Blocked)被监视锁所阻塞无限等待(Waiting)无限期地等待另一个线程执行特定的操作有限等待(Timed_Waiting)有限时间内等待另一个线程执行特定的操作终结(Terminated)运行结束控制线程wait/notify协作object等待队列线程T线程3线程T进入object的等待队列后来,线程3也进入等待队列notifyAll会唤醒等待队列中的所有线程Notify会不确定地唤醒某一线程线程1wait/notify协作object必须是监视锁的monitor修改condition、wait和notify均必须在监视锁内部object.wait被唤醒后,最好再次检测条件是否满足除非你知道不会出错?synchronized关键字要求代码块必须同步执行,那么notifyAll之后,各个线程的执行次序是如何的呢?object等待队列线程3线程1线程2wait/notify!在notifyAll之后,各个被唤醒的线程必须先获取监视锁调用notifyAll的线程释放监视锁被唤醒的线程竞争监视锁某线程竞争成功后,运行代码,最后释放监视锁余下线程重复竞争,直至所有代码都执行完毕性能?如果有N个唤醒的线程,那么会面临大量线程对监视锁的竞争如果唤醒后又重新wait,下次notifyAll时又会面临同样的问题一个坏案例T1、T2、…、Tn都在等待队列Q进入数据,然后从Q中取出一个数据T0每秒往Q插入一个数据,并notifyT1、T2…、TnT*会竞争成功,然后去除一个数据,于是队列Q又空了,其他线程发现队列已经空了,重新等待直至N秒后,所有线程处理结束。看看唤醒操作有多少次?但是,还是建议尽量使用notifyAll正确是第一位的~WaitingBlockedRunnable被唤醒后,竞争锁竞争锁成功Terminated不符合唤醒条件重新等待符合唤醒条件成功执行完毕joinsomeThread.join(…)等待线程someThread终止someThread.wait会在线程结束时被唤醒中断每个线程都有一个中断状态someThread.Interrupt()设置someThread的中断状态为truesomeThread.isInterrupted()返回someThread的中断状态Thread.interrupted()清除当前进程的中断状态,并返回之前的值某些阻塞方法会监视线程何时被中断,然后抛出相应的异常Object.wait,Thread.join,Thread.sleep1.清除中断状态2.抛出InterruptedException异常InterruptibleChannel的I/O操作上受阻1.关闭通道2.设置中断状态3.抛出ClosedByInterruptExceptionSelector中受阻1.立刻从选择操作中返回(可能是非零值)2.设置中断状态除非了解线程的中断策略,否则是不应该中断此线程的中断中断的意义传递请求中断的消息不意味着必须停止线程正在执行的工作可以使用Thread.stop()来立刻停止线程,但是这是有风险的处理中断方式一:传递中断异常方式二:设置中断状态方式三:忽略中断一些建议不应该随意忽略中断状态,除非是符合期望的通用的任务或库中,建议传递中断异常如果无法传递异常(如Runnable类型),可以设置中断状态使用中断来取消任务风险安全性正确地发布对象正确地同步对象正确地使用对象活跃度死锁活锁弱响应饥饿安全性风险无论何时,只要有多于一个的线程同时访问给定的状态变量,而且其中有线程修改此状态变量,则必须通过同步来协调线程对改变量的访问。同步机制synchronized关键字volatile变量显式锁原子变量为何要同步可见性在可共享内存的多处理器体系架构中,每个处理器都有自己的缓存,并且周期性的与主内存保证一致有可能,不同的处理器在相同的存储位置可能看到不同的值重排序JVM允许指令的重排序,只需要其保证满足JVM的存储模型原子性比如:多个线程来操作计数变量线程封闭既然共享对象很烦人,各种不容易,那么最好的选择就是不共享线程封闭技术把对象封闭在一个线程之中方式伪线程限制栈限制ThreadLocal样例绝大多数GUI程序要求所有对可视化组件和数据模型对象的修改都限制在事件分发线程中,否则会抛出异常Swing、Android、…JDBC连接池每次从连接池中取出Connection对象,使用完毕之后归还到池内Connection对象本身是不安全的,但是将之封闭在单个线程之后,使用它就没有问题了伪线程限制和栈限制伪线程限制指要求具体的实现去维护线程限制的任务。无法严格限制具体的实现很容易出错伪线程限制的一个样例对于某个volatile变量,如果要求实现中,只有一个线程来修改这个变量,则是线程安全的。在栈限制中,只能通过本地变量才可以触及对象Java保证了基本类型的本地变量总是线程封闭的对于引用类型的本地变量,要确保不会逸出ThreadLocalThreadLocal是维护线程限制的更规范更有效的方式提供get和set操作每个get会返回有当前线程set的最新值作用防止在单例或全局变量中出现(不正确的)共享在当前线程中上下文传递变量原理Thread对象中有ThreadLocalMapThreadLocal,Var每次调用threadLocal.get()时ThreadcurrentThread=Thread.currentThread();ThreadLocalMapthreadLocalMap=currentThread.threadLocalMapVarvar=threadLocalMap.get(threadLocal)returnvar.value;不可变对象不可变对象需要满足其中的变量不能在创建后被修改所有的变量都是final类型虽然可以不全部都是final,比如String类型中的hashcode变量,这是因为它的结果来源于其他不可变变量。但是强烈不建议自己这么做。被正确创建没有发生this引用的逸出比如在构造函数中启动一个线程典型地GoogleGuava中的ImmutableXXXFinal保证在构造函数完成后,Java保证Final变量对非当前线程都是可见的。然而对于non-final变量不作此保证。?i,j等于多少安全发布对象正确创建的对象可以通过如下条件安全地发布通过静态方法初始化对象的引用将对象的引用存储到volatile或AtomicReference将对象的引用存储到正确的对象的final域中将对象的引用存储到由锁正确保护的域中例如如果将上例中的FinalFieldExample的实例的引用存储到Vector中,则可以安全地发布到通过Vector来获取它的线程中准不可变对象如果一个对象在技术上不是不可变的,但是它的状态在发布后不会再被修改,则成为准不可变对象对象的发布不可变对象可以任意发布准不可变对象必须安全地发布可变对象必须安全地发布,同时必须是线程安全的或者是被锁保护的共享对象的有效策略线程限制一个对象被限制在线程中,且被线程独占,且只能被这个线程修改共享只读一个对象在没有额外的同步下,被多个线程并发地访问,但是不能被任何线程修改。这个对象可以是可变对象或准不可变对象共享线程安全一个对象在内部进行同步,所以其他线程无需额外同步被守护一个对象只能通过特定的锁来访问。活跃度风险死锁两个或两个以上的线程在执行过程中,因争夺资源而造成的一种互相等待的现象饥饿某线程请求所需资源时,总是被拒绝,以至于不能继续进行。弱响应性由于任务的耗时较长,会导致响应变慢活锁尽管没有被阻塞,但是线程仍然无法继续进行,因为在不断充实相同的操作,但却总是失败死锁的必要条件互斥条件资源只能被一个线程所使用,如果其他线程需要申请此资源,需要等待请求和保持条件线程已经保持了至少一个资源,同时又请求新的资源,但改资源又被其他线程所有,导致请求过程被阻塞,但是又不释放保持的资源不剥夺条件线程保持的资源只能由自己释放,不能被其它线程或系统剥夺环路等待条件存在线程,资源的环路,即存在P0,P1,P2,…,Pn链,其中P0在等待P1释放资源,…,Pn在等待P0释放资源无法避免无法避免避免死锁如果所有线程以固定的顺序获取锁,则不会产生死锁破坏环路等待条件尽量使用开发调用当调用的方法不需要持有锁时,成为开放调用尝试定时的锁使用ReentrantLock.tryLock(timeout)可以使用线程转储分析死锁kill-3pidjstackpid饥饿CPU周期饥饿CPU周期是典型的最常见的引发饥饿的资源在Java中,使用线程优先级不当,有可能会造成饥饿建议不要使用线程的优先级,因为会增加平台的依赖性,从而有可能导致饥饿问题。非公平性分配资源NonFair锁使用优先级队列来处理任务活锁场景一描述在消息处理程序中,如果消息处理失败后,回退整个事务,并将之置于队首。如果某消息一直处理失败(可能是BUG),则可能会导致处理程序无法继续,也即发生活锁。原因误将不可修复错误当作可修复错误解决方法1.添加重试次数上限2.不回退到队首场景二描述多个协作线程中,每个线程在请求资源时,如果发现有其他线程也在请求,则谦让地让别的线程先拥有,则有可能一直在谦让,从而导致活锁解决方法1.采取竞争的方式,而不是谦让的方式。先到先服务。2.重试时,添加随机等待。VolatileVolatile变量直接在主内存中修改所有的修改都是立刻可见的Volatile变量写操作会与所有读操作同步对volatilelong/double类型的读写都是原子性的Volatile的操作是不可重排序的volatile只针对变量的引用,而不是它代表的值volatileint[]array,则只针对array,而不会针对array中的元素可以使用AtomicXXXArrayvolatiledoublecheck如果没有volatile,则•虽然A6安全发布•但是A3不是安全引用•A2可能会为0非完整引用synchronizedJava的关键字可以对成员方法使用也可以对方法块使用必须有一个监视对象Java保证在执行同步块之后释放锁即使内部抛出异常可以配合wait/notify机制可以重进入缺点:不灵活,如无法解决读写者问题无法判断是否可以获取锁(即tryLock)无法在获取锁指定超时时间成本高显式锁Java5.0

1 / 72
下载文档,编辑使用

©2015-2020 m.777doc.com 三七文档.

备案号:鲁ICP备2024069028号-1 客服联系 QQ:2149211541

×
保存成功