- 什么是线程
线程是现代操作系统的最小调度单元,这些线程拥有各自的计数器、堆栈和局部变量等属性,并能够访问共享的内存变量。
public class MultiThread {
public static void main(String[] args) {
//获取Java线程管理MXBean
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
//不需要获取同步的monitor和synchronizer信息,仅获取线程和线程堆栈信息
ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false);
//遍历线程信息 仅打印线程ID和线程名称信息
for (ThreadInfo threadInfo : threadInfos) {
System.out.println("[" + threadInfo.getThreadId() + "]" + threadInfo.getThreadName());
}
}
}
输出如下
[5]Monitor Ctrl-Break
[4]Signal Dispatcher //分发处理发送给JVM信号的线程
[3]Finalizer //调用对象finalize方法的线程
[2]Reference Handler //清楚Reference的线程
[1]main //main线程,用户程序入口
一个Java程序的运行不仅仅是main()方法的运行,而是main线程和多个其他线程的同时运行。
-
为什么要使用多线程
(1)更多的处理器核心
(2)更快地响应时间(比如消息队列处理发送短信邮件之类的功能)
(3)更好的编程模型 -
线程优先级
时间片分配决定了线程使用资源的多少,线程优先级就是决定线程需要多或者少分配一些处理器资源的线程属性。
Java线程中,整型成员变量priority控制优先级,范围1~10。
在线程构建时通过 setPriority(int)方法来修改优先级,默认优先级 5 。
优先级高的线程分配时间片的数量多。
针对频繁阻塞(休眠或者I/O操作)的线程设置较高优先级。
偏重计算(需要较多CPU时间或者偏运算)的线程设置较低优先级,确保处理器不会被独占。
线程优先级不作为程序正确性的依赖。
- 线程的状态
状态名称 | 说明 |
---|---|
NEW | 初始状态,线程被构建,但是还没有调用start()方法 |
RUNNABLE | 运行状态,Java线程将操作系统中的就绪和运行两种状态系统的称作“运行中” |
BLOCKED | 阻塞状态,表示线程阻塞于锁 |
WAITING | 等待状态,表示线程进入等待状态,进入该状态表示当前线程需要等待其他线程的通知或中断 |
TIME_WAITING | 超时等待状态,该状态不同于WAITING,可以在指定时间内自行返回 |
TERMINATED | 终止状态,表示当前线程已经执行完毕 |
Java将操作系统中的运行和就绪合并称为运行状态。
阻塞状态是线程阻塞在进入synchronized关键字修饰的方法或代码块(获取锁)时的状态,但是阻塞在java.concurrnt包中的Lock接口的线程状态却是等待,因为java.concurrent包中Lock接口对于阻塞的实现均使用了LockSupport类中的相关方法。
-
Daemon线程
在Java中有两类线程:用户线程(User Thread)和守护线程(Daemon Thread)
守护线程,是指在程序运行的时候在后台提供中通用服务的线程,比如垃圾回收线程。
守护线程为不属于程序不可或缺部分,当所有非守护线程结束,程序终止,同时杀死进程中的所有守护线程。
线程转为守护线程,Thread对象的setDaemon(true)方法:
(1)Thread.setDaemon(true)必须在thread.start()之前设置,否则抛异常IllegalThreadStateException。不能把正在运行的线程设在这里插入代码片
置守护线程。
(2)在Daemon线程中产生的新县城也是Daemon的。
(3)守护线程不应该去访问固有资源,如文件、数据库,因为它会在任何时候在一个操作的期间发生中断。 -
启动和终止线程
6.1 构造线程
Thread的init方法
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
if (name == null) {
throw new NullPointerException("name cannot be null");
}
this.name = name;
Thread parent = currentThread();
SecurityManager security = System.getSecurityManager();
if (g == null) {
/* Determine if it's an applet or not */
/* If there is a security manager, ask the security manager
what to do. */
if (security != null) {
g = security.getThreadGroup();
}
/* If the security doesn't have a strong opinion of the matter
use the parent thread group. */
if (g == null) {
g = parent.getThreadGroup();
}
}
/* checkAccess regardless of whether or not threadgroup is
explicitly passed in. */
g.checkAccess();
/*
* Do we have the required permissions?
*/
if (security != null) {
if (isCCLOverridden(getClass())) {
security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
}
}
g.addUnstarted();
this.group = g;
this.daemon = parent.isDaemon();
this.priority = parent.getPriority();
if (security == null || isCCLOverridden(parent.getClass()))
this.contextClassLoader = parent.getContextClassLoader();
else
this.contextClassLoader = parent.contextClassLoader;
this.inheritedAccessControlContext =
acc != null ? acc : AccessController.getContext();
this.target = target;
setPriority(priority);
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
/* Stash the specified stack size in case the VM cares */
this.stackSize = stackSize;
/* Set thread ID */
tid = nextThreadID();
}
在上述过程中,一个新构造的线程对象是由其parent线程来进行空间分配的,而child线程继承了parent是否为Daemon、优先级和加载资源的contextClassLoader以及可继承的ThreadLocal,同时还会分配一个唯一的ID来标识这个child线程。初始化一个线程完成,在堆内存中等待运行。
6.2 启动线程
线程对象在初始化完成后,调用run()方法启动。线程start()方法的含义:当前线程(parent)同步稿纸Java虚拟机,只要线程规划期空闲应立即启动调用start()方法的线程。
启动一个线程前,最好命名该线程
6.3 线程中断
中断好比其他线程对该线程打了个招呼,其他线程通过调用该线程的interrupt()方法对其进行中断操作。
线程通过isInterrupted()方法进行判断是否被中断,也可以调用静态方法Thread.interrupted()对当前线程的中断标识位进行复位。
如果该线程已经处于终结状态,即使该线程被中断过,在调用该线程对象的isInterrupted()方法将会返回false。
public class InterruptedThread {
public static void main(String[] args) throws InterruptedException {
// sleepThread不断尝试睡眠
Thread sleepThread = new Thread(new SleepThread(), "SleepThread");
sleepThread.setDaemon(true);
// busyThread不停的运行
Thread busyThread = new Thread(new BusyThread(), "BusyThread");
sleepThread.setDaemon(true);
sleepThread.start();
//休眠5秒 让sleepThrea和busyThread充分运行
TimeUnit.SECONDS.sleep(5);
sleepThread.interrupt();
busyThread.interrupt();
System.out.println("SleepThread interrupted is " + sleepThread.isInterrupted() );
System.out.println("BusyThread interrupted is " + busyThread.isInterrupted() );
// 防止sleepThread和busyThread立刻退出
TimeUnit.SECONDS.sleep(5);
}
static class SleepThread implements Runnable {
@Override
public void run() {
while (true) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
static class BusyThread implements Runnable {
@Override
public void run() {
while (true) {
}
}
}
}
输出如下:
SleepThread interrupted is false
BusyThread interrupted is false
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at com.jsh.erp.controller.MultiThread$SleepThread.run(MultiThread.java:30)
at java.lang.Thread.run(Thread.java:748)
抛出 InterruptedException 异常的线程SleepThread,其中断标识位被清除了,而一直运行的线程BusyThread,中断标识位没有被清除。
6.4 过期的 suspend()、resume()和stop()
暂停、恢复和停止对应线程Thread的API就是suspend()、resume()和stop()
不建议使用这三个方法。
以suspend为例,在调用后,线程不会释放已经占有的锁,容易引发死锁。同样,stop在终结一个线程时不会保证锁正常释放。
6.5 安全地终止线程
之前提到中断状态是线程的一个标识位,而中断操作是一种便捷的线程间交互方式,而这种交互方式最适合用来取消或停止任务。除了中断以外,还可以利用一个boolean变量来控制是否需要停止任务并终止该线程。
public class Shutdowm {
public static void main(String[] args) throws InterruptedException {
Runner one = new Runner();
Thread countThread = new Thread(one, "CountThread");
countThread.start();
//睡眠1s main线程对CountThread进行中断 使CountThread能够感知中断而结束
TimeUnit.SECONDS.sleep(1);
countThread.interrupt();
Runner two = new Runner();
countThread = new Thread(two, "CountThread");
countThread.start();
//睡眠1s main线程对 Runner two 进行取消 使CountThread能够感知on为false而结束
TimeUnit.SECONDS.sleep(1);
two.cancel();
}
private static class Runner implements Runnable {
private long i;
private volatile boolean on = true;
@Override
public void run() {
while (on && !Thread.currentThread().isInterrupted()) {
i++;
}
System.out.println("Count i = " + i);
}
public void cancel() {
on = false;
}
}
}
输出如下:
Count i = 702373388
Count i = 779329086
在执行过程中,main线程通过中断操作和cancel()方法均可以使CountThread终止。
这种通过标识位或者终端操作的方式能够使线程在终止时有机会去清理资源。