Java并发(一):并发的多面性与基本的线程机制前缘

Java并发(一):并发的多面性与基本的线程机制前缘

一、并发的多面性
并发编程令人困惑的一个主要原因是:使用并发时需要解决的问题有多个,而实现并发的方式也有多种,并且在这两者之间没有明显的映射关系(通常只具有模糊的界线)。
用并发解决的问题大体上可以分为“速度”和“设计可管理性”两种。

  1. 更快的执行
    对于多处理器的机器,为了使程序运行得更快,并发赋予了利用额外处理器运行程序的能力。但是,并发通常是提高运行在单处理器上的程序的性能
    在这里插入图片描述
    表面上看,将程序的所有部分当作单个的任务运行好像是开销更小一点,并且可以节省上下文切换的代价。罪魁祸首来了——阻塞,阻塞使得程序中的某个任务因为该程序控制范围之外的某些条件(通常是I/O)而导致不能继续执行。因此如果使用并发编程,那么当一个程序阻塞时,程序中的其他任务还可以继续执行。
    事实上,从性能的角度看,如果没有任务会阻塞,那么在单处理器机器上使用并发没有任何意义
    在这里插入图片描述
    而需要指出的是,编写多线程程序最基本的困难在于协调不同线程驱动的任务之间对共享资源的使用,以使得这些资源不会同时被多个任务访问。
  2. 改进代码设计
    对于诸如仿真这种涉及到大量任务的问题,由于在多线程系统中可用的线程数量是有限的,因此在Java中若不能获得足够的线程,那么就无法对每个元素都提供一个线程。
    解决这个问题的典型方法是使用协作多线程Java的线程机制是抢占式的,这表示调度机会周期性地中断线程,将上下文切换到另一个线程,从而为每个线程都提供时间片,使得每个线程都会分配到数量合理的时间去驱动它的任务。
    在协作式系统中,每个任务都会自动地放弃控制,其优势是双重的:上下文切换的开销通常比抢占式系统要低廉很多,并且对可以同时执行的线程数量在理论上没有任何限制

二、基本的线程机制
并发编程使得可以将程序划分为多个分离的、独立运行的任务。其底层机制是切分CPU时间。使用线程机制是一种建立透明的、可扩展的程序的方法。多任务和多线程往往是使用多处理器系统的最合理方式。

  1. 线程的基本用法
    线程可以驱动任务,而描述任务的方式由Runnable接口提供,Runnable的run()方法即为任务的执行内容。而要实现线程行为,必须显示地将一个任务附着到线程上,其实现方式为将Runnable提交给一个Thread构造器。
    在这里插入图片描述
    调用Thread对象的start()方法为该线程执行必需的初始化操作,然后调用Runnable的run()方法,以便在这个新线程中启动该任务。
    需要注意的是start()方法是为该线程进行初始化,而并不是意味着该线程已经启动。这也就意味着在start()方法之后的当前线程的语句可能被执行也可能不会被执行。
    在这里插入图片描述
    线程的交换是由线程调度器自动控制的。如果在多个处理器的机器上,线程调度器将会在这些处理器之间默默地分发线程。值得一提的是线程调度机制是非确定性的,因此对于多线程的程序,每次运行中线程的执行顺序的不确定的。
    在这里插入图片描述
    当main()创建Thread对象时,它并没有捕获任何对这些对象的引用。在使用普通对象时,这对于垃圾回收来说这是一场公平的游戏,但是在使用Thread时,情况就不同了。每个Thread都“注册”了它自己,因此确实有一个对Thread的引用,而且在它的任务退出其run()并死亡之前,垃圾回收期无法清除它
  2. Executor与线程池
    Executor(执行器)将会管理Thread对象,从而简化并发编程。Executor在客户端和任务执行之间提供了一个间接层,这个中介对象将执行任务。Executor允许管理异步任务的执行而无须显示地管理线程的生命周期
    在这里插入图片描述
    shutdown()方法的调用可以防止新任务被提交给这个Executor,当前线程将继续执行在shutdown()被调用之前提交的所有任务。
     newCachedThreaadPool:在程序执行过程中通常会创建与所需数量相同的线程,然后在它回收旧线程时停止创建新线程
     newFixedThreadPool:该方法可以一次性预先执行代价高昂的线程分配,因此限制线程的数量。由于不需要为每个任务都固定付出创建线程的开销,因而节省了时间,并且线程的自动复用的
     newSingleThreadExecutor:线程数量为1的FixedThreadPool。如果向SingleThreadExecutor提交了多个任务,那么这些任务将排队,每个任务都会在下一个任务开始之前运行结束,所有任务都将使用相同的线程。
    在这里插入图片描述
  3. 从任务中产生返回值
    前面提到的Runnable是执行工作的独立任务,但是它并不返回任何值。如果希望从任务中返回值,那么可以通过实现Callable接口来做到。
    在这里插入图片描述
    Callable是一种具有类型参数的泛型,类型参数表示的是从方法call()中返回的值,并且必须使用ExecutorService.submit()方法调用它
    在这里插入图片描述
    submit()方法会产生Future对象它用Callable返回结果的特定类型进行了参数化。可以通过isDone()方法来查询Future是否已经完成。当任务完成时,它具有一个结果,可以调用get()方法来获取结果。也可以不用isDone()进行检查就直接调用get(),这种情况下,get()将阻塞,直至结果准备就绪
    在这里插入图片描述
  4. 休眠
    Thread类的sleep()方法能够使得当前线程休眠指定的时间段,sleep()方法会抛出InterruptedException的异常,需要注意的是异常不能跨线程传播。Java SE5引入了更加显示的sleep()版本,作为TimeUnit类的一部分,其允许指定sleep()延迟的时间单元
    在这里插入图片描述
    休眠的作用在于使得当前的任务阻塞,使得线程调度器可以切换到另一个线程,进而驱动另一个任务,只不过驱动的任务依旧是非确定性的(除非使用了同步控制)。
    在这里插入图片描述
  5. 优先级
    线程的优先级将该线程的重要性传递给了调度器。尽管CPU处理现有线程集的顺序是不确定的,但是调度器将倾向于让优先级最高的线程先执行。换句话说,优先级高的线程执行频率更高,但不意味着一定先执行。
    在绝大多数时间里,所有线程都应该以默认的优先级运行,试图操作线程优先级通常是一种错误。getPriority()用于读取现有线程的优先级,可以在任何时刻调用setPriority()方法修改优先级
    在这里插入图片描述
  6. 让步
    Thread的yield()方法将给线程调度一个暗示,表示该线程的主要工作已经做完,可以切换到另一个线程了。但是需要注意的是这仅仅是一个暗示,没有任何机制保证线程一定会切换
    当调用yield()时,同样是在建议具有相同优先级的其他线程可以运行。当然这也仅仅是建议而已。
    在这里插入图片描述

参考资料:《Java编程思想 第4版》

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值