一、概述
一个Java程序也是一个多线程程序。
JVM 启动的
[5] Attach Listener //负责接收到外部的命令
[4] Signal Dispatcher // 分发处理发送给JVM信号的线程
[3] Finalizer // 调用对象finalize方法的线程
[2] Reference Handler // 清除Reference的线程
用户启动的
[1] main // main线程,用户程序入口
二、线程
简介
使用多线程的原因:
①更多的处理器核心(硬件升级:多核处理器)
②更快的响应时间(业务异步或者数据一致性不强的可以用多线程)
③更好的编程模型
线程的优先级:
在Java线程中,通过一个整型成员变量priority来控制优先级,优先级的范围从1~10,在线程构建的时候可以通过setPriority(int)方法来修改优先级,默认优先级是5,优先级高的线程分配时间片的数量要多于优先级低的线程。针对频繁阻塞(休眠或者I/O操作)的线程需要设置较高优先级,而偏重计算(需要较多CPU时间或者偏运算)的线程则设置较低的优先级,确保处理器不会被独占。
线程的状态:
阻塞状态是线程阻塞在进入synchronized关键字修饰的方法或代码块(获取锁)时的状态,但是阻塞在java.concurrent包中Lock接口的线程状态却是等待状态,因为java.concurrent包中Lock接口对于阻塞的实现均使用了LockSupport类中的相关方法。
Daemon线程(守护线程):
Daemon属性需要在启动线程之前设置,不能在启动线程之后设置。在构建Daemon线程时,不能依靠finally块中的内容来确保执行关闭或清理资源的逻辑。
启动和终止线程
①构造线程:一个新构造的线程对象是由其parent线程来进行空间分配的,而child线程继承了parent是否为Daemon、优先级和加载资源的contextClassLoader以及可继承的ThreadLocal,同时还会分配一个唯一的ID来标识这个child线程。至此,一个能够运行的线程对象就初始化好了,在堆内存中等待着运行。
②启动线程:线程对象在初始化完成之后,调用start()方法就可以启动这个线程。线程start()方法的含义是:当前线程(即parent线程)同步告知Java虚拟机,只要线程规划器空闲,应立即启动调用start()方法的线程。
注意:启动一个线程前,最好为这个线程设置线程名称,因为这样在使用jstack分析程序或者进行问题排查时,就会给开发人员提供一些提示,自定义的线程最好能够起个名字。
③理解中断:其他线程通过调用该线程的interrupt()方法对其进行中断操作。线程通过检查自身是否被中断来进行响应,线程通过方法isInterrupted()来进行判断是否被中断,也可以调用静态方法Thread.interrupted()对当前线程的中断标识位进行复位。如果该线程已经处于终结状态,即使该线程被中断过,在调用该线程对象的isInterrupted()时依旧会返回false。
注意:从Java的API中可以看到,许多声明抛出InterruptedException的方法(例如Thread.sleep(longmillis)方法)这些方法在抛出
InterruptedException之前,Java虚拟机会先将该线程的中断标识位清除,然后抛出InterruptedException,此时调用isInterrupted()
方法将会返回false。
④过期的suspend()、resume()和stop():以suspend()方法为例,在调用后,线程不会释放已经占有的资源(比如锁),而是占有着资源进入睡眠状态,这样容易引发死锁问题。同样,stop()方法在终结一个线程时不会保证线程的资源正常释放,通常是没有给予线程完成资源释放工作的机会,因此会导致程序可能工作在不确定状态下。
⑤安全地终止线程:除了中断以外,还可以利用一个boolean变量来控制是否需要停止任务并终止该线程。
线程间的通讯
①volatile和synchronized关键字
②等待/通知机制:在功能层面上实现了解耦,体系结构上具备了良好的伸缩性。
注意:
a、使用wait()、notify()和notifyAll()时需要先对调用对象加锁。
b、调用wait()方法后,线程状态由RUNNING变为WAITING,并将当前线程放置到对象的等待队列。
c、notify()或notifyAll()方法调用后,等待线程依旧不会从wait()返回,需要调用notify()或notifAll()的线程释放锁之后,等待线程才有机会从wait()返回。
d、notify()方法将等待队列中的一个等待线程从等待队列中移到同步队列中,而notifyAll()方法则是将等待队列中所有的线程全部移到同步队列,被移动的线程状态由WAITING变为BLOCKED。
e、从wait()方法返回的前提是获得了调用对象的锁。
③等待/通知的经典范式:
④管道输入/输出流:主要用于线程之间的数据传输,而传输的媒介为内存。
⑤Thread.join()的使用:
// 加锁当前线程对象
public final synchronized void join() throws InterruptedException {
// 条件不满足,继续等待
while (isAlive()) {
wait(0);
}
// 条件符合,方法返回
}
⑥ThreadLocal的使用(线程变量):可以通过set(T)方法来设置一个值,在当前线程下再通过get()方法获取到原先设置的值
线程池技术
不使用线程池的弊端:会使操作系统频繁的进行线程上下文切换,无故增加系统的负载,而线程的创建和消亡都是需要耗费系统资源的,也无疑浪费了系统资源。
使用线程池的好处:一方面,消除了频繁创建和消亡线程的系统资源开销,另一方面,面对过量任务的提交能够平缓的劣化。
网友评论