Java程序设计基础教程第10章-并发编程.ppt

上传人(卖家):三亚风情 文档编号:3372392 上传时间:2022-08-24 格式:PPT 页数:71 大小:4.39MB
下载 相关 举报
Java程序设计基础教程第10章-并发编程.ppt_第1页
第1页 / 共71页
Java程序设计基础教程第10章-并发编程.ppt_第2页
第2页 / 共71页
Java程序设计基础教程第10章-并发编程.ppt_第3页
第3页 / 共71页
Java程序设计基础教程第10章-并发编程.ppt_第4页
第4页 / 共71页
Java程序设计基础教程第10章-并发编程.ppt_第5页
第5页 / 共71页
点击查看更多>>
资源描述

1、u刘刚刘刚 刘伟刘伟 编著编著 支持多线程是现代操作系统的一大特点,多线程的支持多线程是现代操作系统的一大特点,多线程的操作系统因为可以真正意义上地实现多任务同时运行,操作系统因为可以真正意义上地实现多任务同时运行,极大地提升了操作系统的处理速度极大地提升了操作系统的处理速度。跨平台的特性导致跨平台的特性导致JavaJava无法像无法像C C/C+/C+这些语言一样通过调用系统这些语言一样通过调用系统APIAPI来实现来实现多线程程序,所以它在语言本身加了对多线程的支持。多线程程序,所以它在语言本身加了对多线程的支持。这些功能都以面向对象的方式来实现,更加易于理解和这些功能都以面向对象的方式来

2、实现,更加易于理解和使用使用。10.1 10.1 线程与进程线程与进程 在操作系统中,通常将进程看作是系统资源分配和在操作系统中,通常将进程看作是系统资源分配和运行的基本单位,一个任务就是一个进程运行的基本单位,一个任务就是一个进程。进程拥有独。进程拥有独立的系统资源,包含立的系统资源,包含CPUCPU、内存和输入输出端口等,例、内存和输入输出端口等,例如打开的浏览器和如打开的浏览器和WordWord文档,这些相对独立的资源表明文档,这些相对独立的资源表明了进程具有动态性、并发性、独立性和异步性等特点。了进程具有动态性、并发性、独立性和异步性等特点。线程(线程(threadthread)是)是

3、“进程进程”中某个单一顺序的控制中某个单一顺序的控制流,被称为轻量级进程(流,被称为轻量级进程(lightweightlightweight processesprocesses),),是比进程更小的执行单位,也是程序执行流中最小的单是比进程更小的执行单位,也是程序执行流中最小的单位位。一个标准的线程由线程。一个标准的线程由线程IDID、当前指令指针(、当前指令指针(PCPC)、)、寄存器集合和堆栈组成。线程是进程中的一个实体,是寄存器集合和堆栈组成。线程是进程中的一个实体,是被系统独立调度和分配的基本单位,线程在运行中的资被系统独立调度和分配的基本单位,线程在运行中的资源归属于进程,同属于一

4、个进程的所有线程共享该进程源归属于进程,同属于一个进程的所有线程共享该进程所拥有的系统资源。所拥有的系统资源。一个线程可以创建和撤销另一个线程,同一个进程一个线程可以创建和撤销另一个线程,同一个进程中的多个线程也可以并发执行中的多个线程也可以并发执行。由于进程所有资源是固由于进程所有资源是固定的且线程间存在相互制约,使得线程可能处于就绪、定的且线程间存在相互制约,使得线程可能处于就绪、阻塞和运行等状态,令线程的执行呈现出间断性阻塞和运行等状态,令线程的执行呈现出间断性。线程线程之间可以共享代码和数据、实时通信、进行必要的同步之间可以共享代码和数据、实时通信、进行必要的同步操作等操作等。一个程序

5、都至少拥有一个进程;每个进程拥有一个程序都至少拥有一个进程;每个进程拥有一个或者多个线程一个或者多个线程。每个线程都有自己独立的资源和生每个线程都有自己独立的资源和生命周期命周期。进程和线程的最大区别在于,进程是由操作系统来进程和线程的最大区别在于,进程是由操作系统来控制的,而线程则是由进程来控制的控制的,而线程则是由进程来控制的。进程都是相互独。进程都是相互独立的,各自享有各自的内存空间,因此进程间的通信是立的,各自享有各自的内存空间,因此进程间的通信是昂贵且受限的,进程间的转换也是需要开销的;线程则昂贵且受限的,进程间的转换也是需要开销的;线程则共享进程的内存空间,线程通信是便宜的且线程间

