多线程基础篇
一、进程与线程
1. 概念:
进程是操作系统结构的基础,是一个计算机中正在运行的程序实例,是分配给处理器并由处理器执行的一个实体,是由单一顺序执行显示、描述当前状态和一组相关系统资源组成的活动单元。如打开一个浏览器就是启动浏览器进程,打开word文档,就是启动word进程。
线程是进程执行运算的最小单位,也就是执行调度的基本单元,一个进程中至少包含一个线程。
2. 为什么会有多线程?
多线程就像是流水线上的各部件加工,彼此独立工作,最后完成设备的生产。也就是说多线程的引用,可以将一个复杂的功能模块拆分成不同的任务来完成,这样也就大大提高了系统的性能,缩短了处理时间,提高了用户体验。
二、线程运行的状态
- 创建状态(new):新建一个线程对象,实现的方法有四种【继承Thread类、实现Runable接口、实现Callable接口、线程池Executors框架】
- 就绪状态(Runable):线程对象被创建后,调用start()方法,进程进入可运行状态参与cpu时间片的竞争,等待被调度。
- 运行状态(Running):线程对象获取cpu时间片,线程被执行。
- 阻塞状态(Blocked):线程放弃cpu时间片,暂停执行。
阻塞状态分为三种:
1)等待阻塞:运行的线程调用o.wait()方法,该线程释放所有资源,进入“等待池”(也称为等待队列)中。进入这个状态后,线程不会自动唤醒,必须依赖其他线程调用o.notify()或o.notifyAll()方法,该线程才被唤醒,进入“锁池”中。
2)同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用时,JVM会将该线程放入“锁池”中。
3)其他阻塞:运行的线程调用Thread.sleep()或者t.join()方法,或者发出I/O请求时,JVM会将该线程置为阻塞状态,当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
- 死亡状态(Dead):线程正常执行完毕或抛出一个未处理的异常导致线程的提前结束,线程生命周期结束。
三、sleep、yield、wait三者之间的区别
1)sleep和yield是Thread下的静态方法。对运行的线程调用sleep()方法,线程让出cpu时间片,睡眠指定的时间,进入阻塞状态,该过程中,线程不会释放对象锁;到期后自动恢复,进入就绪状态,参与cpu时间片的竞争。对运行的线程调用yield()方法,线程让出cpu时间片,进入就绪状态,参与cpu时间片的竞争。
2)wait和notify、notifyAll都是Object类下的方法。对运行的线程调用wait()方法,该线程释放所有资源,同时也会释放对象锁,进入“等待池”。线程不会自动唤醒,必须通过其他线程调用notify()或notifyAll()方法,线程才可被唤醒。(注意:由于notify()只是唤醒一个线程,但是由于不能确定具体唤醒的是哪一个线程,也许需要唤醒的线程不能够被唤醒,因此在实际使用时,一般都用notifyAll()方法,唤醒有所线程),线程被唤醒后会进入锁池,等待获取对象的锁标记。
3)【延伸】同wait()需要搭配notify()或notifyAll()成对出现一样,suspend()需要搭配resume()使用,区别在于wait()会释放对象的锁,而suspend()不会释放对象的锁。另外,suspend()方法和不指定超时期限的wait()方法的调用都可能产生死锁。
四、线程中断
1 try {
2 Thread.sleep(100);
3 // wait();
4 // new Thread().join();
5 } catch (InterruptedException e) {
6 e.printStackTrace();
7 }
先来看上面一段代码,当调用Thread类下的静态方法sleep,或者调用Thread类下的非静态方法,或者调用Object类下的wait方法,都需要捕获或者抛出中断异常 InterruptedException,那么什么情况下会出现这个异常呢,看看sleep的源码解释(wait和join等同一致):
* @throws InterruptedException
* if any thread has interrupted the current thread. The
* <i>interrupted status</i> of the current thread is
* cleared when this exception is thrown.
*/
public static native void sleep(long millis) throws InterruptedException;
上面释义:当任何其他线程打断了当前线程,当前线程的中断状态被清除时,该异常就会抛出。很明显,释义已经解释很清楚了,什么是线程中断:
当前线程被其他线程打断,且当前线程的中断状态被清除,那么这个线程就中断了。
那问题来了,什么方法可以去人工干预打断当前这个线程呢?
线程Thread类下有个方法interrupt()来请求终止线程:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 /**
2 * Interrupts this thread.
3 *
4 * <p> Unless the current thread is interrupting itself, which is
5 * always permitted, the {@link #checkAccess() checkAccess} method
6 * of this thread is invoked, which may cause a {@link
7 * SecurityException} to be thrown.
8 *
9 * <p> If this thread is blocked in an invocation of the {@link
10 * Object#wait() wait()}, {@link Object#wait(long) wait(long)}, or {@link
11 * Object#wait(long, int) wait(long, int)} methods of the {@link Object}
12 * class, or of the {@link #join()}, {@link #join(long)}, {@link
13 * #join(long, int)}, {@link #sleep(long)}, or {@link #sleep(long, int)},
14 * methods of this class, then its interrupt status will be cleared and it
15 * will receive an {@link InterruptedException}.
16 *
17 * <p> If this thread is blocked in an I/O operation upon an {@link
18 * java.nio.channels.InterruptibleChannel InterruptibleChannel}
19 * then the channel will be closed, the thread's interrupt
20 * status will be set, and the thread will receive a {@link
21 * java.nio.channels.ClosedByInterruptException}.
22 *
23 * <p> If this thread is blocked in a {@link java.nio.channels.Selector}
24 * then the thread's interrupt status will be set and it will return
25 * immediately from the selection operation, possibly with a non-zero
26 * value, just as if the selector's {@link
27 * java.nio.channels.Selector#wakeup wakeup} method were invoked.
28 *
29 * <p> If none of the previous conditions hold then this thread's interrupt
30 * status will be set. </p>
31 *
32 * <p> Interrupting a thread that is not alive need not have any effect.
33 *
34 * @throws SecurityException
35 * if the current thread cannot modify this thread
36 *
37 * @revised 6.0
38 * @spec JSR-51
39 */
40 public void interrupt() {
41 if (this != Thread.currentThread())
42 checkAccess();
43
44 synchronized (blockerLock) {
45 Interruptible b = blocker;
46 if (b != null) {
47 interrupt0(); // Just to set the interrupt flag
48 b.interrupt(this);
49 return;
50 }
51 }
52 interrupt0();
53 }
另外Thread类下也提供了两个方法,静态方法interrupted()和非静态方法isInterrupted,但是这两种方法有些内在的差异:
public boolean isInterrupted()
isInterrupted是一个实例方法,主要用于判断当前线程对象的中断标志位是否被标记了,如果被标记了则返回true表示当前已经被中断,否则返回false,调用isInterrupted并不会清除线程对象的中断标识位
public static boolean interrupted()
interrupted是一个静态的方法,用于返回当前线程是否被中断,并且该方法调用结束的时候会清空中断标识位。
五、守护线程
守护线程(即daemon thread),是个服务线程,准确地来说就是服务其他的线程,这是它的作用——而其他的线程只有一种,那就是用户线程。所以java中的线程可以分两种:
1、守护线程(典型的GC线程);2、用户线程
守护线程,专门用于服务其他的线程,如果其他的线程(即用户自定义线程)都执行完毕,连main线程也执行完毕,那么jvm就会退出(即停止运行),此时,连jvm都停止运行了,守护线程当然也就停止执行了。
在Java语言中,守护线程一般具有较低的优先级,它并非只由JVM内部提供,用户在编写程序时也可以自己设置守护线程,例如将一个用户线程设置为守护线程的方法就是在调用start()方法启动线程之前调用对象的setDaemon(true)方法。值得注意的是,当一个线程被设置成守护线程后,即使是finally的语句,也是不会执行的。