一 线程
1.线程的引入
如果说在操作系统中引入进程的目的是使多个程序并发执行以改善资源利用率及提高系统的吞吐量;那么在操作系统中再引入线程,则是为了减少程序并发执行时所付出的时空开销,使操作系统具有更好的并发性。
进程的基本属性:进程是一个可拥有资源的独立单位;进程同时又是一个可以独立调度和分配的基本单位。
简而言之,进程是一个资源拥有者,因而在创建,撤销和切换中,系统必须付出为之较大的时空开销。在系统中所设置的进程不易过高,切换频率也不宜过高,但是这也就限制了并发程度的进一步提高。
如何使多个程序更好的并发执行,同时又尽量减少系统的开销—》线程:作为调度和分派的基本单位,不同时作为独立资源的单位,轻装运行。
2 线程的创建方式
(1)继承Thread:定义THread的子类,重写run()方法,run方法就代表线程需要完成的任务-线程执行体。创建Thread子类的实例,即创建线程对象。调用线程对象的start方法启动。
(2)实现Runnable接口:实现Runnable接口,重写run;创建Runnable实例,并将该实例作为参数传递到Thread中。
(3)使用Callable和Future创建线程:创建Callable接口的实现类,并重写call()方法,该方法就是线程方法执行体,且call()方法有执行返回值,再创建Callable实现类的实例;使用Future Task的实例,来包装Callable对象,即把callable的实例以形参的方式传入Future Task()的构造函数中;使用FutureTask对象作为Thread对象的target创建启动线程;通过FutureTask实例对象调用get方法得到子线程的返回值。
3 创建方式对比:使用实现Runnable接口 或Callable接口,同时开可以继承其他类;
多个线程可以共享一个target对象,非常适合多个相同线程来处理同一资源的情况,从而将CPU,代码,数据分开,形成清晰的模型,较好的体现了面向对象的思想。
缺点:编程复杂,如果要访问当前线程,必须使用Thread.currentThread()方法
使用继承Thread类:可以使用this访问当前线程,不可以继承其他类。
4 Java中的6种线程状态及转换
状态 | 说明 |
NEW | 初始状态,线程被构建,但是还没有调用start()方法 |
RUNNABLE | 运行状态,Java线程将操作系统中的就绪和运行两种状态笼统的称作为"运行中" |
BLOCKED | 阻塞状态,表示线程阻塞于锁 |
WAITING | 等待状态,表示线程进入等待状态,进入该状态表示当前线程需要等待其他线程做出一些特定动作(通知或中断) |
TIME_WAITING | 超时等待,该状态不同于WAITING,它是可以在指定的时间自行返回的。 |
TERMINATED | 终止状态,表示当前线程已经执行完毕。 |
5 线程间通信
(1)使用wait/notify实现线程间的通信
方法wait()的作用是使当前执行代码的线程进行等待,wait()方法是Object类的方法,该方法用来将当前线程置入“预执行队列”中,并且在wait()所在的代码行处停止执行,直到接到通知或被中断为止。在调用wait()之前,线程必须获得该对象的对象级别锁,即只能在同步方法或同步代码块中调用wait()方法。在执行wait()方法后,当前线程锁被释放。在wait()返回前,线程与其他线程竞争重新获得锁。如果调用wait()时没有持有适当锁,会抛出异常。不需要try catch进行捕获。
方法notify()也要在同步方法或者同步块中调用,即在调用前,线程也必须获得该对象的对象级别锁。该方法用来通知那些可能等待该对象的对象锁的其他线程,如果有多个线程等待,则由线程规划器随机挑选出一个呈wait状态的线程,对其发出notify通知,并使它等待获取该对象的对象锁。需说明的是,在执行notify()方法后,当前线程不会马上释放该对象锁,呈wait状态的线程也并不能马上获取该对象锁,要等到执行notify()方法的线程将程序执行完,也就是退出synchronized代码块后,当前线程才会释放锁,而呈wait状态所在的线程才可以获取该对象锁。
关键字synchronized可以将任何一个Object对象作为同步对象来看,而Java为每个Object都实现了wait()方法和notify()方法,他们必须用在被synchronized同步的Object的临界区内。
1)新建一个线程对象后,再调用它的start()方法,系统会为此线程分配CPU资源,使其处于Runnable(可运行)状态,这是一个准备运行的阶段。如果线程抢占到CPU资源就处于Running 状态。
2)线程进入Runnable状态大体分为如下5种情况:
调用sleep()方法后经过的时间超过了指定的休眠时间
线程调用的阻塞IO已经返回,阻塞方法执行完毕
线程成功获得了试图同步的监视器
线程正在等待某个通知,其他线程发出了通知
处于挂起的线程调用了resume恢复方法
Blocked是阻塞的意思,例如遇到了一个IO操作,此时CPU处于空闲状态,可能会转而把CPU时间片分配给其他线程,这时也可以称为“暂停”状态。Blocked结束后,进入Runnable状态,等待系统重新分配资源。
3)出现阻塞的情况大致分为如下5种:
线程调用sleep,主动放弃占用的处理器资源
线程调用了阻塞式IO方法,在该方法返回前,该线程被阻塞
线程试图获得一个同步监视器,但该同步监视器正被其他线程所持有
线程等待某个通知
程序调用了suspend方法将该线程挂起。此方法容易死锁,尽量避免。
4)run()方法运行结束后进入销毁阶段,整个线程执行完毕。
每个锁对象都有两个队列,一个是就绪队列,一个是阻塞队列。就绪队列存储了将要获得锁的线程,阻塞队列存储了被阻塞的线程。一个线程被唤醒后,才会进入就绪队列,等待CPU的调度;反之,一个线程被wait后,就会进入阻塞队列,等待下一次被唤醒。
(2)生产者/消费者的实现
(3)方法join的使用
在很多情况下,主线程创建并启动子线程,如果子线程中药进行大量的耗时运算,主线程往往将早于子线程之前结束。这时,如果主线程想等待子线程执行完毕之后再结束,比如子线程处理一个数据,主线程要取得这个数据中的值,就要用到join()方法了,方法join()的作用是等待线程对象的销毁。
(4)ThreadLocal类的使用
变量值的共享可以用public static变量的形式,所有线程使用同一个public static变量。如果想实现每一个线程都有自己的共享变量该如何解决呢,JDK中提供的类ThreadLocal正是为了解决这样的问题。
类ThreadLocal主要解决的就是每个线程绑定自己的值。
问题:
- 如何让两个线程依次执行?
-
假设有两个线程,一个是线程 A,另一个是线程 B,两个线程分别依次打印 1-3 三个数字即可。我们来看下代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private
static
void
demo1() {
Thread A =
new
Thread(
new
Runnable() {
@Override
public
void
run() {
printNumber(
"A"
);
}
});
Thread B =
new
Thread(
new
Runnable() {
@Override
public
void
run() {