Java并发编程1:实践基础

进程与线程 

      进程通常由三部分组成。一部分是程序,一部分数据集合,另一部分被称为进程控制块(ProcessControlBlock,简记PCB),每个进程基本上有自己独立的代码和数据空间,独立的程序计数器等上下文环境,进程切换的开销是比较大的。  

      进程具备并发性的特点,这种并发性是不同的进程之间反映出来的,不同的进程有不同进程空间,进程之间的切换消耗比较大。那么就考虑到引入线程的概念,在进程的内部引入并发性,一个进程可以创建多个线程,线程之间具备并发性。不同的线程之间可以共享进程的地址空间和数据。

    一般的讲,线程是一个程序,或者进程内部的一个顺序控制流。线程本身不能独立运行,必须在进程中执行,使用进程的地址空间。每个线程有自己单独的程序计数器。一个进程内部包含多个顺序控制流,或者并发执行多种运算,就是多线程。每个程序执行时都会产生一个进程,而每一个进程至少要有一个主线程。这个线程其实是进程执行的一条线索(Thread),除了主线程外你还可以给进程增加其它的线程,也即增加其它的执行线索,由此在某种程度上可以看成是给一个应用程序增加了多任务功能。当程序运行后,您可以根据各种条件挂起或运行这些线程,尤其在多CPU 的环境中,这些线程是可以并发或者并行运行的。
    多线程就是在一个进程内有多个线程。从而使一个应用程序有了多任务的功能。有人会问:多进程技术不是也可以实现这一点吗?但是创建进程的高消耗(每个进程都有独立的数据和代码空间),进程之间通信的不方便(消息机制),进程切换的时间太长,这些导致了多线程的提出。对于单CPU 来说(没有开启超线程),在同一时间只能执行一个线程,所以如果想实现多任务,那么就只能每个进程或线程获得一个时间片,在某个时间片内,只能一个线程执行,然后按照某种策略换其他线程执行。由于时间片很短,这样给用户的感觉是同时有好多线程在执行。但是线程切换是有代价的,因此如果采用多进程,那么就需要将线程所隶属的该进程所需要的内存进行切换,这时间代价是很多的。而线程切换代价就很少,线程是可以共享内存的。所以采用多线程在切换上花费的比多进程少得多。但是,线程切换还是需要时间消耗的。所以采用一个拥有两个线程的进程执行所需要的时间比一个线程的进程执
行两次所需要的时间要多一些。即采用多线程不会提高程序的执行速度,反而会降低速度,但是对于用户来说,可以减少用户的响应时间。上述结果只是针对单CPU,如果对于多CPU或者CPU 采用超线程技术的话,采用多线程技术还是会提高程序的执行速度的。因为单线程只会映射到一个CPU 上,而多线程会映射到多个CPU 上,超线程技术本质是多线程硬件化,所以也会加快程序的执行速度。

    总之,进程内的同一类线程可以共享代码和数据空间,每个线程有独立的运行栈和程序计数器,切换的开销比较小,灵活性高。在支持超线程和多核的CPU 上,多线程能够并发或者并行执行,可以在同一时间段内完成不同的任务,或者加快程序的执行。同一进程内的多个线程,调度比较灵活,可以相互协调和协作共同完成特定任务。

java中的多线程

一般在java 中有两种比较典型的构造线程的方法:1)继承Thread 类,重写run()方法;2)把线程体从Thread类中独立出来,形成单独的线程目标对象,就是实现Runnable 接口及其run()方法。这两种方法都是通过 Thread 类的start()方法启动线程的。

 java中的线程池    

JDK5.0 提供了创建线程池并执行线程的方法。

        线程有时称为轻量级进程。与进程一样,它们拥有通过程序运行的独立的并发路径,并且每个线程都有自己的程序计数器,称为堆栈和本地变量。然而,线程存在于进程中,它们与同一进程内的其他线程共享内存、文件句柄以及每进程状态。一个进程中的线程是在同一个地址空间中执行的,所以多个线程可以同时访问相同对象,并且它们从同一堆栈中分配对象。创建线程会使用相当一部分内存,其中包括有堆栈,以及每线程数据结构。如果创建过多线程,其中每个线程都将占用一些 CPU 时间,结果将使用许多内存来支持大量线程,每个线程都运行得很慢。这样就无法很好地使用计算资源。线程的目标执行对象可以共享线程池中有限数目的线程对象。一般的服务器都需要线程池,比如Web、FTP 等服务器,不过它们一般都自己实现了线程池,比如Tomcat、Resin 和Jetty 等,现在JDK 本身提供了,我们就没有必要重复造车轮了,直接使用就可以,何况使用也很方便,性能也非常高。

        使用JDK 提供的线程池一般分为3 步:1)创建线程目标对象,可以是不同的,例如程序中的Runnner;2)使用Executors 创建线程池,返回一个ExecutorService类型的对象;3)使用线程池执行线程目标对象,exec.execute(run),最后,结束线程池中的线程,exec.shutdown()。
API:java.util.concurrent.Executors extends Object该类主要定义了一些工厂方法和工具方法,其中最重要的就是创建各种线程池。