6、的转共享进程的内存空间,线程通信是便宜的且线程间的转换也是低成本的,这种低成本低开销的通信也可能会产换也是低成本的,这种低成本低开销的通信也可能会产生意想不到的错误:当多个线程访问同一个变量时,获生意想不到的错误:当多个线程访问同一个变量时,获取到的值是不一样的!取到的值是不一样的!不过,也不必担心,这些问题可不过,也不必担心,这些问题可以通过同步机制和锁机制来消除以通过同步机制和锁机制来消除。10.2 10.2 线程的创建线程的创建 多线程技术是多线程技术是JavaJava语言的重要特性之一,语言的重要特性之一,JavaJava平台平台提供了一套广泛且功能强大的提供了一套广泛且功能强大的AP

7、IAPI、工具和技术、工具和技术。JavaJava编写的程序都运行在编写的程序都运行在JavaJava虚拟机(虚拟机(JVMJVM)中)中。在。在JVMJVM内部,内部,程序的多任务是通过线程来实现的。在同一个程序的多任务是通过线程来实现的。在同一个JVMJVM进程进程中,有且只有一个进程,那就是中,有且只有一个进程,那就是JVMJVM本身,在本身,在JVMJVM环境中,环境中,所有的程序代码都是以线程来运行的。所有的程序代码都是以线程来运行的。JavaJava中的线程有两种实现方式,中的线程有两种实现方式,一种是继承一种是继承ThreadThread类,一种是实现类,一种是实现Runnabl

8、eRunnable接口接口。但是无论是哪种方式,但是无论是哪种方式,线程都要使用到线程都要使用到ThreadThread类及其相关方类及其相关方法法。10.2.1 10.2.1 继承继承ThreadThread类类 Thread Thread类是一个实体类,该类封装了线程的行为,类是一个实体类,该类封装了线程的行为,想要利用想要利用ThreadThread创建一个线程,必须创建一个从创建一个线程,必须创建一个从ThreadThread类导出的子类,并实现类导出的子类,并实现ThreadThread的的run()run()方法,在方法,在runrun()()方法内部可以根据需要编写相应的实现逻辑

9、,最后调用方法内部可以根据需要编写相应的实现逻辑,最后调用ThreadThread类的类的startstart()()方法来启动方法来启动。Thread Thread的构造方法有很多种,每种构造方法用途各的构造方法有很多种,每种构造方法用途各异,如表异,如表10-110-1所示。所示。构造方法构造方法说明说明Thread()构造一个线程对象构造一个线程对象Thread(Runnable target)构造一个线程对象,构造一个线程对象,target是被创建线程是被创建线程的目标对象,它实现了的目标对象,它实现了Runnable接口中的接口中的run()方法方法Thread(String nam

10、e)以指定名称构造一个线程对象以指定名称构造一个线程对象Thread(ThreadGroup group,Runnable target)在指定线程组中构造一个线程对象,使用在指定线程组中构造一个线程对象,使用目标对象的目标对象的target的的run()方法方法Thread(Runnable target,String name)以指定名称构造一个线程对象,使用目标以指定名称构造一个线程对象,使用目标对象对象target的的run()方法方法Thread(ThreadGroup group,Runnable target,String name)在指定的线程组中创建一个指定名称的线在指定的线

11、程组中创建一个指定名称的线程,使用目标对象程,使用目标对象target的的run()方法方法Thread(ThreadGroup group,Runnable target,String name,long stackSize)在指定线程组中构造一个线程对象,以在指定线程组中构造一个线程对象,以name作为线程的名字,使用目标对象作为线程的名字,使用目标对象target的的run()方法,方法,stackSize指定堆栈大小指定堆栈大小表表10-1 Thread类的构造方法类的构造方法 Thread Thread也提供了很多辅助方法,以让线程正常运行也提供了很多辅助方法,以让线程正常运行和方便

12、程序员对线程的控制,其常用方法如表和方便程序员对线程的控制,其常用方法如表10-210-2所示。所示。方法名方法名说明说明static int activeCount()返回线程组中正在运行的线程的数目返回线程组中正在运行的线程的数目void checkAccess()确定当前运行的线程是否有权限修改线程确定当前运行的线程是否有权限修改线程static Thread currentThread()返回当前正在执行的线程返回当前正在执行的线程void destroy()销毁线程,但不回收资源销毁线程,但不回收资源static void dumpStack()显示当前线程的堆栈信息显示当前线程的堆

13、栈信息long getId()返回当前线程的返回当前线程的id值值String getName()返回当前线程的名称返回当前线程的名称int getPriority()返回当前线程的优先级返回当前线程的优先级Thread.State getState()返回当前线程的状态返回当前线程的状态ThreadGroup getThreadGroup()返回当前线程所属的线程组返回当前线程所属的线程组void interrupt()中断线程中断线程boolean isAlive()判断当前线程是否存活判断当前线程是否存活boolean isDaemon()判断当前线程是否是守护线程判断当前线程是否是守护

