《并发故障模式》PPT课件.ppt

上传人:tia****nde 文档编号:12727429 上传时间:2020-05-20 格式:PPT 页数:57 大小:275.31KB
返回 下载 相关 举报
《并发故障模式》PPT课件.ppt_第1页
第1页 / 共57页
《并发故障模式》PPT课件.ppt_第2页
第2页 / 共57页
《并发故障模式》PPT课件.ppt_第3页
第3页 / 共57页
点击查看更多>>
资源描述
1,并发故障模式,2,基础知识,方法一:继承Thread类,覆盖方法run()下面是一个例子:publicclassMyThreadextendsThreadintcount=1,number;publicMyThread(intnum)number=num;System.out.println(创建线程+number);publicvoidrun()while(true)System.out.println(线程+number+:计数+count);if(+count=6)return;publicstaticvoidmain(Stringargs)for(inti=0;i5;i+)newMyThread(i+1).start();,3,方法二:实现Runnable接口下面是一个例子:publicclassMyThreadimplementsRunnableintcount=1,number;publicMyThread(intnum)number=num;System.out.println(创建线程+number);publicvoidrun()while(true)System.out.println(线程+number+:计数+count);if(+count=6)return;publicstaticvoidmain(Stringargs)for(inti=0;i5;i+)newThread(newMyThread(i+1).start();,4,Runnable接口只有一个方法run(),我们声明自己的类实现Runnable接口并提供这一方法,将我们的线程代码写入其中,就完成了这一部分的任务。但是Runnable接口并没有任何对线程的支持,我们还必须创建Thread类的实例,这一点通过Thread类的构造函数publicThread(Runnabletarget);,5,线程的四种状态1.新状态:线程已被创建但尚未执行(start()尚未被调用)。2.可执行状态:线程可以执行,虽然不一定正在执行。CPU时间随时可能被分配给该线程,从而使得它执行。3.死亡状态:正常情况下run()返回使得线程死亡。调用stop()或destroy()亦有同样效果,但是不被推荐,前者会产生异常,后者是强制终止,不会释放锁。4.阻塞状态:线程不会被分配CPU时间,无法执行。,6,线程的同步,由于同一进程的多个线程共享同一片存储空间,在带来方便的同时,也带来了访问冲突这个严重的问题。Java语言提供了专门机制以解决这种冲突,有效避免了同一个数据对象被多个线程同时访问。这套机制就是synchronized关键字,它包括两种用法:synchronized方法和synchronized块。,7,1.synchronized方法:通过在方法声明中加入synchronized关键字来声明synchronized方法。如:publicsynchronizedvoidaccessVal(intnewVal);synchronized方法控制对类成员变量的访问:每个类实例对应一把锁,每个synchronized方法都必须获得调用该方法的类实例的锁方能执行,否则所属线程阻塞,方法一旦执行,就独占该锁,直到从该方法返回时才将锁释放,此后被阻塞的线程方能获得该锁,重新进入可执行状态。这种机制确保了同一时刻对于每一个类实例,其所有声明为synchronized的成员函数中至多只有一个处于可执行状态(因为至多只有一个能够获得该类实例对应的锁),从而有效避免了类成员变量的访问冲突(只要所有可能访问类成员变量的方法均被声明为synchronized)。,8,在Java中,不光是类实例,每一个类也对应一把锁,这样我们也可将类的静态成员函数声明为synchronized,以控制其对类的静态成员变量的访问。synchronized方法的缺陷:若将一个大的方法声明为synchronized将会大大影响效率,典型地,若将线程类的方法run()声明为synchronized,由于在线程的整个生命期内它一直在运行,因此将导致它对本类任何synchronized方法的调用都永远不会成功。当然我们可以通过将访问类成员变量的代码放到专门的方法中,将其声明为synchronized,并在主方法中调用来解决这一问题,但是Java为我们提供了更好的解决办法,那就是synchronized块。,9,2.synchronized块:通过synchronized关键字来声明synchronized块。语法如下:synchronized(syncObject)/允许访问控制的代码synchronized其中的代码必须获得对象syncObject(如前所述,可以是类实例或类)的锁方能执行,具体机制同前所述。由于可以针对任意代码块,且可任意指定上锁的对象,故灵活性较高。,10,线程的阻塞,为了解决对共享存储区的访问冲突,Java引入了同步机制,现在让我们来考察多个线程对共享资源的访问,显然同步机制已经不够了,因为在任意时刻所要求的资源不一定已经准备好了被访问,反过来,同一时刻准备好了的资源也可能不止一个。为了解决这种情况下的访问控制问题,Java引入了对阻塞机制的支持。阻塞指的是暂停一个线程的执行以等待某个条件发生(如某资源就绪)。Java提供了大量方法来支持阻塞:,11,1.sleep()方法:sleep()允许指定以毫秒为单位的一段时间作为参数,它使得线程在指定的时间内进入阻塞状态,不能得到CPU时间,指定的时间一过,线程重新进入可执行状态。典型地,sleep()被用在等待某个资源就绪的情形:测试发现条件不满足后,让线程阻塞一段时间后重新测试,直到条件满足为止。2.suspend()和resume()方法:两个方法配套使用,suspend()使得线程进入阻塞状态,并且不会自动恢复,必须其对应的resume()被调用,才能使得线程重新进入可执行状态。典型地,suspend()和resume()被用在等待另一个线程产生的结果的情形:测试发现结果还没有产生后,让线程阻塞,另一个线程产生了结果后,调用resume()使其恢复。,12,3.yield()方法:yield()使得线程放弃当前分得的CPU时间,但是不使线程阻塞,即线程仍处于可执行状态,随时可能再次分得CPU时间。调用yield()的效果等价于调度程序认为该线程已执行了足够的时间从而转到另一个线程。4.wait()和notify()方法:两个方法配套使用,wait()使得线程进入阻塞状态,它有两种形式,一种允许指定以毫秒为单位的一段时间作为参数,另一种没有参数,前者当对应的notify()被调用或者超出指定时间时线程重新进入可执行状态,后者则必须对应的notify()被调用。初看起来它们与suspend()和resume()方法对没有什么分别,但是事实上它们是截然不同的。区别的核心在于,前面叙述的所有方法,阻塞时都不会释放占用的锁(如果占用了的话),而这一对方法则相反。,13,上述的核心区别导致了一系列的细节上的区别。首先,前面叙述的所有方法都隶属于Thread类,但是这一对却直接隶属于Object类,也就是说,所有对象都拥有这一对方法。初看起来这十分不可思议,但是实际上却是很自然的,因为这一对方法阻塞时要释放占用的锁,而锁是任何对象都具有的,调用任意对象的wait()方法导致线程阻塞,并且该对象上的锁被释放。而调用任意对象的notify()方法则导致因调用该对象的wait()方法而阻塞的线程中随机选择的一个解除阻塞(但要等到获得锁后才真正可执行)。其次,前面叙述的所有方法都可在任何位置调用,但是这一对方法却必须在synchronized方法或块中调用,理由也很简单,只有在synchronized方法或块中当前线程才占有锁,才有锁可以释放。同样的道理,调用这一对方法的对象上的锁必须为当前线程所拥有,这样才有锁可以释放。因此,这一对方法调用必须放置在这样的synchronized方法或块中,该方法或块的上锁对象就是调用这一对方法的对象。若不满足这一条件,则程序虽然仍能编译,但在运行时会出现IllegalMonitorStateException异常。,14,wait()和notify()方法的上述特性决定了它们经常和synchronized方法或块一起使用,将它们和操作系统的进程间通信机制作一个比较就会发现它们的相似性:synchronized方法或块提供了类似于操作系统原语的功能,它们的执行不会受到多线程机制的干扰,而这一对方法则相当于block和wakeup原语(这一对方法均声明为synchronized)。它们的结合使得我们可以实现操作系统上一系列精妙的进程间通信的算法(如信号量算法),并用于解决各种复杂的线程间通信问题。,15,1.不正确的同步,1)不连续的同步对于下列程序publicclassServerStatuspublicsynchronizedvoidaddUser(Stringu)users.add(u);publicvoidremoveUser(Stringu)users.remove(u);,16,如果一个类是要线程安全的,那么其中涉及对共享变量的修改都要进行同步修饰。,17,2)对易变域的同步对于下列程序publicclassMutablePointpublicintx,y;publicMutablePoint()x=0;y=0;publicMutablePoint(MutablePointp)this.x=p.x;this.y=p.y;,18,/下面的方法对上面的易变域进行了同步操作publicclassDelegatingVehicleTrackerprivatefinalMapunmodifiableMap;publicDelegatingVehicleTracker(Mappoints)publicMutablePointgetLocation(Stringid)publicvoidsetLocation(Stringid,intx,inty),19,对于易变域的引用是不可靠的,不同的线程可能同步到不同的对象上。,20,3)set方法被同步了但是get方法却没有publicsynchronizedvoidset(intvalue)/dosomethingpublicintget()/dosomething,21,一般一个类中既有get方法,也有set方法,都是对一些公共变量进行访问。如果set方法进行了同步,但是get方法没有同步,就会使不同的线程看到对象的不同状态,引起错误。,22,4)方法writeObject同步但是其他方法均没有同步考虑下列程序publicclassMySerialimplementsSerializableprivateStringa;privatetransientStringb;publicMySerial(Stringaa,Stringbb)/dosomethingpublicStringtoString()returna+”n”+b;privatevoidsynchronizedwriteObject(ObjectOutputStreamstream)throwsIOException,23,类中writeObject被同步了,但是方法中其他方法均没有被同步修饰。,24,5)方法readObject使用了Synchronized修饰publicclassMySerialimplementsSerializableprivateStringa;privatetransientStringb;privatevoidsynchronizedreadObject(ObjectInputStreamstream)throwsIOException,ClassNotFoundExceptionstream.defaultReadObject();b=(String)stream.readObject();,25,若实现接口Serializable的类中的方法只被一个线程访问,所以没有必要对其进行同步修饰。,26,6)静态域的不正确初始化publicclassUnsafeLazyInitializationprivatestaticResourceresource;publicstaticResourcegetInstance()if(resource=null)resource=newResource();returnresource;,27,在多线程访问中,如果一个域被声明为静态的,那么它的初始化应该是同步的,否则当多个线程对其进行访问时,很有可能使线程看到一个未被完全初始化的对象。,28,2.可能导致死锁,1)方法占有两个锁时通知解锁考虑下列程序synchronizedvoidfinish(objecto)synchronized(o)o.notify();,29,方法在占有两个锁的时候调用notify或notifyall。如果这个通知是唤醒一个正在等待相同锁的另一个方法,则可能出现死锁。因为wait只能放弃一个锁,但是notify却不能使之得到两个锁,因此notify不会成功。,30,2)存在没有释放锁的路径考虑下列程序voidaction()Lockl=newReentrantLock();l.lock();try/dosomethingcatch(Java.lang.Exceptione)l.unlock;,31,voidaction()Lockl=newReentrantLock();l.lock();try/dosomething();catch(java.lang.Exceptione)thrownewRuntimeException(“xxx”);l.unlock();,32,上面的程序中存在没有释放锁的可执行路径。注意,路径还包括可执行的异常路径。,33,3)互锁引起的死锁考虑下列程序publicclassLeftRightDeadlockprivatefinalObjectleft=newObject();privatefinalObjectright=newObject();publicvoidleftRight()synchronized(left)synchronized(right)/dosomethingpublicvoidrightLeft()synchronized(right)synchronized(left)/dosomething,34,一个对象占有锁想要请求另外的锁,而另一个线程正好占用这个锁,但却在请求第一个线程拥有的锁,因而产生了死锁。,35,4)方法在上了锁之后又循环调用Thread.sleep()对于下列程序Stringname;synchronizedvoidfinish(Objecto)throwsInterruptedExceptionwhile(!name.equals(“root”)Thread.sleep(1000);,36,方法上锁时又循环调用Thread.sleep(),可能导致低性能或者死锁,因为其他线程可能正在等待这个锁,最好是调用wait(),释放该锁让其他的线程使用。,37,5)占有两个锁的时候再请求锁考虑下列程序Stringname;synchronizedvoidwaitForCondition(Objectlock)trysynchronized(lock)name=“aa”;lock.wait();catch(InterruptedExceptione)return;,38,方法持有两个或更多的锁时调用Object.wait()可能导致死锁,wait释放的是正等待的对象上的锁,而不是其他的锁。也就是说,wait等待的锁是这个方法或者对象已经占用的另外一个锁。,39,(3)多线程应用中方法调用的时机或方式不正确,1)错误地调用了notify或notifyall考虑下列程序publicvoidset(intvalue)notify();,40,方法调用了notify或notifyall,但是方法并没有被声明为同步的,即没有被上锁,也就不需要被通知去解锁。,41,2)错误地调用了wait考虑下列程序publicvoidFoothrowsInterruptedExceptionwait();,42,方法调用了wait,但是方法并没有上锁,需要声明为同步的。,43,3)没有改变临界条件就调用了notify考虑下列程序publicsynchronizedvoidset(intvalue)while(occupiedBufferCount=1)/dosomethingbuffer=value;/+occupiedBufferCount;notify();,44,当一个被同步了的方法调用nofity的时候,因为它改变了一些条件而满足了其他线程的要求,故而能使其他线程对公共变量等进行访问。但是,如果没有对临界变量进行修改,就没有必要通知其他的线程。,45,4)调用notify而不是notifyAll考虑下列程序publicsynchronizedvoidput(Vv)throwsInterruptedExceptionwhile(isFull()wait();notify();,46,Java处理器通常应用于多个线程的多个临界条件的处理,调用notify只唤醒一个进程,而这个进程未必是正在等待这个临界条件的进程,所以采用notifyAll以便对线程进行判断。,47,5)无条件等待考虑下列程序booleanisFull=true;publicsynchronizedvoidput(Vv)throwsInterruptedExceptionwhile(isFull=true)wait();/dosomething,48,对wait的调用不是在条件控制流中,线程所等待的条件已经发生,这个线程将无条件不确定地等待下去。,49,6)线程对象直接调用了run方法考虑下列程序publicclassProducerextendsThreadpublicvoidrun()publicclassSharedBufferTestpublicstaticvoidmain(Stringargs)Producerproducer=newProducer(SharedLocation);producer.run();,50,创建的线程需要启动的时候应该调用Thread.start(),而不是直接调用run()方法。,51,4.同一变量的双重验证,对于下列程序publicstaticSingletongetInstance()if(instance=null)synchronized(Singleton.class)/1if(instance=null)/2instance=newSingleton();/3returninstance;,52,双重验证是指对一个对象进行了两次判断是否为空,但这种方式在多线程应用中会引起错误。比如有两个线程:Thread1进入到第3行位置,执行newSingleton(),但是在构造函数刚刚开始的时候被Thread2抢占CPU,Thread2进入getInstance(),判断instance不等于null,返回instance(instance已经执行了new操作,分配了内存空间,但是没有初始化数据)。Thread2利用返回的instance做某些操作,失败或异常,Thread1取得CPU,初始化完成,过程中可能有多个线程取到了没有完成的实例,并用这个实例进行某些操作。,53,5.相互初始化的类,1)循环初始化考虑下列程序publicclassFirstClasspublicFirstClass()SecondClasssecondClass=newSecondClass();publicvoidset(intvalue)value=secondClass.get();,54,publicclassSecondClasspublicSecondClass()FirstClassfirstClass=newFirstClass();publicintget()inta=firstClass.set(b);returna;,55,两个类当中分别有对对方的实例初始化的代码,很多错误是这样衍生而来。,56,2)超类在初始化中使用子类考虑下列程序publicclassCircularClassInitializationstaticclassInnerClassSingletonextendsCircularClassSingletonstaticInnerClassSingletonsingleton=newInnerClassSingleton;staticCircularClassInitializationsingtonfoo=InnerClassSingleton.singleton;,57,在一个类的初始化中,这个类调用一个子类。它的子类还没有被初始化,那么调用后,它也没有被初始化。,
展开阅读全文
相关资源
正为您匹配相似的精品文档
相关搜索

最新文档


当前位置:首页 > 图纸专区 > 课件教案


copyright@ 2023-2025  zhuangpeitu.com 装配图网版权所有   联系电话:18123376007

备案号:ICP2024067431-1 川公网安备51140202000466号


本站为文档C2C交易模式,即用户上传的文档直接被用户下载,本站只是中间服务平台,本站所有文档下载所得的收益归上传人(含作者)所有。装配图网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。若文档所含内容侵犯了您的版权或隐私,请立即通知装配图网,我们立即给予删除!