渣渣小本求职复习之路每天一博客系列——Java基础(8)
前情回顾:在上一章,我们回顾了IO的继承架构和几个常用的类,林信良老师在这几章传达的意思愈发强烈,那就是“学习这些标准API,要想不沦为死记硬背,应该先掌握API在设计时的封装、继承、多态结构。更进一步地,还可以从API中学习到良好设计的观念,有了这样的好习惯,以后对新的API或链接库就能更快掌握如何使用甚至改进。”
我深以为然。
刚才看球了,恒大夺冠,亚洲之巅!
——————————————————————————闲聊结束——————————————————————————
第十一章:线程
在这之前谈及的都是单线程程序,也就是启动的程序从main()程序进入点到结束只有一个流程。可是,我们常常会需要程序中可以有多个流程,也就是平常所说的多线程(Multi-thread)程序。
第一节:Thread类与Runnable接口
创建多线程的方法有两种,一种是实现Runnable接口,实现run()方法;另一种方法是继承Thread类,重写run()方法。其实,我们看看API说明文档或者源代码,就知道其实Thread类本身也实现了Runnable接口。
所以,我们可以说:在java中,任何线程可执行的流程都要定义在Runnable的run()方法里。那既然是这样,我们是实现Runnable在run()中定义额外流程好,还是继承Thread在run()中定义额外流程好呢?
实现接口的好处就是比较留有余地,还可以继承其他的类。如果继承了Thread,那么该类就是一种Thread,通常是为了直接调用Thread类中定义的一些方法,才会选择这样的实现方式。
第二节:线程的生命周期
我们从最简单的开始,Daemon线程。
主线程会从main()方法开始执行,直到main()方法结束后才会关闭JVM。如果主线程中启动了额外的线程,一般来说默认等待被启动的所有线程都执行完run()方法才关闭JVM。但是,如果一个Thread被设置为Daemon(守护)线程,在所有的非Daemon线程都结束时,JVM自动就会关闭。
从main()方法开始的主线程就是一个非Daemon线程,所以说,要想把一个线程设置为Daemon线程或把一个Daemon线程设置为非Daemon线程,我们可以使用setDaemon()方法。那么,让我们来想一想,这个setDaemon()方法是在哪里定义的呢?
根据源代码,我们发现setDaemon是定义在Thread类里的。那么,我的问题又来了。是否实现Runnable接口的类(非继承Thread类的子类)就不能设置为Daemon线程了呢?这个问题,小伙伴们可以想一想哦。
值得注意的是,默认所有从Daemon线程产生的线程也都是Daemon线程,因为基本上由一个守护(服务)线程衍生出来的线程,也应该是为此而生的。
然后,我们再来看看Thread基本状态图。在调用了start()方法后,有三个基本状态:可执行(Runnable)、被阻断(Blocked)、执行中(Running),状态间的转移如下图:
看到这个图,我们实例化Thread并执行Start()之后,线程就会进入Runnable状态,这时候线程还没有真正开始执行run()方法,必须要等到排班器(Scheduler)调度到CPU执行,线程才会执行run()方法,进入Running状态。现在提一下其实我们的多线程并不是并行的,而是并发,看起来像是同时执行,但事实上在同一个时刻,一个CPU还是只能执行一个线程,只不过CPU会不断切换线程,且切换动作很快,所以看起来像是同时执行。
线程其实是有权限权的,我们可以使用Thread的setPriority()方法设定优先权,可设定值为1(Thread.MIN_PRIORITY)到10(Thread.MAX_PRIORITY),默认是5(Thread.NORM_PRIORITY),超出1到10外的设定值会抛出异常(IllegalArgumentExeption)。数字越大优先权就越高,排班器就越优先为其调度。
有几种状况会让线程进入到阻塞(Blocked)状态,例如调用Thread.sleep()方法,就会让线程阻塞(其他的,还有进入synchronized钱竞争对象锁定的阻断、调用wait()的阻断等);等待输入/输出完成也会进入Blocked。使用多线程,就是当某线程进入Blocked时,让另一线程调度CPU执行(进如Running状态),避免CPU空闲时间过多,是改进效能的常用方式之一。
线程因输入/输出进入Blocked状态,在完成输入/输出后,会回到Runnable状态,等待Schedule调度执行(Running状态)。一个进入Blocked状态,若此时有其他线程调用了该线程的interrupt()方法,就会抛出InterruptedException异常对象,这是让线程“醒过来”的方式。
有一种策略叫做安插线程。如果A线程正在运行,流程中允许B线程加入,等到B线程执行完毕后再继续A线程的流程,我们可以使用join()方法。这就好比,你在吃饭,突然有一个紧急电话打进来等电话打完了之后,你才再继续吃饭。看一段代码:
1 package cc.openhome;
2
3 public class JoinDemo {
4 public static void main(String[] args) {
5 System.out.println("Main thread 开始...");
6 Thread threadB = new Thread() {
7 @Override
8 public void run() {
9 try {
10 System.out.println("Thread B 开始...");
11 for(int i = 0; i < 5; i++) {
12 Thread.sleep(1000);
13 System.out.println("Thread B 执行...");
14 }
15 System.out.println("Thread B 将结束...");
16 }
17 catch(InterruptedException e) {
18 e.printStackTrace();
19 }
20 }
21 };
22
23 threadB.start();
24
25 try {
26 // Thread B 加入 Main thread 流程
27 threadB.join();
28 }
29 catch(InterruptedException e) {
30 e.printStackTrace();
31 }
32 System.out.println("Main thread 將结束...");
33 }
34 }
那么,我们要如何停止线程呢?线程完成run()方法后,就会进入Dead,进入Dead(或已经调用过start()方法)的线程不可以再次调用start()方法,否则会抛出IllegalThreadStateException。
Thread类上定义有stop()方法,不过被标示Deprecated。被标示为Deprecated的API,表示过去确实定义过,后来因为会引发某些问题,为了确保向前兼容性,这些API没有直接剔除,但不建议新撰写的程序再使用它。其实,最好的方法就是,让线程跑完应有的流程,进入死亡状态。
第三节:关于ThreadGroup
在多线程编程中,有时候会有很多线程需要管理,这时候,让我们隆重介绍ThreadGroup线程群组。
每个线程产生时,都会被归入某个线程群组。如果没有指定,则会被归入到产生该子线程的线程群组中取。值得注意的是,线程一旦被归入到某个群组,就无法更换群组。
我们可以用java.lang.ThreadGroup类来管理群组中的线程。可以使用以下方式产生群组,并在产生线程时指定所属群组:
1 ThreadGroup t1=new ThreadGroup("group1");
2 ThreadGroup t2=new ThreadGroup("group2");
3 //或者是下面那样
4 Thread thread1=new Thread(threadGroup1,"group1's member");
5 Thread thread2=new Thread(threadGroup2,"group2's member");
ThreadGroup的某些方法,可以对群组中所有线程产生作用。例如,interrupt()方法可以中断群组中所有的线程,setMaxPriority()方法可以设定群组中所有线程最大优先权(本来就拥有更高优先权的线程不受影响)。更多的方法,大家可以参考API说明文档,我更推荐在看完文档之后再去瞅瞅源代码!
今天的线程就回顾到这里,明天的博客继续关注线程。
———————————————————————第二十二天————————————————————
Linux很有意思。
1.之前虽然有碰过一两次,不过基本都没怎么操作。
2.这两天因为毕设的缘故,接触得比较多,嘻嘻,突然发现各种便利,各种高效,各种高大上吖!!!
3.考虑近期就进行Linux的学习哦。大家有什么好资料介绍一下么?实体书、电子书、博客什么的都可以哟。