开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 1 天,点击查看活动详情
目录
- JAVA线程
- JAVA线程状态
- 通过JAVA代码理解线程状态
- JAVA线程生命周期
- Thread的方法
- init()
- start()
- run()
- sleep(long)、sleep(long,int)
- stop()
- interrupt()
- yield()
- join()
- setDaemon()
- setPriority()
线程
看一下百度百科对 进程 和 线程 的介绍。
进程:进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。
线程:线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。在Unix System V及SunOS中也被称为轻量进程(lightweight processes),但轻量进程更多指内核线程(kernel thread),而把用户线程(user thread)称为线程。
线程是独立调度和分派的基本单位。线程可以为操作系统内核调度的内核线程,如Win32线程;由用户进程自行调度的用户线程,如Linux平台的POSIX Thread;或者由内核与用户进程,如Windows 7的线程,进行混合调度。
同一进程中的多条线程将共享该进程中的全部系统资源,如虚拟地址空间,文件描述符和信号处理等等。但同一进程中的多个线程有各自的调用栈(call stack),自己的寄存器环境(register context),自己的线程本地存储(thread-local storage)。
一个进程可以有很多线程,每条线程并行执行不同的任务。
在多核或多CPU,或支持Hyper-threading的CPU上使用多线程程序设计的好处是显而易见,即提高了程序的执行吞吐率。在单CPU单核的计算机上,使用多线程技术,也可以把进程中负责I/O处理、人机交互而常被阻塞的部分与密集计算的部分分开来执行,编写专门的workhorse线程执行密集计算,从而提高了程序的执行效率。
JAVA线程
在JAVA中,线程以对象的形式存在,我们可以通过操作对象的方式来操作线程。
介绍三种JAVA中创建线程的方式。
1. 实现Runnable
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("线程执行");
}
}
@Test
void testRunnable() {
new MyRunnable().run();
}
2. 继承Thread
class MyThread extends Thread {
@Override
public void run() {
System.out.println("线程执行");
}
}
@Test
void testThread() {
new MyThread().start();
}
3. 实现Callable
class MyCallable implements Callable {
@Override
public Object call() throws Exception {
System.out.println("线程执行");
return null;
}
}
@Test
void testCallable() throws Exception {
new MyCallable().call();
}
JAVA线程状态
以Thread类为例,JAVA中线程分为6种状态。
- NEW(新建):新创建了一个线程,还未启动(比如上文的
new MyThread())。 - RUNNABLE(运行):Java线程中将 就绪(READY)和 运行中(RUNNING)两种状态笼统的称为 “运行”状态。
Thread对象创建后,调用了该对象的
start()方法(比如上文new MyThread().start())。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态(READY)。就绪状态的线程在获得CPU时间片后变为运行中状态(RUNNING)。 - BLOCKED(阻塞):线程被锁阻塞。
- WAITING(等待):等待,这里是无限等待。
- TIMED_WAITING(计时等待):等待,在指定时间后返回,不同于WAITING。
- TERMINATED(消亡):该线程已经执行完毕。
下面是Thread类内部对状态的定义。
public enum State {
/**
* 尚未启动的线程的线程状态。
*/
NEW,
/**
* 可运行线程的线程状态。处于可运行状态的线程正在Java虚拟机中
* 执行等待来自操作系统的其他资源,如处理器。
*/
RUNNABLE,
/**
* 等待监视器锁而阻塞的线程的线程状态。
* 处于阻塞状态的线程正在等待监视器锁进入同步块/方法或调用后重新输入同步块/方法
* {@link Object#wait() Object.wait}.
*/
BLOCKED,
/**
* 等待线程的线程状态。由于调用以下方法之一,线程处于等待状态:
* <ul>
* <li>{@link Object#wait() Object.wait} with no timeout</li>
* <li>{@link #join() Thread.join} with no timeout</li>
* <li>{@link LockSupport#park() LockSupport.park}</li>
* </ul>
*/
WAITING,
/**
* 具有指定等待时间的等待线程的线程状态。
* 线程处于定时等待状态,这是由于调用下列方法中的一个具有指定的正等待时间:
* <ul>
* <li>{@link #sleep Thread.sleep}</li>
* <li>{@link Object#wait(long) Object.wait} with timeout</li>
* <li>{@link #join(long) Thread.join} with timeout</li>
* <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
* <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
* </ul>
*/
TIMED_WAITING,
/**
* 终止线程的线程状态。
* 线程已完成执行。
*/
TERMINATED;
}
通过JAVA代码理解线程状态
private static Object lock = new Object();
class MyThread extends Thread {
@Override
public void run() {
synchronized (lock) {
if("myThread0".equals(Thread.currentThread().getName())){
try {
// 让当前线程进入TIMED_WAITING状态
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}else if("myThread1".equals(Thread.currentThread().getName())){
try {
// 让当前线程进入WAITING状态
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
System.out.println(Thread.currentThread().getName() + "线程执行run()");
}
}
@Test
void testThread() throws InterruptedException {
MyThread myThread0 = new MyThread();
myThread0.setName("myThread0");
MyThread myThread1 = new MyThread();
myThread1.setName("myThread1");
// 此时myThread0和myThread1都处于NEW状态
System.out.println("第一次打印myThread0状态为:" + myThread0.getState().name());
System.out.println("第一次打印myThread1状态为:" + myThread1.getState().name());
myThread0.start();
myThread1.start();
// start()方法调用后,会调用run()方法会需要执行时间,所以此处睡眠一会(此处睡眠的时间可适当调整)
Thread.sleep(200);
// 睡眠一会后,myThread0和myThread1都执行了run()方法
// 因为synchronized锁的原因只有一个会进入synchronized里的代码,所以一个为TIMED_WAITING状态,一个为BLOCKED状态
System.out.println("第二次打印myThread0状态为:" + myThread0.getState().name());
System.out.println("第二次打印myThread1状态为:" + myThread1.getState().name());
Thread.sleep(1000);
// 睡眠一会后myThread0执行完毕变为TERMINATED状态,myThread1获取到锁变为WAITING状态
System.out.println("第三次打印myThread0状态为:" + myThread0.getState().name());
System.out.println("第三次打印myThread1状态为:" + myThread1.getState().name());
}
查看执行结果。
第一次打印myThread0状态为:NEW
第一次打印myThread1状态为:NEW
第二次打印myThread0状态为:TIMED_WAITING
第二次打印myThread1状态为:BLOCKED
myThread0线程执行run()
第三次打印myThread0状态为:TERMINATED
第三次打印myThread1状态为:WAITING
JAVA线程生命周期
Thread的方法
接下来跟着Thread的方法源码来深入理解JAVA线程。
init()
new Thread()的时候就会调用该方法。
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}
init()源码。
/**
* 初始化一个线程。
*
* @param g 线程组
* @param target 调用其run()方法的对象
* @param name 新线程的名称
* @param stackSize 新线程所需的堆栈大小,或者0表示该参数将被忽略。
* @param acc 继承AccessControlContext,或者AccessController.getContext()如果为空
* @param inheritThreadLocals 如果{@code true},则从构造线程继承可继承线程局部变量的初始值
*/
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
// 如果名称为null,直接异常
if (name == null) {
throw new NullPointerException("name cannot be null");
}
// 名称赋值
this.name = name;
// 是native修饰的方法,大概就是返回当前正在执行的线程引用(父线程)
Thread parent = currentThread();
// 获取JAVA安全管理器,防止恶意代码对系统产生影响
SecurityManager security = System.getSecurityManager();
// 优先初始化传入的threadgroup,然后SecurityManager的threadgroup,最后parent的threadgroup
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();
}
}
// 确保当前运行的线程是否有权限修改线程组
g.checkAccess();
// 校验权限
if (security != null) {
if (isCCLOverridden(getClass())) {
security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
}
}
// 增加线程组中未启用线程的计数,synchronized修饰
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);
// 子类线程也能继承父类线程的ThreadLocal
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
// 设置新线程栈大小
this.stackSize = stackSize;
// 分配一个线程tid,synchronized修饰,保证唯一
tid = nextThreadID();
}
start()
NEW状态下可调用start()方法。
/**
* 使这个线程开始执行;Java虚拟机调用这个线程的run()方法。
*/
public synchronized void start() {
// 判断是否是NEW状态,不是NEW状态抛IllegalThreadStateException异常
if (threadStatus != 0)
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
// 通知该组线程即将启动,该组未启动线程数递减,synchronized修饰
group.add(this);
// 是否启动
boolean started = false;
try {
// 真正启动的方法,是一个native方法,表明它具体是由C/C++来实现
// 大概就是创建JavaThread对象,创建内核线程,在内核线程中执行run方法
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
private native void start0();
run()
Thread的子类需要重写该方法,实现我们需要的业务逻辑。
/**
* 如果这个线程是使用一个单独的Runnable run对象构造的,
* 那么Runnable对象的run方法被调用;否则,该方法不执行任何操作并返回。
*/
@Override
public void run() {
if (target != null) {
target.run();
}
}
sleep(long)、sleep(long,int)
使当前执行的线程暂时停止执行,不会放弃监视器的所有权。
/**
* 使当前正在执行的线程休眠(暂时停止执行)指定的毫秒数,
* 以系统计时器和调度器的精度和准确性。线程不会失去任何监视器的所有权。
*/
public static native void sleep(long millis) throws InterruptedException;
/**
* 使当前正在执行的线程休眠(暂时停止执行),休眠时间为指定的毫秒数加上指定的纳秒数,
* 这取决于systemd计时器和调度器的精度和准确性。线程不会失去任何监视器的所有权。
*
* @param millis 以毫秒为单位的睡眠时间
* @param nanos 额外的纳秒睡眠
*/
public static void sleep(long millis, int nanos) throws InterruptedException {
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (nanos < 0 || nanos > 999999) {
throw new IllegalArgumentException(
"nanosecond timeout value out of range");
}
if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
millis++;
}
sleep(millis);
}
stop()
该方法已被废弃。
该方法本身就是不安全的。会强制线程停止,解锁已锁定的所有监视器,造成数据不一致问题。建议使用interrupt()方法。
/**
* 强制线程停止执行。
* 如果此线程与当前线程不同(即当前线程正在尝试停止与其自身不同的线程),
* 则安全管理器的checkPermission方法(带有一个调用RuntimePermission("stopThread"参数)加法。
* 同样,这可能会导致抛出SecurityException(在当前线程中)。
* 此线程表示的线程将被迫停止其异常操作,并抛出一个新创建的<code>ThreadDeath</code>对象作为异常。
* 允许停止尚未启动的线程。
* 如果线程最终启动,它将立即终止。
* 如果未捕获的异常是ThreadDeath的实例,
* 则对未捕获异常作出反应的顶级错误处理程序不会打印消息,也不会以其他方式通知应用程序。
*/
@Deprecated
public final void stop() {
SecurityManager security = System.getSecurityManager();
if (security != null) {
checkAccess();
if (this != Thread.currentThread()) {
security.checkPermission(SecurityConstants.STOP_THREAD_PERMISSION);
}
}
// 零状态值对应于“NEW”,它不能更改为非NEW,因为我们持有锁。
if (threadStatus != 0) {
resume(); // Wake up thread if it was suspended; no-op otherwise
}
// VM可以处理所有线程状态
stop0(new ThreadDeath());
}
interrupt()
给线程发送中断信号,但是不保证线程被真正的中断(不会中断一个正在运行的线程)。
在调用interrupt()方法之前,如果当前线程已经处于阻塞状态(比如调用了sleep(long)方法),那么调用该阻塞线程的interrupt()方法将导致当前线程从sleep(long)函数中醒来,并抛出InterruptedException异常。
/**
* 中断这个线程。
*/
public void interrupt() {
// 不是当前线程调用,检验是否有修改权限
if (this != Thread.currentThread())
checkAccess();
//
synchronized (blockerLock) {
Interruptible b = blocker;
if (b != null) {
// native方法,设置中断标志
interrupt0(); // 只是为了设置中断标志
b.interrupt(this);
return;
}
}
interrupt0();
}
yield()
向调度器提示当前线程愿意放弃当前使用的处理器,调度器可以忽略这个提示。用于改善线程之间的相互进展。
/**
* 向调度器提示当前线程愿意放弃当前使用的处理器。调度程序可以忽略这个提示。
*/
public static native void yield();
join()
等待线程结束。
比如主线程启动了一个子线程,但子线程执行时间长,主线程想要获得子线程的执行结果,主线程可以通过join() 方法加入子线程。
/**
* 等待线程死亡的时间最多为{@code millis}毫秒。超时{@code o}意味着永远等待。
*/
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
setDaemon()
线程启动之前,可以设置守护线程。
/**
* 将此线程标记为{@linkplain #isDaemon daemon}线程或用户线程。
* 当唯一运行的线程都是守护线程时,Java虚拟机退出。
*/
public final void setDaemon(boolean on) {
checkAccess();
if (isAlive()) {
throw new IllegalThreadStateException();
}
daemon = on;
}
setPriority()
更改线程优先级。
/**
* 更改此线程的优先级。
*/
public final void setPriority(int newPriority) {
ThreadGroup g;
checkAccess();
if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
throw new IllegalArgumentException();
}
if((g = getThreadGroup()) != null) {
if (newPriority > g.getMaxPriority()) {
newPriority = g.getMaxPriority();
}
setPriority0(priority = newPriority);
}
}