14、线程boolean isInterrupted()判断本线程是否被中断判断本线程是否被中断void join()等待直到线程死亡等待直到线程死亡void join(long millis)等待最多等待最多millis毫秒,直到线程死亡毫秒,直到线程死亡void run()如果类是使用单独的如果类是使用单独的Runnable对象构造的,将调用对象构造的,将调用Runnable对对象的象的run()方法,否则本方法不做任何事情就返回了,如果是子类方法,否则本方法不做任何事情就返回了,如果是子类继承继承Thread类,请务必实现本方法以覆盖父类类,请务必实现本方法以覆盖父类void setDaemo

15、n(boolean on)将当前线程设置为守护线程将当前线程设置为守护线程void setName(String name)将当前线程名称修改为将当前线程名称修改为namevoid setPriority(int newPriority)设置当前线程的优先级设置当前线程的优先级static void sleep(long millis)线程休眠线程休眠millis毫秒毫秒void start()启动线程,启动线程,JVM会自动调用会自动调用run()方法方法static void yield()暂停当前线程,同时允许其他线程运行暂停当前线程,同时允许其他线程运行表表10-2 常用的常用的Thr

16、ead方法方法 在以前的案例中,当需要执行当前类时,每个类都在以前的案例中,当需要执行当前类时,每个类都有一个有一个mainmain()()方法方法。该方法是类的入口,。该方法是类的入口,JVMJVM会找到该入会找到该入口方法并运行,此时产生了一个线程,该线程便是主线程。口方法并运行,此时产生了一个线程,该线程便是主线程。当当mainmain()()方法运行结束后,主线程运行完成,方法运行结束后,主线程运行完成,JVMJVM也就随也就随即退出了。即退出了。JVMJVM负责对进程、线程进行管理,负责对进程、线程进行管理,JVMJVM分配时分配时间片(间片(CPUCPU时间)给线程,线程按照系统的

17、设定轮流获取时间)给线程,线程按照系统的设定轮流获取时间片执行,切换时间很短,在对线程运行效率要求不严时间片执行,切换时间很短,在对线程运行效率要求不严格的场景下可以忽略不计。格的场景下可以忽略不计。运行结果如图运行结果如图10-110-1所示。所示。图图10-1 运行结果运行结果 由于每个线程运行的次数较少,所以线程默认优先由于每个线程运行的次数较少,所以线程默认优先级下的运行随机性不是很明显,但通过方框标注的线程级下的运行随机性不是很明显,但通过方框标注的线程Thread-3Thread-3的运行可以看出,实际上线程运行并不是顺序的运行可以看出,实际上线程运行并不是顺序的。的。运行结果如图

18、运行结果如图10-210-2所示。所示。图图10-2 运行结果运行结果 运行结果如图运行结果如图10-310-3所示。所示。图图10-3 运行结果运行结果 启动启动ThreadThread类时,必须要使用类时,必须要使用startstart()()方法启动一方法启动一个线程,如果直接调用个线程,如果直接调用runrun()()方法,则方法,则JVMJVM认为这只是一认为这只是一次普通的方法调用,而非需要启动一个线程在执行次普通的方法调用,而非需要启动一个线程在执行runrun()()方法内部的逻辑方法内部的逻辑。读者在使用线程的时候切记读者在使用线程的时候切记。在在startstart()()

19、方法调用后也可以看出,运行的是两个线程的方法调用后也可以看出,运行的是两个线程的代码,而且它们之间互不干扰地同时执行代码,而且它们之间互不干扰地同时执行。所以一些工所以一些工作交给线程去做的时候,启动一个新线程的线程可以做作交给线程去做的时候,启动一个新线程的线程可以做自己想做的其他事情,而无需等到新线程的执行结束自己想做的其他事情,而无需等到新线程的执行结束。10.2.2 10.2.2 实现实现RunnableRunnable接口接口 实现多线程的另一个方式是实现实现多线程的另一个方式是实现RunnableRunnable接口接口。RunnableRunnable只有一个方法,即只有一个方法

20、,即runrun()()方法,该方法需方法,该方法需要由一个实现了此接口的类来实现要由一个实现了此接口的类来实现。实现了实现了RunnableRunnable接接口的类的对象需要由口的类的对象需要由ThreadThread类的一个实例内部运行它,类的一个实例内部运行它,其本身不能直接运行其本身不能直接运行。运行结果如图运行结果如图10-410-4所示。所示。图图10-4 运行结果运行结果 图图10-410-4只摘取部分的输出内容,从内容上看,实现只摘取部分的输出内容,从内容上看,实现RunnableRunnable和继承和继承ThreadThread都能达到相同目的,都能启动一都能达到相同目的

