并发(2)基本的线程机制

1、定义任务

并发编程可以使我们将程序划分为多个分离的、独立运行的任务,任务本身不会执行。通过使用多线程机制,这些任务将会由执行线程来驱动处理。

Java里面准备好了使用底层线程机制的逻辑,对于应用研发人员来说,只要按部就班的调用即可。

Runnable就是描述任务的一种方式。实现Runnable接口,并且编写run()方法。它本身不具备产生线程的能力,要想实现线程行为,必须显式的将一个任务附着到线程上。

线程调度器Thread.yield(),将CPU从一个线程转移到另一个线程。表示当前线程已经执行完了最重要的部分,可以切换CPU给其他任务。

2、Thread类

Thread不能称之为线程,但是可以理解它具备产生线程的能力,也就是Runnable要附着在的东西上。

Thread.start(),是线程执行必需的初始化工作。main方法中执行Thread.start()。程序会同时执行两个方法,一个是main、一个是Thread里面的Runnable.run(),由两个线程独立驱动。

在正常的垃圾回收中,因为Thread对象在执行完后,且没有其他的引用,应该会被回收。但是在这个场景中,Thread对象不会被回收。每个Thread都注册了他自己,因此缺失有个对它的引用,在他的任务退出其run()并死亡之前,垃圾回收器不会回收他。

3、使用Executor

executor是jdk提供的管理Thread对象的执行器。用来创建和管理系统中的所有任务。

shutdown()方法是为了防止新任务提交给Executor,而已经提交的任务将会被继续执行。

Executor可以创建3种线程池:FixedThreadPool、CachedThreadPool、SingleThreadExecutor。在任何线程池中,现有线程在可能的情况下,都会被自动复用。底层都是ThreadPoolExecutor。

FixedThreadPool,一次性执行代价高昂的线程分配,但是也限制了线程的数量。

CachedThreadPool,在程序执行过程中,通常会创建与所需数量相同的线程,然后在它回收旧线程时停止创建新线程。

SingleThreadExecutor,就像线程数量为1的FixedThreadPool,也可以提交多个任务,排队执行。

4、从任务中产生返回值

Runnable是执行工作的独立任务,但不会产生返回值。如果需要返回值,则需要实现Callable接口,而不是Runnable;同时提交到线程池时使用submit()方法,而不是execute()方法。

submit()方法会产生Future对象,Future是个泛型包装类,泛型类则是Callable返回结果的类型。可以用isDone()方法判断Future是否已完成。Future.get()是获取里面的结果,get()方法具有设置超时时间的机制,不设置则会一直等Future完成,即线程执行完后。

5、休眠

Thread.sleep()可以让线程停止执行一段时间。可能会抛出InterruptedException,需要在使用的地方捕获处理。

TimeUnit,可以指定线程延迟的单元,比如延迟几秒、几分钟、几小时等。

6、优先级

线程的优先级传给调度器,但是调度器不一样按此处理。在线程run()方法里面开头设置优先级setPriority()。JDK有很多优先级,但是操作系统的优先级是不确定的,两者之间不能好好映射,如果使用则建议用高MAX_PRIORITY、中NORM__PRIORITY、低MIN_PRIORITY三个级别。

7、让步

yield(),表示你的工作已经差不多了,让可以让出CPU给别的线程。调用该方法时,建议相同优先级的其他线程可以执行。

与休眠sleep()的差异:

a 优先级区分,sleep()会给其他任何优先级的线程机会,而yield()则会给同级别或更高优先级线程机会。

b 运行状态区分,sleep()会阻塞,时间到了后变成就绪,yield()会直接就绪。

c 抛出异常之分,sleep()会跑出InterruptedException,yield()则不会。

8、后台线程

后台(daemon)线程,是指在程序运行的时候在后台提供一种通用服务的贤臣,并且这种线程并不属于程序中不可获取的部分。

与优先级setPriority()类似,要想设置后台线程,要在线程启动之前调用setDaemon()方法;与setPriority()区别是,线程start()方法之前这是,优先级是在run()里面设置。

后台线程创建的其他线程都是后台线程。

9、编码的变体

可以在一个类中,创建一个内部类,实现Runnable、继承Thread来创建任务,或者使用匿名内部来,共四种情况。

10、术语

主要为了区分任务、线程。我们实现Runnable、继承Thread不过是创建了一个任务,线程本身并非我们创建,我们也不用关注线程创建的细节,只知道这样会有线程驱动我们的任务执行。

11、加入一个线程

一个线程在其他线程之上调用join()方法,效果是等一段时间直到第二个线程结束才继续执行。如A线程里面调用B.join(),后续要等到B线程继续才会继续。join()方法可以设置超时。

针对这种场景CyclicBarrier可能是更优的解决方案。

12、创建有相应的用户界面

使用线程的动机之一就是建立有响应的用户界面。可以通过建立一个后台线程,实时监听用户输入并且做出响应,同时用户可以在界面上进行其他操作,实时监听程序不至于将程序卡死。

13、线程组

线程组是持有一个线程的集合。是一次不成功的尝试。

14、捕获异常

由于线程的本质特性,异常可能逃出任务的run()方法,直到main()方法中。解决的方法是先建立一个实现Thread.UncaughtExceptionHandler的类MyHandler,在创建完任务后Thread t = new Thread(),将其附着在线程上,t.setUncaughtExceptionHandler(new MyHandler())。

也可以创建一个全局的未捕获异常处理器:Thread.setDefaultUncaughtExceptionHandler()。

相当于注册了一个监听器,如果出现异常则会调用。感觉意义不大。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值