一、线程生命周期
在《详解操作系统进程》中,从操作系统层面介绍了进程(线程)的生命周期的变迁,在操作系统中,线程的状态主要包含了五种:初始化、等待状态、就绪状态、运行状态和终止状态
但在Java中,定义了六种状态的,其中RUNNABLE状态对应运行状态和就绪状态,而等待状态在Java中细分为三种BLOCKED、WAITING、TIMED_WAITING
关于这三种状态的描述,下面的源码中的注释已经解释的很清楚
BLOCKED状态对应于对象锁,某个线程竞争对象锁失败时就处于阻塞状态
WAITING状态对应于永久等待,如果没有被唤醒,将会处于永久等待的状态
TIMED_WAITING状态对应于超时等待,超过了等待的时间之后,它们就会被主动唤醒,然后处于RUNNABLE状态,等待CPU分配时间片
public enum State {
NEW,
RUNNABLE,
/**
* Thread state for a thread blocked waiting for a monitor lock.
* A thread in the blocked state is waiting for a monitor lock
* to enter a synchronized block/method or
* reenter a synchronized block/method after calling
* {@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中这六种线程状态的生命周期,可以用下面的图来表示:
在上图中,调用notify()和notifyAll()方法后,可能会进入到阻塞状态,因为wait()方法和notify的两个方法需要结合Synchronized关键字使用,被唤醒后,就会去竞争对象锁,如果没有拿到对象锁,就会处于Blocked状态
从JVM的层面,也定义一些针对JavaThread对象的状态
二、Thread常用方法
2.1 sleep()
当线程调用sleep()方法之后,会让当前线程从RUNNABLE状态切换到TIMED_WAITING状态,但是并不会释放对象锁
下面的代码展示了,我们在调用sleep()方法时,可能会抛出InterruptedException中断异常,这是因为在其他线程中,可以使用interrupt()方法中断正在睡眠的线程,这时调用sleep()方法的线程就会抛出中断异常,并且会清除中断标志(方法注释上说明的很清楚)
睡眠结束的线程处于RUNNABLE状态,未必会立刻得到执行
当sleep()传入的参入为0时,和yield()方法相同
RunnableTest runnableTest = new RunnableTest();
Thread thread = new Thread(runnableTest);
thread.setPriority(5);
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
/**
* The thread does not lose ownership of any monitors.
* @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;
2.2 yield()
当线程调用yield()方法时,当前线程会让出CPU,然后从RUNNING状态切换到READY状态,让优先级更高的线程获得执行机会,不会释放对象锁
假设当前JVM进程只有main线程,当调用yield方法之后,main线程会继续运行,因为没有比它优先级更好的线程
具体的实现依赖于操作系统的任务调度器,正如方法注释上说的,任务调度器可以忽略这个标志,也就是说就算当前线程执行了yield()方法,它也有可能即刻就被处理器调度
/**
* A hint to the scheduler that the current thread is willing to yield
* its current use of a processor. The scheduler is free to ignore this
* hint.
*/
public static native void yield();
2.3 join()
join()方法主要用于等待调用join()方法的线程执行完成之后,在执行后面的逻辑。一般用于等待异步线程执行结果之后再运行的场景
public class ThreadJoinDemo {
public static void main(String[] sure) throws InterruptedException {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("t begin");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t finished");
}
});
long start = System.currentTimeMillis();
t.start();
//主线程等待线程t执行完成
t.join();
System.out.println("执行时间:" + (System.currentTimeMillis() - start));
System.out.println("Main finished");
}
}
所有重载的join()方法,最后都会调用下面的方法,可以看到,join()方法就是判断当前线程是否还在存活,如果存在就一直调用wait()方法进行等待(特别注意:不论哪个线程调用的join()方法,最终都是执行join()方法的代码段所在的线程在真正调用wait()方法,进行等待,比如上面的t.join()
是在主线程执行的,那么就是主线程去执行wait()方法,如果t.join()
是在另外一个线程中执行的,那么就有该主线程去执行wait()方法)
join()方法会使当前所在线程一直等待下去,直到被其他线程中断或者join的线程执行完毕,如果指定了时间,当前线程也会在时间到达后,退出阻塞状态
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
2.4 stop
stop()方法已经被JDK弃用,原因是stop()方法太过暴力,它可以强行把执行到一般的线程终止。
比如下面的例子,线程执行一半被终止后,锁也被释放了,就可能导致thread线程的run()方法执行出现数据不一致的情况
public class ThreadStopDemo {
private static Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
synchronized (lock) {
System.out.println(Thread.currentThread().getName() + "获取锁");
try {
Thread.sleep(60000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "执行完成");
}
});
thread.start();
Thread.sleep(2000);
// 停止thread,并释放锁
thread.stop();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "等待获取锁");
synchronized (lock) {
System.out.println(Thread.currentThread().getName() + "获取锁");
}
}
}).start();
}
}
那么Java中有没有比较优雅的停止线程的方式呢?其实Java本身并没有提供一种简单、直接的方式来停止线程,而是提供了一种中断机制来实现优雅的停止线程
三、Java线程中断机制
中断机制是一种协作机制,也就是说它并不能直接中断某个线程,而是需要被中断的线程自己决定是否中断。
被中断的线程拥有完全的自主权,它既可以选择立即停止,也可以选择一段时间后停止,也可以选择压根不停止
主要实现就是通过中断方法将线程的中断标志为设置为true,然后线程可以在自己run()方法里面去判断当前线程的标志位决定是否退出
相关API:
- interrupt():将线程中的中断标志为设置为true,不会停止线程
- isInterrupted():判断当前线程的标志位是否位true,不会清除中断标志位
- Thread.interrupted():判断当前线程的中断标志位是否位true,并清除中断标志,重置为false
public class ThreadInterruptTest {
static int i = 0;
public static void main(String[] args) {
System.out.println("begin");
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
i++;
System.out.println(i);
//Thread.interrupted() 清除中断标志位
//Thread.currentThread().isInterrupted() 不会清除中断标志位
if (Thread.currentThread().isInterrupted() ) {
System.out.println("=========");
}
if(i==10){
break;
}
}
}
});
t1.start();
//不会停止线程t1,只会设置一个中断标志位 flag=true
t1.interrupt();
}
}
利用中断机制优雅的停止线程
while (!Thread.currentThread().isInterrupted() && more work to do) {
do more work
public class StopThread implements Runnable {
@Override
public void run() {
int count = 0;
while (!Thread.currentThread().isInterrupted() && count < 1000) {
System.out.println("count = " + count++);
}
System.out.println("线程停止: stop thread");
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new StopThread());
thread.start();
Thread.sleep(5);
thread.interrupt();
}
}
注:使用中断机制时一定要注意是否存在中断标志为被清除的情况
线程sleep期间能够感受到中断,修改上面的代码,线程执行期间有休眠需求
@Override
public void run() {
int count = 0;
while (!Thread.currentThread().isInterrupted() && count < 1000) {
System.out.println("count = " + count++);
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("线程停止: stop thread");
}
处于休眠中的线程被中断,线程是可以感受到中断信号的,并且会抛出一个InterruptedException异常,同时清除中断信号,将中断信号设置为false。这样就会导致while条件Thread.currentThread().isInterrupted()
为false,程序会在不满足count < 1000
时退出。如果不再catch语句中重新添加中断信号,不做任何处理,中断信号就被屏蔽掉了,可能导致while循环里面的代码会一直执行,无法正确退出
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
//重新设置线程中断状态为true
Thread.currentThread().interrupt();
sleep和wait的线程都可以被中断,抛出中断异常:InterruptedException,同时清除中断标志位
四、线程间通信
4.1 Volatile
volatile有两大特性,一是可见性,二是有序性,禁止指令重排序,其中可见性就是可以让线程之间进行通信。
public class VolatileDemo {
private static volatile boolean flag = true;
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
while (true){
if (flag){
System.out.println("trun on");
flag = false;
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
while (true){
if (!flag){
System.out.println("trun off");
flag = true;
}
}
}
}).start();
}
}
4.2 等待唤醒机制
等待唤醒机制是通过wait和notify方法来实现的,在一个线程内调用该线程锁对象的wait()方法,线程将进入等待队列进行等待直到被唤醒
下面的代码,线程执行wait()方法后,会释放lock对象锁,其他线程可以竞争该对象锁,notify()方法只能唤醒一个等待lock对象锁的线程,而notifyAll()方法会去唤醒所有等待lock对象锁的线程
并且notify()方法只能随意唤醒某一个等待lock对象锁的线程,不能指定唤醒某一个线程
public class WaitDemo {
private static Object lock = new Object();
private static boolean flag = true;
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
synchronized (lock){
while (flag){
try {
System.out.println("wait start .......");
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("wait end ....... ");
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
if (flag){
synchronized (lock){
if (flag){
lock.notify();
System.out.println("notify .......");
flag = false;
}
}
}
}
}).start();
}
}
注:wait于notify()必须结合synchronized关键字使用,否则会抛出IllegalMonitorStateException
异常
等待唤醒机制的另一种实现是LockSupport,LockSupport是JDK中用来实现线程阻塞和唤醒的工具,线程调用park()方法则等待”许可“,调用unpark()方法则为指定线程提供”许可“。使用LockSupport的好处是,可以在任何场景使线程阻塞(不需要像wait()方法需要结合synchronized关键),并且不用担心阻塞和唤醒的顺序,但是要注意多次唤醒的效果和一次唤醒的效果一样
public class LockSupportTest {
public static void main(String[] args) {
Thread parkThread = new Thread(new ParkThread());
parkThread.start();
System.out.println("唤醒parkThread");
LockSupport.unpark(parkThread);
}
static class ParkThread implements Runnable{
@Override
public void run() {
System.out.println("ParkThread开始执行");
LockSupport.park();
System.out.println("ParkThread执行完成");
}
}
}
4.3 管道输入输出流
管道输入/输出流和普通文件输入/输出流或者网络输入/输出流的不同指出在于,它主要用于线程之间的数据传输,而传输的媒介为内存。管道输入/输出流主要包括如下四种具体实现:
PipedOutputStream、PipedInputStream、PipedReader和PipedWriter,前面两种面向字节,而后面两种面向字符
public class PipedTest {
public static void main(String[] args) throws Exception {
PipedWriter out = new PipedWriter();
PipedReader in = new PipedReader();
// 将输出流和输入流进行连接,否则在使用时会抛出IOException
out.connect(in);
Thread printThread = new Thread(new Print(in), "PrintThread");
printThread.start();
int receive = 0;
try {
while ((receive = System.in.read()) != -1) {
out.write(receive);
}
} finally {
out.close();
}
}
static class Print implements Runnable {
private PipedReader in;
public Print(PipedReader in) {
this.in = in;
}
@Override
public void run() {
int receive = 0;
try {
while ((receive = in.read()) != -1) {
System.out.print((char) receive);
}
} catch (IOException ex) {
}
}
}
}
4.4 Thread.join()
join()方法可以理解为线程合并,将并行的线程通过join()方法合并成串行的,因为当前线程会一直阻塞等待调用join()方法的线程执行完毕才能继续执行(如果没有指定时间),所以join()方法的好处是可以保证线程的执行顺序,但是如果调用线程的join()方法其实已经失去了并行的意义,虽然存在多个线程,但这些线程本质上还是串行的。join()的实现也是基于等待唤醒机制的。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/153634.html