21、,都能启动一个新线程。个新线程。唯一的区别是唯一的区别是RunnableRunnable对象必须包装成对象必须包装成ThreadThread对象后才能运行对象后才能运行。如果查看如果查看ThreadThread和和RunnableRunnable类源码会发现,类源码会发现,ThreadThread类实际上是类实际上是RunnableRunnable的一个实现的一个实现类类。可能有读者会对可能有读者会对RunnableRunnable接口的存在产生疑问,毕接口的存在产生疑问,毕竟这个接口只有一个竟这个接口只有一个runrun()()方法方法。RunnableRunnable的存在是因的存在是因为

22、为JavaJava的类有且只能有一个直接父类,如果只是提供了的类有且只能有一个直接父类,如果只是提供了ThreadThread类,那么想要继承其他类且需要同时继承类,那么想要继承其他类且需要同时继承ThreadThread类的这个子类,在实现这种继承逻辑上会产生很多困难,类的这个子类,在实现这种继承逻辑上会产生很多困难,而而RunnableRunnable则避免了这种尴尬局面的出现,在则避免了这种尴尬局面的出现,在JavaJava中,中,一个类是可以实现多个接口的。一个类是可以实现多个接口的。10.3 10.3 线程的调度线程的调度 在在JVMJVM中,线程只有在获取了中,线程只有在获取了CP

23、UCPU分配的时间片后才分配的时间片后才会真正地执行,在线程创建后到死亡的这个过程中还有会真正地执行,在线程创建后到死亡的这个过程中还有其他的线程状态,这些状态组成了线程的生命周期。其他的线程状态,这些状态组成了线程的生命周期。10.3.1 10.3.1 线程的生命周期线程的生命周期 如同生命体一般,线程也有生命周期,线程的生命如同生命体一般,线程也有生命周期,线程的生命周期是从线程新建开始,一直持续到线程死亡周期是从线程新建开始,一直持续到线程死亡。在新建。在新建和死亡之间,线程还有就绪、阻塞和运行状态,一个线和死亡之间,线程还有就绪、阻塞和运行状态,一个线程会在这程会在这5 5种状态间转换

24、,最终完成自己的使命。种状态间转换,最终完成自己的使命。线程的状态及转换关系如图线程的状态及转换关系如图10-510-5所示。所示。图图10-5 Java线程状态转换图线程状态转换图 线程各个状态的说明如下线程各个状态的说明如下。新建:当创建一个新建:当创建一个ThreadThread类和它的子类、对象后,线程就类和它的子类、对象后,线程就处于新建状态,这种状态的线程并不具备运行的能力,该操处于新建状态,这种状态的线程并不具备运行的能力,该操作对于系统而言,仅仅消耗普通对象创建时会消耗的非作对于系统而言,仅仅消耗普通对象创建时会消耗的非CPUCPU资资源。源。就绪:当处于新建状态的线程调用就绪

25、:当处于新建状态的线程调用startstart()()方法被启动之方法被启动之后,线程将进入线程队列等待后,线程将进入线程队列等待CPUCPU时间片,进行执行。时间片,进行执行。此时的此时的线程才具备了运行的能力,一旦获取了时间片线程就执行线程才具备了运行的能力,一旦获取了时间片线程就执行。运行:就绪状态的线程获取了时间片之后,就进入了运行运行:就绪状态的线程获取了时间片之后,就进入了运行状态,此时线程会执行状态,此时线程会执行runrun()()方法内的代码逻辑方法内的代码逻辑。线程一旦进线程一旦进入运行状态,就与启动该线程的线程没有任何关系了,两者入运行状态,就与启动该线程的线程没有任何关

26、系了,两者平行运行,互不影响平行运行,互不影响。阻塞:线程在运行的过程中因资源无法满足、前驱阻塞:线程在运行的过程中因资源无法满足、前驱任务没有完成或者被调用阻塞方法都会导致线程进入阻任务没有完成或者被调用阻塞方法都会导致线程进入阻塞状态塞状态。阻塞状态的线程会让出。阻塞状态的线程会让出CPUCPU,然后等待,直到,然后等待,直到引起阻塞的条件不存在了,线程会重新进入就绪状态,引起阻塞的条件不存在了,线程会重新进入就绪状态,等待等待CPUCPU时间片。时间片。死亡:不具备继续运行能力的线程就处于死亡状态死亡:不具备继续运行能力的线程就处于死亡状态。线程在运行完毕后会自然进入死亡状态正常死亡,在

