很乱,写给自己看的。
文章目录
第3章
case标签可以是:
● 类型为char、byte、short或int的常量表达式。
● 枚举常量。
● 从Java SE 7开始,case标签还可以是字符串字面量。
当在switch语句中使用枚举常量时,不必在每个标签中指明枚举名,可以由switch的表达式值确定。
每一个Java应用程序都有一个带Stringarg[]参数的main方法。这个参数表明main方法将接收一个字符串数组,也就是命令行参数。
第4章
类之间的关系:
在类之间,最常见的关系有
● 依赖(“uses-a”)
● 聚合(“has-a”)
● 继承(“is-a”)
依赖(dependence),即“uses-a”关系,是一种最明显的、最常见的关系。例如,Order类使用Account类是因为Order对象需要访问Account对象查看信用状态。但是Item类不依赖于Account类,这是因为Item对象与客户账户无关。因此,如果一个类的方法操纵另一个类的对象,我们就说一个类依赖于另一个类。应该尽可能地将相互依赖的类减至最少。如果类A不知道B的存在,它就不会关心B的任何改变(这意味着B的改变不会导致A产生任何bug)。用软件工程的术语来说,就是让类之间的耦合度最小。
聚合(aggregation),即“has-a”关系,是一种具体且易于理解的关系。例如,一个Order对象包含一些Item对象。聚合关系意味着类A的对象包含类B的对象。
继承(inheritance),即“is-a”关系,是一种用于表示特殊与一般关系的。例如,Rush Order类由Order类继承而来。在具有特殊性的RushOrder类中包含了一些用于优先处理的特殊方法,以及一个计算运费的不同方法;而其他的方法,如添加商品、生成账单等都是从Order类继承来的。一般而言,如果类A扩展类B,类A不但包含从类B继承的方法,还会拥有一些额外的功能(下一章将详细讨论继承,其中会用较多的篇幅讲述这个重要的概念)。
UML(Unified Modeling Language,统一建模语言)绘制类图,用来描述类之间的关系。
第5章
域也可以被声明为final。对于final域来说,构造对象之后就不允许改变它们的值了。不过,如果将一个类声明为final,只有其中的方法自动地成为final,而不包括域。
Class类实际上是一个泛型类。例如,Employee.class的类型是Class。没有说明这个问题的原因是:它将已经抽象的概念更加复杂化了。在大多数实际问题中,可以忽略类型参数,而使用原始的Class类。
第8章
第9章
第14章
多任务:同一时刻运行多个程序的能力
多进程服务:一个应用程序里有多个进程
多线程程序:可以同时运行一个以上线程的程序,即一个程序同时执行多个任务(一个任务称为一个线程(thread)。
区别:本质的区别在于每个进程拥有自己的一整套变量,而线程则共享数据。
虽然听起来似乎有些风险,然而共享变量使线程之间的通信比进程之间的通信更有效、更容易。此外,在有些操作系统中,与进程相比较,线程更”轻量级“,创建、撤销一个线程比启动新进程的开销要小得多。
在实际应用中,多线程非常有用。例如,一个浏览器可以同时下载几幅图片。一个web服务器需要同时处理几个并发的请求。图形用户界面(GUI)程序用一个独立的线程从宿主操作环境中收集用户界面的时间。
1.什么是线程
这里从察看一个没有使用多线程的程序开始。用户很难让它执行多个任务。
调用Thread.sleep不会创建一个新线程,sleep是Thread的静态方法,用于暂停当前线程的活动。
1.1 使用线程给其他任务提供机会
将移动球的代码放置在一个独立的线程中,运行这段代码可以提高弹跳球的响应能力。实际上,可以发起多个球,每个球都在自己的线程中运行。当用户点击Close时,事件调度线程将有机会关注到这个时间,并处理”关闭“这一动作。
如果需要执行一个比较耗时的任务,应当并发地运行任务。
单独线程中执行一个任务的简单过程:
1)将任务代码转移到了实现Runnalble接口的类的run方法中。这个接口只有一个方法:
public interface Runnable
{
void run();
}
Runnable是一个函数式接口,可以用lambda表达式建立一个实例:
Runnable r = () -> {task code};
2)由Runnable创建一个Thread对象:
Thread t = new Thread(r);
3)启动线程:
t.start();
同样地。需要捕获sleep方法可能抛出的异常InterruptedException。一般情况下,线程在中断时被中止。因此,当发生InterruptedException异常时,run结束运行。
也可以通过构建一个Thread类的子类定义一个线程
然后,构造一个子类对象并调用start方法。不过这种方法已经不再推荐。应该将要并行运行的任务与运行机制解耦合。如果有很多任务,要为每个任务创建一个独立的线程所付出的代价太大了。可以用线程池解决这个问题。
警告:不要调用Thread类或Runnable对象的run方法。直接调用run方法,只会执行同一个线程中的任务,而不会启动新线程。应该调用Thread.start方法。这个方法将创建一个执行run方法的新线程。
2.中断线程
当线程的run方法执行方法体的最后一条语句后,并经由执行return语句返回时,或者出现了在方法中没有捕获的异常时,线程将终止。在java的早期版本中,还有stop方法,其他线程可以调用它终止线程,但已被弃用。
没有可以强制线程中止的方法。然而,interrupt方法可以用来请求终止线程。
对一个线程调用interrupt方法,线程的中断状态将被置位(每一个线程都具有的boolean标志)。每个线程应时不时检查这个标志,以判断线程是否被中断。
想弄清中断状态是否被置位,先调用静态的Thread.currentThread方法取得当前线程,再调用isInterrupted方法:
但是,如果线程被阻塞,就无法检测中断状态。这是产生InterruptedException异常的地方。当在一个被阻塞的线程(调用sleep或wait)上调用interrupt方法时,阻塞调用将被InterruptedException异常中断。(存在不能被中断的阻塞I/O调用,应该考虑选择可中断的调用。卷Ⅱ的第1章和第3章。)
某些线程很重要,以至于应该处理完异常后,继续执行,而不理会中断。但,普遍情况是线程将简单地将中断作为一个终止的请求。这种线程的run方法:
如果每次工作迭代后都调用sleep或其他的可中断方法,isInterrupted检测没用处。如果在中断状态被置位时调用sleep,它不会休眠。相反,它将清除这一状态(!)并抛出InterruptedException。因此,如果在循环调用sleep,不会检测中断状态。相反,要如下捕获InterruptedException异常:
注意:interrupted是静态方法,检测当前的线程是否被中断。而且,调用它会清楚该线程中的中断状态。isInterrupted是一个实例方法,用来检验是否有线程被中断。调用它不会改变中断状态。interrupt作用是中断此线程(此线程不一定是当前线程,而是指调用该方法的Thread实例所代表的线程),但实际上只是给线程设置一个中断标志,线程仍会继续运行。
两种选择:
3.线程状态
线程可以有如下6种状态:
- New(新创建)
- Runnable(可运行)
- Blocked(被阻塞)
- Waiting(等待)
- Time waiting(计时等待)
- Terminated(被终止)
调用getState方法确定一个线程的当前状态。
3.1 新创建线程
new Thread®,该线程还没有开始运行。状态是new。程序还没开始运行线程中的代码。运行前有一些基础工作要做。
3.2 可运行线程
调用start方法,线程处于runnable状态。一个可运行的线程可能正在运行也可能没有运行。(一个正在运行的线程也处于runnable状态)
一个线程开始运行,不必保持始终运行。运行中的线程被中断,目的是为了让其他线程获得运行机会。抢占式调度系统给每一个可运行线程一个时间片来执行任务。时间片用完,系统剥夺线程的运行权,并给另一个线程运行机会。选择下一个线程时考虑线程优先级。
但像手机可能使用协作式调度,一个线程只有调用yield方法、或者被阻塞或等待时,线程才失去控制权。
在具有多处理器的机器,每一个处理器运行一个线程,可以有多个线程并行运行。如果线程数目>处理器数目,调度器依然采用时间片机制。
3.3 被阻塞线程和等待线程
当处于这两种状态时,线程暂时不活动。不运行任何代码且消耗最少资源。直到线程调度器重新激活它。
- 当一个线程试图获取一个内部的对象锁(而不是java.util.concurrent库中的锁),而该锁被其他线程持有,则该线程进入阻塞状态。当所有其他线程释放该锁,且线程调度器允许本线程持有它时,该线程将变为非阻塞状态。
- 当线程等待另一个线程通知调度器一个条件时,它自己进入等待状态。在调用Object.wait方法或Thread.join方法或等待java.util.concurrent库中的Lock或Condition时,就会出现这种情况。
- 有几个方法有一个超时参数。调用它们导致线程进入计时等待(timed waiting)状态。这一状态将保持到超时期满或者接受到适当的通知。带有超时参数的方法:
Thread.sleep
Object.wait
Thread.join
Lock.tryLock
Condition.await的计时版。
当一个线程被阻塞或等待时(或终止),另一个线程被调度为运行状态。当一个线程被重新激活(例如因为超时期满或成功获得了一个锁),调度器检查它是否具有比当前运行线程更高的优先级。如果是,调度器从当前运行线程挑选一个,剥夺其运行权,选择一个新的线程运行。
3.4被终止的线程
原因:
- run方法正常退出而自然死亡
- 一个没有捕获的异常终止了run而意外死亡
特别是,可以调用线程的stop方法杀死一个线程。该方法抛出ThreadDeath错误对象,由此杀死线程。但是方法过时了。
4.线程属性
- 线程优先级
- 守护线程
- 线程组
- 处理未捕获异常的处理器
4.1 线程优先级
每一个线程有一个优先级。默认情况下,一个线程继承它的父线程的优先级。可以用setPriority方法提高或降低任何一个线程的优先级。可以将优先级设置在MIN_PRIORITY(在Thread类中定义为1)与MAX_PRIOTITY(定义为10)之间的任何值。NORM_PRIORITY被定义为5。
线程优先级是高度依赖于系统的。Windows7个,Oracle为Linux提供的Java虚拟机,所有线程相同优先级。
4.2 守护线程
调用t.setDaemon(true);
将线程转换为守护线程(daemon thread)。唯一用途是为其他线程提供服务。
4.3 未捕获异常处理器
线程的run方法不能抛出任何受查异常,但,非受查异常会导致线程终止。
但不需要任何catch子句来处理可以被传播的异常。相反,在线程死亡之前,异常被传递到一个用于未捕获异常的处理器。
该处理器必须属于一个实现Thread.UncaughtExceptionHandler接口的类。单方法接口:
可用setUncaughtExceptionHandler为所有线程安装一个默认的处理器。
如果不为独立的线程安装处理器,此时的处理器就是该线程的ThreadGroup对象。
**注意:**线程组是一个可以统一管理的线程集合。默认情况下,创建的所有线程属于相同的线程组。
5.同步
大多数多线程应用中,两个或以上的线程需要共享对同一个数据的存取。根据各线程访问数据的次序,可能会产生讹误的对象。这样一个情况称为竞争条件(race condition)。
有两种机制防止代码块受并发访问的干扰。
- synchronized关键字自动提供一个锁以及相关的“条件”。
- 用ReentrantLock保护代码块
这一结构确保任何时刻只有一个线程进入临界区,一旦一个线程封锁了锁对象,其他任何线程无法通过lock语句。当其他线程调用lock时,它们被阻塞,直到第一个线程释放锁对象。
**警告:**要把解锁操作括在finally子句内。如果在临界区的代码抛出异常,锁必须被释放。否则,其他线程将永远阻塞。如果使用锁,就不能使用带资源的try语句。