public static ExecutorService newFixedThreadPool(int nThreads)创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程

public static ThreadFactory defaultThreadFactory()返回用于创建新线程的默认线程工厂

public static ExecutorService newCachedThreadPool()创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们。对于执行很多短期异步任务的程序而言,这些线程池通常可提高程序性能

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。

void execute(Runnable command)在未来某个时间执行给定的命令。该命令可能在新的线程、已入池的线程或者正调用的线程中执行,这由 Executor 实现决定。

 void shutdown()启动一次顺序关闭,执行以前提交的任务,但不接受新任务。如果已经关闭,则调用没有其他作用。

线程的基本控制

线程创建后,可以执行start()方法启动线程,根据线程任务的特性和线程之间的协调性要求,需要对线程进行控制。对线程的控制通常是通过调用Thread 对象的方法实现的,主要有sleep()、suspend()、resume()、join()、interrupt()、wait()、stop()和stop ()。一般情况下方法的调用会引起线程状态的转变。

使用 Sleep 暂停执行:Thread.sleep()使当前线程的执行暂停一段指定的时间,这可以有效的使应用程序的其他线程或者运行在计算机上的其他进程可以使用处理器时间。该方法不会放弃除CPU 之外的其它资源。Sleep 有两个重载的版本,一个以毫秒指定睡眠时间,另一个以纳秒指定睡眠时间,但并不保证这些睡眠时间的精确性,因为他们受到系统计时器和调度程序精度和准确性的影响。另外中断(interrupt)可以终止睡眠时间,在任何情况下,都不能假设调用sleep 就会按照指定的时间精确的挂起线程。

使用 join 等待另外一个线程结束

Join 方法让一个线程等待另一个线程的完成,如果t1,t2 是两个Thread 对象,在t1 中调用t2.join(),会导致t1 线程暂停执行,直到t2 的线程终止。Join 的重载版本允许程序员指定等待的时间,但是和sleep 一样,这个时间是不精确的。

使用中断(Interrupt)取消线程

已经启动的线程是活跃的,即isAlive()方法返回true,线程终止之前一直是活跃的。有三种方法可以使线程终止:1)run()方法正常返回;2)run()方法意外结束;3)应用程序终止。经常会碰到这样的情况,我们创建了执行某项工作的线程,然后在他完成之前需要取消这项工作。要使线程在完成任务之前可取消,必须采取一定的措施,但应该是一个清晰而安全的机制使线程终止。我们可以通过中断(Thread.interrupt)线程来请求取消,并且让线程来监视并响应中断。中断请求通常是用户希望能够终止线程的执行,但并不会强制终止线程,但是它会中断线程的睡眠状态,比如调用sleep 和wait 方法后。线程自己检查中断状态并终止线程比直接调用 stop()放要安全很多,因为线程可以保存自己的状态。并且stop()方法已经不推荐使用了。和中断线程有关的方法有:1)interrupt,向线程发送中断,2)isInterrupted,测试线程是否已经被中断;3)Interrupted,测试当前线程是否已经被中断,随后清楚线程“中断”状态的静态方法。线程的中断状态只能有线程自己清除,当线程侦测到自己被中断时,经常需要在响应中断之前做某些清除工作,这些清除工作可能涉及那些在线程仍然保持中断状态时会受到影响的操作。如果被中断的线程正在执行 sleep,或者wait 方法,就会抛出InterruptedException 异常。这种抛出异常的中断会清除线程的中断状态。

使用 Stop 终止线程

在 Thread 类中提供了Stop 方法了强迫线程停止执行。但是现在已经过时了。该方法具有固有的不安全性用 ,Thread.stop 来终止线程将释放它已经锁定的所有监视器(作为沿堆栈向上传播的未检查ThreadDeath 异常的一个自然后果)。如果以前受这些监视器保护的任何对象都处于一种不一致的状态,则损坏的对象将对其他线程可见,这有可能导致任意的行为。

结束程序的执行

每个应用程序都从执行 main 的线程开始的,如果应用程序没有创建任何其他的线程,那么main 方法返回时,应用程序就结束了,但是如果应用程序创建了其他线程,就要根据
线程的类型分情况来考虑了。线程一般分为两种:用户线程和守护线程。用户线程的存在可以使应用程序保持运行状态,而守护线程则不会。当最后一个用户线程结束时,所有守护线程都会被终止,应用程序也随之结束。守护线程的终止,很像调用destroy 所产生的终止,事发突然,没有机会做任何清楚,所以应该考虑清楚,用守护线程执行哪种类型的任务。使用Thread.setDaemon(true)可以把线程标记为守护线程。默认情况下,线程的守护状态继承自创建它的线程。一般 main 线程是程序运行时第一个启动的线程,称作初始线程。如果希望应用程序在初始线程消亡后就退出,就可以把所有创建出来的线程都标记为守护线程。我们也可以通过调用 System,或者Runtime 的exit 方法来强制应用程序结束,这个方法将终止Java 虚拟机的当前执行过程。许多类会隐式的在应用程序中创建线程,比如图形用户界面,并创建了特殊的线程来处理事件。有些是守护线程,有些不是。如果没有更好的办法,那么就可以用exit 方法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值