27、运线程在运行完毕后会自然进入死亡状态正常死亡,在运行过程中也会因为异常退出而导致非正常死亡行过程中也会因为异常退出而导致非正常死亡。需要说明的是,在大部分系统中都支持线程优先级需要说明的是,在大部分系统中都支持线程优先级的设定的设定。在相同的情况下,优先级高的线程会优先获得。在相同的情况下,优先级高的线程会优先获得CPUCPU时间片进行执行。时间片进行执行。10.3.2 10.3.2 线程的优先级线程的优先级 同同VIPVIP和超级和超级VIPVIP一样,线程也是有优先级的,线程一样,线程也是有优先级的,线程的优先级可以通过方法的优先级可以通过方法getPrioritygetPriority(

28、)()获取,为了使重获取,为了使重要的事情优先完成,要的事情优先完成,JavaJava也提供了也提供了setPrioritysetPriority()()方法方法给线程设定优先级给线程设定优先级。但是需要指出的是,。但是需要指出的是,JVMJVM是运行在是运行在所属系统上的一个线程,线程的创建和执行还是需要基所属系统上的一个线程,线程的创建和执行还是需要基于对应的系统的,所以,在一些不支持线程优先级策略于对应的系统的,所以,在一些不支持线程优先级策略的系统中,的系统中,JavaJava设定的优先级并不起作用,这一点是读设定的优先级并不起作用,这一点是读者一定要引起注意的。者一定要引起注意的。运

29、行结果如图运行结果如图10-610-6所示。所示。图图10-6 运行结果运行结果 运行结果如图运行结果如图10-610-6所示。所示。从案例从案例10-510-5的输出结果可以看出,在的输出结果可以看出,在JavaJava中线程是中线程是有默认优先级的,默认情况下线程的优先级为有默认优先级的,默认情况下线程的优先级为5 5,是普,是普通优先级。通优先级。JavaJava中定义了线程的优先级为中定义了线程的优先级为1 11010,数字,数字越大,优先级越高。越大,优先级越高。对于优先级,读者需要注意以下几点对于优先级,读者需要注意以下几点。(1 1)并不是线程优先级高的线程一定会比线程优先级)并

30、不是线程优先级高的线程一定会比线程优先级低的线程先执行,它只是会比线程优先级低的线程有更低的线程先执行,它只是会比线程优先级低的线程有更多的机会先执行。多的机会先执行。(2 2)JavaJava的线程优先级取决于的线程优先级取决于JVMJVM运行的系统,线程优运行的系统,线程优先级策略也依赖于系统,这导致了可能在一个系统中优先级策略也依赖于系统,这导致了可能在一个系统中优先级不同的线程在另一个系统中优先级相同,甚至对于先级不同的线程在另一个系统中优先级相同,甚至对于某些不支持线程优先级调度策略的系统,某些不支持线程优先级调度策略的系统,JavaJava定义的优定义的优先级完全无效。先级完全无效

31、。10.3.3 10.3.3 线程插队线程插队 线程的魅力是充分地利用线程的魅力是充分地利用CPUCPU,使得程序在单位时间,使得程序在单位时间内充分地利用内充分地利用CPUCPU而提升程序的处理效率。而提升程序的处理效率。但由于线程运但由于线程运行顺序的不确定性加上当代操作系统核心数的提升,导致行顺序的不确定性加上当代操作系统核心数的提升,导致在某些情况下线程无法明确前驱任务是否完成在某些情况下线程无法明确前驱任务是否完成。为了保证为了保证前驱任务完成后才执行当前线程,可以调用前驱任务完成后才执行当前线程,可以调用joinjoin()()方法方法。join()join()会阻塞当前线程直到插

32、队线程执行完毕之后才会会阻塞当前线程直到插队线程执行完毕之后才会继续执行继续执行。运行结果如图运行结果如图10-710-7所示。所示。图图10-7 运行结果运行结果10.3.4 10.3.4 线程休眠线程休眠 ThreadThread类中有类中有sleepsleep()()方法方法。该方法可以让当前线。该方法可以让当前线程休眠并让出程休眠并让出CPUCPU,使得其他线程可以获取,使得其他线程可以获取CPUCPU进行执行。进行执行。对于周期性很强的系统,调用线程休眠是最好的形式,对于周期性很强的系统,调用线程休眠是最好的形式,线程休眠时只会等待休眠结束且不占用线程休眠时只会等待休眠结束且不占用C

