前言
Java语言是支持多线程的,一个正在运行的Java程序可以称之为一个进程(process),在每个进程里面包含多个线程,线程是进程中单一的顺序控制流,CPU在执行计算机指令的时候都是按顺序执行,但是由于其执行速度很快,可以把时间分成很细小的时间片,交替执行,线程和进程的区别在于:
- 创建进程的开销大于创建线程的开销,进程之间的通信比线程间要难
- 线程不能独立存在,依托于进程而存在,线程也可以看作轻量级的进程
多进程的稳定性高于多线程,一个进程的运行不会影响其他进程,但线程崩溃往往会引起程序的崩溃
Thread类
Thread类位于java.lang包,JDK1.0引入。在HotSpot虚拟机中,线程使用的是基于操作系统的1 : 1的内核实现模型来创建线程,线程的创建、调度、执行、销毁等由内核进行控制,调度过程通过抢占式策略进行调度。
生命周期
Java语言定了线程生命周期的6种状态,在任意时刻线程只能处于一种状态,Java中提供了特定的API,在某些场景下可以实现线程间状态的转换。这6种状态使用枚举类java.lang.Thread.State来表示,每种状态如下所示:
NEW(新建状态):新创建后尚未启动的线程处于这种状态
- RUNNABLE(运行状态):Java虚拟机中处于该状态的线程,对应操作系统状态中的Running和Ready,表示该线程正在执行或者等待操作系统分配执行时间
- BLOCKED(阻塞状态):当多个线程进行资源争抢,等待获取一个排他锁的线程会处于该状态,当线程获取到锁之后状态会变为RUNNABLE状态,其他等待锁的线程继续处于BLOCKED状态,一般被synchronized关键字修饰或者ReentrantLock包裹的代码快会触发不可到到达线程处于该状态。
- WAITING(无限期等待状态):处于该状态的线程不会被分配执行时间,需要等待被其他线程显式唤醒,调用Thread中以下方法会触发线程处于该状态:
- Object.wait() with no timeout
- Thread.join() with no timeout
- LockSupport.park()
- TIMED_WAITING(限期等待状态):计时等待状态的线程,不会被操作系统分配执行时间,也不需要被其他线程显式唤醒,在计时结束后由操作系统自动唤醒,Thread中的以下方法会触发线程进入此状态:
- Thread.sleep()
- Object.wait() with timeout
- Thread.join() with timeout
- LockSupport.parkNanos()
- LockSupport.parkUntil()
- TERMINATED(终止状态):线程任务执行结束时处于这种状态
线程的生命周期以及状态转换如下图所示:
接下来对Thread对象的声明周期包含的六种状态进行进行简单模拟。
NEW
public class Test{
public static void main(String[] args) throws Exception{
System.out.println("Thread State is:"+new Thread().getState());
}
}
输出结果
Thread State is:NEW
RUNNABLE
public class Test implements Runnable{
@Override
public void run() {
System.out.println("Thread State is:"+Thread.currentThread().getState());
}
public static void main(String[] args) throws Exception{
Test test = new Test();
Thread thread = new Thread(test);
thread.start();
}
}
输出结果
Thread State is:RUNNABLE
BLOCKED
- 创建线程T1,T2调用
start()
方法,T1,T2状态均为RUNNABLE
- 若T1获得锁,T1状态为
RUNNABLE
,T2状态变为BLOCKED
等待T1执行完释放锁,T2获得锁,T2状态
RUNNABLE
class BlockThread extends Thread {
private String name; //当前线程名称
private Object lock; //锁
public BlockThread(Object lock,String name){
this.lock = lock;
this.name = name;
}
@Override
public void run() {
System.out.println("Thread "+name+" State is "+Thread.currentThread().getState());
synchronized (lock) {
System.out.println("Thread "+name+" hold the lock");
try {
System.out.println("Thread "+name+" State is "+Thread.currentThread().getState());
Thread.sleep(1000 * 10); //抢到锁的线程执行逻辑,这里用睡眠模拟
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread " + name + " release the lock");
}
}
}
public class Test{
public static void main(String[] args) throws Exception{
Object lock = new Object();//锁
BlockThread t1 = new BlockThread(lock,"T1");
BlockThread t2 = new BlockThread(lock,"T2");
t1.start(); //线程 T1开始运行
t2.start(); //线程 T2开始运行
Thread.sleep(100); //阻塞主线程,等待T1,T2抢锁
System.out.println("Thread T1 State is " + t1.getState()); //获取T1线程状态
System.out.println("Thread T2 State is " + t2.getState()); //获取T2线程状态
}
}
输出结果
Thread T1 State is RUNNABLE
Thread T1 hold the lock
Thread T1 State is RUNNABLE
Thread T2 State is RUNNABLE
Thread T1 State is TIMED_WAITING
Thread T2 State is BLOCKED
Thread T1 release the lock
Thread T2 hold the lock
Thread T2 State is RUNNABLE
Thread T2 release the lock
WAITING
class WaitingThread extends Thread {
private Object lock;
public WaitingThread(String name, Object lock) {
super(name);
this.lock = lock;
}
@Override
public void run() {
System.out.println("Thread " + Thread.currentThread().getName()+" try to wait");
synchronized (lock) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Test{
public static void main(String[] args) throws Exception {
Object lock = new Object();
WaitingThread t = new WaitingThread("T", lock);
t.start();
Thread.sleep(1000);
System.out.println("Thread T State is " + t.getState());
System.out.println("Thread "+Thread.currentThread().getName()+" State is " + Thread.currentThread().getState());
}
}
输出结果
Thread T try to wait
Thread T State is WAITING
Thread main State is RUNNABLE
TIMED_WAITING
线程调用
sleep()
方法,进入TIMED_WAITING状态class WaitingThread extends Thread {
private Object lock;
public WaitingThread(String name, Object lock) {
super(name);
this.lock = lock;
}
@Override
public void run() {
synchronized (lock) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Test{
public static void main(String[] args) throws Exception {
Object lock = new Object();
WaitingThread t1 = new WaitingThread("T1", lock);
WaitingThread t2 = new WaitingThread("T2", lock);
t1.start();
t2.start();
Thread.sleep(1000);
System.out.println("Thread T1 State is " + t1.getState());
System.out.println("Thread T2 State is " + t2.getState());
}
}
输出结果
Thread T1 State is TERMINATED
Thread T2 State is TIMED_WAITING
TERMINATED
public class Test implements Runnable{
@Override
public void run() {
}
public static void main(String[] args) throws Exception{
Test test = new Test();
Thread thread = new Thread(test);
thread.start();
System.out.println("Thread State is "+thread.getState());
}
}
输出结果
Thread State is TERMINATED
构造方法
Java中的线程相关操作由Thread类实现,Thread类位于java.lang包,于JDK 1.0版本引入。Thread类封装了多线程操作的上层API,可执行单元代码逻辑通过实现Runnable接口,重写run方法完成,Thread类本身也实现了该接口,接口定义如下所示:
@FunctionalInterface
public interface Runnable {
/**
* When an object implementing interface <code>Runnable</code> is used
* to create a thread, starting the thread causes the object's
* <code>run</code> method to be called in that separately executing
* thread.
* <p>
* The general contract of the method <code>run</code> is that it may
* take any action whatsoever.
*
* @see java.lang.Thread#run()
*/
public abstract void run();
}
在JDK1.8 引入函数式编程后,Runnable接口也被@FunctionalInterface注解标记为函数式接口,支持Lambda表达式。在JDK1.8中,Thread类提供了如下构造方法:
- Thread() :创建一个默认设置的线程对象实例
- Thread(Runnable target) :创建一个包含可执行对象的线程实例
- Thread(Runnable target, String name) :创建一个包含可执行对象,指定名称的线程对象
- Thread(String name):创建一个指定名称的线程对象
- Thread(ThreadGroup group, Runnable target) :创建一个指定线程组,包含可执行对象的线程对象实例
- Thread(ThreadGroup group, Runnable target, String name) :创建一个指定线程组,包含可执行对象,指定线程名称的线程对象实例
- Thread(ThreadGroup group, Runnable target, String name, long stackSize) :创建一个指定线程组、包含可执行对象、指定名称以及堆栈大小的线程对象实例
- Thread(ThreadGroup group, String name):创建一个指定线程组,线程名称的线程实例
说明:关于线程组(ThreadGroup类),一个线程组代表一组线程。此外,一个线程组还可以包括其他线程组。线程组形成一棵树,其中除了初始线程组之外的每个线程组都有一个父级。允许线程访问有关其自己的线程组的信息,但不能访问有关其线程组的父线程组或任何其他线程组的信息。
优先级
Java中创建的线程,每个线程都有一个优先级,具有较高优先级的线程优先于具有较低优先级的线程执行。当在某个线程中运行的代码创建一个新的Thread对象时,新线程的优先级最初设置为等于创建线程的优先级。Thread类中定义了以下三个默认优先级:
// 线程所能设置的最小优先级
public final static int MIN_PRIORITY = 1;
// 创建线程的默认优先级
public final static int NORM_PRIORITY = 5;
// 线程所能设置的最大优先级
public final static int MAX_PRIORITY = 10;
创建线程
Java中如何创建一个线程Thread,可以继承Thread类、实现Runnable或者Callable接口进行显式的创建线程。需要注意的是Runnable接口只是提供了自定义任务执行单元的方法逻辑,而真正关于线程的上层API操作实现还是位于Thread类。查看Thread中重写的run方法源码:
@Override
public void run() {
if (target != null) {
target.run();
}
}
虽然Thread类实现了run方法,但是并没有做任何的特殊处理,真正的任务执行单元,会交给构造方法传入的可执行对象完成逻辑处理,这里使用的是模板方法设计模式。
继承Thread类
通过继承Thread类,并重写run方法。代码如下:
public class Test extends Thread{
@Override
public void run() {
System.out.println("Thread is Created");
}
public static void main(String[] args) {
Test test = new Test();
Thread thread = new Thread(test);
thread.start();
System.out.println(Thread.currentThread().getName());
}
}
在主线程创建新的线程,二者的执行时机由系统调度确定,因此输出结果是随机的,如下所示:
/*新建线程先执行*/
Thread is Created
main
/*主线程先执行*/
main
Thread is Created
实现Runnable接口
通过实现Runnable接口并且重写run方法来创建一个线程。代码如下
public class Test implements Runnable{
@Override
public void run() {
System.out.println("Thread is Created");
}
public static void main(String[] args) {
Test test = new Test();
Thread thread = new Thread(test);
thread.start();
System.out.println(Thread.currentThread().getName());
}
}
输出结果
/*新建线程先执行*/
Thread is Created
main
/*主线程先执行*/
main
Thread is Created
实现Callable接口
严格来讲,参考Oracle官方文档,创建一个Thread只有这两种方式,无论实现Runable接口,还是继承Thread类,都存在一些缺陷,我们无法获得线程的执行结果,无法处理执行过程的异常,这里提供另外一种创建线程的方式。
Callable是JDK 1.5新增的接口,位于java.util.concurrent
包下,Callable接口里面定义了call方法,call方法是run方法的增强版,可以通过实现Callable接口时传入泛型来指定call方法的返回值,并且可以声明抛出异常。
@FunctionalInterface
public interface Callable<V> {
V call() throws Exception;
}
Thread中无论哪一种构造方法都没有Callable类型的target,只能传入Runnable类型的target,那如何把Thread和Callable联系起来,这里就要引入Future
接口和FutureTask
实现类。
Future接口定义如下方法
/**尝试取消执行此任务。*/
boolean cancel(boolean mayInterruptIfRunning)
/**等待计算完成,然后检索其结果。*/
V get()
/**如果需要等待最多在给定的时间计算完成,然后检索其结果(如果可用)。*/
V get(long timeout, TimeUnit unit)
/**如果此任务在正常完成之前被取消,则返回 true 。*/
boolean isCancelled()
/**返回 true如果任务已完成。*/
boolean isDone()
查看源码,RunnableFuture作为一个过渡同时继承了Runnable接口和Future接口,而FutureTask实现了RunnableFuture接口。所以它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值。
public interface RunnableFuture<V> extends Runnable, Future<V> {
void run();
}
查看FutureTask的类图关系
FutureTask类提供了两个构造方法
- FutureTask(Callable
callable):创建一个包含Callable可执行对象的FutureTask实例。 - FutureTask(Runnable runnable, V result):创建一个包含Runnable可执行对象及执行结果的FutureTask实例,参数由RunnableAdapter进行适配包装。
再回到最初的问题如何将实现了Callable接口的线程类作为Thread实例的target,这里经过了以下过程
- 创建线程类实现Callable接口,重写call方法
- 创建FutureTask实例,将实现Callable接口的线程类实例化对象作为FutureTask的target
- 创建Thread类,将FutureTask实例化对象作为Thread的target
Future接口和FutureTask类在中间做了一层包装,代码展示如下
public class Test implements Callable<String> {
@Override
public String call() throws Exception {
System.out.println("Thread is Created");
return "OK";
}
public static void main(String[] args) throws Exception {
Test test = new Test();
FutureTask futureTask = new FutureTask(test);
Thread thread = new Thread(futureTask);
thread.start();
String str = (String) futureTask.get(5,TimeUnit.SECONDS);
System.out.println(str);
System.out.println(Thread.currentThread().getName());
}
}
输出结果
Thread is Created
OK
main
native方法
Java中线程采用内核线程模型来实现用户程序中的线程,因此一些常用方法依托于虚拟机原生实现,下面介绍这些native方法的基本使用。
yield
yield方法是一个native方法,由C++底层进行关于操作系统层面的逻辑处理。yield的字面意思是退让。调用该方法会向调度程序提示当前线程愿意放弃其当前对处理器的使用,调度程序可以随意忽略此提示。
yield是一种启发式尝试,使用它可以改善线程之间的相对进展,否则会过度使用 CPU。在使用yield方法时通常有下面两种使用场景:
- yield的使用应与详细的分析和基准测试相结合,以确保实际上具有预期的效果,但很少使用这种方法。对于调试或测试目的可能很有用,它可能有助于重现由于竞争条件导致的错误
- 在设计并发控制结构(例如 java.util.concurrent.locks 包中的结构)时,它也可能很有用
如下代码所示
public class TestYield {
public static void main(String[] args) {
MyThread thread1 = new MyThread("thread-1");
MyThread thread2 = new MyThread("thread-2");
thread1.start();
thread2.start();
}
private static class MyThread extends Thread {
public MyThread(String name) {
super(name);
}
@Override
public void run() {
for (int i = 1; i <= 5; i++) {
if (i % 2 == 0) {
Thread.yield();
System.out.println(getName() + ":" + i);
}
}
}
}
}
join
join方法让一个线程加入到另一个线程之前执行,在此线程执行期间,其他线程进入阻塞状态,当然也可以指定join入参(指定执行等待的超时时间),最多等待几毫秒让该线程终止,超时0意味着永远等待。
此实现使用以this.isAlive为条件的this.wait调用循环,当线程终止时,将调用this.notifyAll方法。建议应用程序不要在Thread实例上使用wait、notify或notifyAll。如果任何线程中断了当前线程,会抛出InterruptedException异常时清除当前线程的中断状态。
public class TestJoin {
public static void main(String[] args) throws InterruptedException {
MyThread thread1 = new MyThread("thread-1");
MyThread thread2 = new MyThread("thread-2");
thread1.start();
thread1.join();
thread2.start();
}
private static class MyThread extends Thread {
public MyThread(String name) {
super(name);
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(getName());
}
}
}
}
sleep
当调用线程的sleep方法,使当前执行的线程休眠(暂时停止执行)指定的毫秒数,取决于系统计时器和调度程序的精度和准确性。如果任何线程中断了当前线程,会抛出InterruptedException异常时清除当前线程的中断状态。
interrupt
使用interrupt方法会中断这个线程,除非当前线程正在中断自己,否则会调用该线程的checkAccess方法,这可能会导致抛出SecurityException。主要有以下几种场景:
- 如果一个线程被Object类的wait、或者Thread的join、sleep方法调用处于阻塞状态时,那么它的中断状态会被清除并且会收到一个InterruptedException。
- 如果该线程在InterruptibleChannel上的IO操作中被阻塞,则通道将关闭,线程的中断状态将被设置,线程将抛出 java.nio.channels.ClosedByInterruptException。
如果该线程在java.nio.channels.Selector中被阻塞,则该线程的中断状态将被设置,并且它将立即从选择操作返回,可能带有非零值,就像调用了选择器的唤醒方法一样。如果前面的条件都不成立,则将设置该线程的中断状态。
public class TestInterrupt {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "thread");
thread.start();
thread.interrupt();
}
}
输出结果
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at com.starsray.test.api.TestInterrupt.lambda$main$0(TestInterrupt.java:8)
at java.lang.Thread.run(Thread.java:748)
其他
这里就不一一演示,列举在Thread中提供的其他方法。
activeCount()
:返回当前线程的thread group及其子组中活动线程数的估计checkAccess()
:确定当前正在运行的线程是否有权限修改此线程clone()
:将CloneNotSupportedException作为线程抛出无法有意义地克隆currentThread()
:返回对当前正在执行的线程对象的引用dumpStack()
:将当前线程的堆栈跟踪打印到标准错误流enumerate(Thread[] tarray)
:将当前线程的线程组及其子组中的每个活动线程复制到指定的数组中getAllStackTraces()
:返回所有活动线程的堆栈跟踪图getContextClassLoader()
:返回此Thread的上下文ClassLoadergetDefaultUncaughtExceptionHandler()
:返回线程由于未捕获异常突然终止而调用的默认方法getId()
:返回此线程的标识符getName()
:返回此线程的名称getPriority()
:返回此线程的优先级getStackTrace()
:返回表示此线程的堆栈转储的堆栈跟踪元素数组。getState()
:返回此线程的状态getThreadGroup()
:返回此线程所属的线程组getUncaughtExceptionHandler()
:返回由于未捕获的异常,此线程突然终止时调用的处理程序holdsLock(Object obj)
:返回 true当且仅当当前线程在指定的对象上保持监视器锁interrupt()
:中断这个线程interrupted()
:返回当前线程是否中断isAlive()
:返回这个线程是否活着isDaemon()
:返回这个线程是否是守护线程isInterrupted(boolean ClearInterrupted)
:测试某个线程是否已被中断setContextClassLoader(ClassLoader cl)
:设置此线程的上下文ClassLoader。setDaemon(boolean on)
:将此线程标记为 daemon线程或用户线程。- …
守护线程
Java中线程除了用户工作线程外还有一种特殊的线程被称为守护线程。通过setDaemon(true)方法将此线程标记为守护线程或用户线程。当虚拟机中唯一运行的线程都是守护线程时,Java虚拟机退出。该方法必须在线程启动前调用。
守护线程最主要的作用就是服务于虚拟机中的非守护线程,比如虚拟机中的垃圾回收线程就是典型的守护线程。需要注意的是如果是在守护线程中产生的线程,那么这个线程依然是守护线程。如代码所示: ```java import java.io.IOException; import java.util.ArrayList; import java.util.List;
public class TestDaemon { private static volatile boolean restFlag = false; private static final int threshold = 5;
public static void main(String[] args) throws InterruptedException, IOException {
List<Integer> list = new ArrayList<>();
// 工作线程
Thread worker = new Thread(() -> {
while (!restFlag) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (!restFlag) {
list.add(1);
System.out.println("worker is working");
}
}
System.out.println("worker rest");
}, "work-thread");
// 守护线程
Thread employers = new Thread(() -> {
while (!restFlag) {
if (list.size() >= threshold) {
restFlag = true;
System.out.println("employers exit");
}
}
}, "employers-thread");
employers.setDaemon(true);
worker.start();
employers.start();
}
总结
Java中线程使用的是与操作系统1 : 1的内核线程模型,因此线程的创建、运行等依赖于操作系统的调度。线程生命周期中包含六种状态,线程调用不同的方法可以在RUNNING、WATING、TIMED_WAITING、BLOCK之间相互转换。
这里总结一下三种创建线程的方法特点
- 继承Thread类实现多线程:
- 实现起来简单,而且要获取当前线程,无需调用Thread.currentThread()方法,直接使用this即可获取当前线程
- 线程类已经继承Thread类了,就不能再继承其他类
- 多个线程不能共享同一份资源
- 通过实现Runnable接口或者Callable接口实现多线程:
- 线程类只是实现了接口,还可以继承其他类
- 多个线程可以使用同一个target对象,适合多个线程处理同一份资源的情况
- 通过这种方式实现多线程,相较于第一类方式,编程较复杂
- 要访问当前线程,必须调用Thread.currentThread()方法
一般推荐使用以实现接口的方式创建线程类。