33、PUCPU资源,等到资源,等到线程休眠结束后会进入就绪状态等待时间片继续执行。线程休眠结束后会进入就绪状态等待时间片继续执行。运行结果如图运行结果如图10-810-8所示。所示。图图10-8 运行结果运行结果10.3.5 10.3.5 同步与互斥同步与互斥 寄宿学校都会有排队打水的场景,许多人同时等待寄宿学校都会有排队打水的场景,许多人同时等待一个开水阀准备接开水一个开水阀准备接开水。当前面一个人接水完毕后,后当前面一个人接水完毕后,后面一个人才能开始接水,如果接水的动作不是同步的,面一个人才能开始接水,如果接水的动作不是同步的,那么就会出现问题那么就会出现问题。运行结果如图运行结果如图10-

34、910-9所示。所示。图图10-9 运行结果运行结果 通过案例通过案例10-810-8不难发现,没有添加同步的接水场景不难发现,没有添加同步的接水场景有些莫名奇妙,明明王有些莫名奇妙,明明王1 1先开始打水,结果却是王先开始打水,结果却是王0 0第一第一个打完水,而且,王个打完水,而且,王1 1还没有接完水,后面的人就开始还没有接完水,后面的人就开始了接水,场面混乱不堪。了接水,场面混乱不堪。synchronizedsynchronized是是JavaJava中的关键字,是一种同步锁中的关键字,是一种同步锁。在多线程场景中,它用于控制线程对同一个代码片段是在多线程场景中,它用于控制线程对同一个

35、代码片段是否可以并发执行否可以并发执行。它修饰的对象有以下几种它修饰的对象有以下几种。修饰代码块:被修饰的代码块被称为同步语句块,修饰代码块:被修饰的代码块被称为同步语句块,其作用的范围是大括号其作用的范围是大括号括起来的代码,作用的对象是括起来的代码,作用的对象是调用这个代码块的对象调用这个代码块的对象。修饰方法:被修饰的方法称为同步方法,其作用的修饰方法:被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象范围是整个方法,作用的对象是调用这个方法的对象。修饰静态方法:其作用的范围是整个静态方法,作修饰静态方法:其作用的范围是整个静态方法,作用的对象是这个类的所有

36、对象用的对象是这个类的所有对象。修饰类:其作用的范围是修饰类:其作用的范围是synchronizedsynchronized后面括号括后面括号括起来的部分,作用的对象是这个类的所有对象起来的部分,作用的对象是这个类的所有对象。对于成员变量的修饰,相当于修饰代码块,作用于对于成员变量的修饰,相当于修饰代码块,作用于类的一个实例,对另一个实例不起作用;对于静态变量类的一个实例,对另一个实例不起作用;对于静态变量的修饰类似于静态方法,作用于类的所有实例。的修饰类似于静态方法,作用于类的所有实例。运行结果如图运行结果如图10-1010-10所示。所示。图图10-10 运行结果运行结果 该案例使用的是该

37、案例使用的是synchronizedsynchronized修饰静态成员变量的方修饰静态成员变量的方式式。使用该方式会对这个类的所有对象进行同步控制,也就。使用该方式会对这个类的所有对象进行同步控制,也就是说,每一次只会有一个该类的对象执行是说,每一次只会有一个该类的对象执行synchronizedsynchronized修修饰的代码内容,其他线程对该类的这个对象和该类的其他对饰的代码内容,其他线程对该类的这个对象和该类的其他对象都必须等待当前线程执行完毕方可执行。象都必须等待当前线程执行完毕方可执行。有时候为了实现这种同步,也会使用信号量进行控有时候为了实现这种同步,也会使用信号量进行控制,

38、具体案例如下制,具体案例如下:运行结果如图运行结果如图10-1110-11所示。所示。图图10-11 运行结果运行结果 其中其中flagflag相当于一个信号量,当有线程访问公共资相当于一个信号量,当有线程访问公共资源的时候会首先检测信号量,如果可用,则修改信号量源的时候会首先检测信号量,如果可用,则修改信号量防止其他线程进入,否则就进入等待,当访问完成之后防止其他线程进入,否则就进入等待,当访问完成之后修改信号量,并将所有处于该信号量等待状态的线程唤修改信号量,并将所有处于该信号量等待状态的线程唤醒,给其他线程获取该信号量的机会。醒,给其他线程获取该信号量的机会。运行结果如图运行结果如图10

39、-1210-12所示。所示。图图10-12 运行结果运行结果 生产生产-消费者模型是线程同步中最著名的同步问题,消费者模型是线程同步中最著名的同步问题,在该模型中,生产者负责生产数据,但数据需要在可缓在该模型中,生产者负责生产数据,但数据需要在可缓存的数量之内,如果超出库存则需要等待数据被消费后存的数量之内,如果超出库存则需要等待数据被消费后再插入;消费者消费库存数据则恰恰相反,如果库存空再插入;消费者消费库存数据则恰恰相反,如果库存空了则需要等待,等到有库存以后再进行消费。从案例了则需要等待,等到有库存以后再进行消费。从案例10-1110-11中可以发现,虽然消费者和生产者在消费和生产中可以

40、发现,虽然消费者和生产者在消费和生产的层面上是异步进行的,但是他们之间必须保持同步,的层面上是异步进行的,但是他们之间必须保持同步,生产者不能在库存满了之后还继续增加库存,消费者也生产者不能在库存满了之后还继续增加库存,消费者也不能在一个空的库存中获取产品。不能在一个空的库存中获取产品。10.3.6 10.3.6 死锁问题死锁问题 在日常生活中偶尔会碰到这种情况,买肉的说:在日常生活中偶尔会碰到这种情况,买肉的说:“我只有拿到了肉我才会给卖肉的钱!我只有拿到了肉我才会给卖肉的钱!”而卖肉的则说:而卖肉的则说:“我只有拿到了钱才会给买肉的肉!我只有拿到了钱才会给买肉的肉!”这种争执如果得这种争执

41、如果得不到劝和必然导致买肉的买不到肉,卖肉的卖不出去肉,不到劝和必然导致买肉的买不到肉,卖肉的卖不出去肉,这种这种“死脑筋死脑筋”的场景在计算机系统中被称为死锁。的场景在计算机系统中被称为死锁。死锁是指多个进程因竞争资源而造成的一种相互等死锁是指多个进程因竞争资源而造成的一种相互等待的僵局,如果没有外力的作用,必然导致无限的等待待的僵局,如果没有外力的作用,必然导致无限的等待。例如,例如,A A进程占用了输入设备,在释放前请求了打印机进程占用了输入设备,在释放前请求了打印机设备,但是打印机被设备,但是打印机被B B进程占用,进程占用,B B在释放前需要请求输在释放前需要请求输入设备,这样,入设

42、备,这样,A A进程和进程和B B进程就会无休止地等待,进入进程就会无休止地等待,进入死锁状态。死锁状态。死锁是由系统资源的竞争导致系统资源不足以及资死锁是由系统资源的竞争导致系统资源不足以及资源分配不当或进程运行过程中请求和释放资源的顺序不源分配不当或进程运行过程中请求和释放资源的顺序不当导致的当导致的。死锁的产生有。死锁的产生有4 4个必要条件。个必要条件。互斥条件:一个资源每次只能被一个进程使用,即互斥条件:一个资源每次只能被一个进程使用,即一段时间内这个资源只能被一个进程占用,其他进程请一段时间内这个资源只能被一个进程占用,其他进程请求资源,请求线程只能等待求资源,请求线程只能等待。请

43、求与保持条件:进程已经保持了至少一个资源,请求与保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源已被其他进程占用,但又提出了新的资源请求,而该资源已被其他进程占用,此时请求进程被阻塞,但对自己已获得的资源保持不放。此时请求进程被阻塞,但对自己已获得的资源保持不放。不可剥夺条件:进程所获得的资源在未使用完毕之不可剥夺条件:进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,即只能由获得该资源的前,不能被其他进程强行夺走,即只能由获得该资源的进程自己来释放(只能是主动释放进程自己来释放(只能是主动释放)。)。循环等待条件:若干进程间形成首尾相接循环等待循环等待条件:若干

44、进程间形成首尾相接循环等待资源的关系资源的关系。死锁只能在上述死锁只能在上述4 4个条件都满足的条件下才能产生。个条件都满足的条件下才能产生。运行结果如图运行结果如图10-1310-13所示。所示。图图10-13 运行结果运行结果 这是比较简单的竞争导致的死锁,案例这是比较简单的竞争导致的死锁,案例10-1210-12中,线程中,线程t1t1获得了一个对象锁获得了一个对象锁objALockobjALock,释放前请求,释放前请求objBLockobjBLock锁,锁,而而t2t2线程则是获取了线程则是获取了objBLockobjBLock锁,释放前请求锁,释放前请求objALockobjALo

45、ck锁,锁,由于双方都要求在获取对方的锁后释放锁,导致了类似于先由于双方都要求在获取对方的锁后释放锁,导致了类似于先给钱还是先给肉的矛盾而产生死锁。给钱还是先给肉的矛盾而产生死锁。死锁产生的条件有四个,所以想要避免死锁,只需要死锁产生的条件有四个,所以想要避免死锁,只需要破坏四个条件中的任意一个就能实现破坏四个条件中的任意一个就能实现。例如:可以避免嵌套。例如:可以避免嵌套锁,嵌套锁是死锁产生的高发场景;避免无限期等待,可以锁,嵌套锁是死锁产生的高发场景;避免无限期等待,可以设置等待超时时间;一次只对一个资源获取锁,当需要获取设置等待超时时间;一次只对一个资源获取锁,当需要获取另一个锁的时候,

46、先释放当前锁。另一个锁的时候,先释放当前锁。10.4 10.4 多线程多线程 理解了线程的创建、同步和死锁问题之后,就是领理解了线程的创建、同步和死锁问题之后,就是领会多线程真正魅力的时候了,相较于串行执行的简单和会多线程真正魅力的时候了,相较于串行执行的简单和耗时,多线程则稍显复杂且高效耗时,多线程则稍显复杂且高效。军事天才拿破仑可以军事天才拿破仑可以同时听取数位将军的汇报并做出相应的军事部署,就是同时听取数位将军的汇报并做出相应的军事部署,就是因为他具有多线程可以同时处理多个任务的能力因为他具有多线程可以同时处理多个任务的能力。10.4.1 10.4.1 线程池技术线程池技术 JavaJa

47、va中的线程池技术是运行场景最多的并发框架,中的线程池技术是运行场景最多的并发框架,几乎所有需要异步或者并发执行任务的程序都可以使用几乎所有需要异步或者并发执行任务的程序都可以使用线程池技术线程池技术。合理使用线程池技术可以降低线程创建和合理使用线程池技术可以降低线程创建和销毁造成的消耗,提高相应速度和提高线程的可管理性销毁造成的消耗,提高相应速度和提高线程的可管理性。线程池的处理流程如下线程池的处理流程如下。(1 1)线程池判断核心线程池是否都在执行任务,如果)线程池判断核心线程池是否都在执行任务,如果不是,创建一个新的线程来执行任务,如果核心线程池不是,创建一个新的线程来执行任务,如果核心

48、线程池里的线程都在执行任务,则进入下一个流程。里的线程都在执行任务,则进入下一个流程。(2 2)线程池判断工作队列是否已经满了。)线程池判断工作队列是否已经满了。如果没有满,如果没有满,将新提交的任务存储到这个工作队列中,如果满了,则将新提交的任务存储到这个工作队列中,如果满了,则进入下一个流程进入下一个流程。(3 3)线程池判断线程池的线程是否都处于工作状态,)线程池判断线程池的线程是否都处于工作状态,如果没有,创建一个新的工作线程来执行任务,如果满如果没有,创建一个新的工作线程来执行任务,如果满了,则交给饱和策略来处理这个任务。了,则交给饱和策略来处理这个任务。Java Java通过通过E

49、xecutorsExecutors提供如下提供如下4 4种线程池。种线程池。(1 1)newCachedThreadPoolnewCachedThreadPool:创建一个可缓存线程池,:创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,如果线程池长度超过处理需要,可灵活回收空闲线程,如无可回收,则创建线程。如无可回收,则创建线程。(2 2)newFixedThreadPoolnewFixedThreadPool:创建一个定长线程池,可:创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。控制线程最大并发数,超出的线程会在队列中等待。(3 3)newSche

50、duledThreadPoolnewScheduledThreadPool:创建一个定长线程池,:创建一个定长线程池,支持定时及周期性任务执行。支持定时及周期性任务执行。(4 4)newSingleThreadExecutornewSingleThreadExecutor:创建一个单线程化:创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证的线程池,它只会用唯一的工作线程来执行任务,保证所有的任务按照指定顺序(所有的任务按照指定顺序(FIFOFIFO、LIFOLIFO、优先级)执行。、优先级)执行。缓存线程池使用得比较普遍,而计划任务线程池的缓存线程池使用得比较普遍,而计划任务线

展开阅读全文
相关资源
猜你喜欢
相关搜索
资源标签

当前位置:首页 > 办公、行业 > 各类PPT课件(模板)
版权提示 | 免责声明

1,本文(Java程序设计基础教程第10章-并发编程.ppt)为本站会员(三亚风情)主动上传,163文库仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。
2,用户下载本文档,所消耗的文币(积分)将全额增加到上传者的账号。
3, 若此文所含内容侵犯了您的版权或隐私,请立即通知163文库(发送邮件至3464097650@qq.com或直接QQ联系客服),我们立即给予删除!


侵权处理QQ:3464097650--上传资料QQ:3464097650

【声明】本站为“文档C2C交易模式”,即用户上传的文档直接卖给(下载)用户,本站只是网络空间服务平台,本站所有原创文档下载所得归上传人所有,如您发现上传作品侵犯了您的版权,请立刻联系我们并提供证据,我们将在3个工作日内予以改正。


163文库-Www.163Wenku.Com |网站地图|