本文章是Java多线程系列的一篇文章,其他文章:
Java多线程:锁的底层实现
Java多线程:synchronized和volatile
Java多线程:JUC包-锁的封装
Java多线程:Thread的使用,以及wait(),notify(),notifyAll()
Java多线程:线程池
在Thread类的1750行左右定义了Thread的状态枚举(源码注释简单易懂,建议直接去看源码):
public enum State {
//Thread被创建至今还没有启动
NEW,
//可运行线程的线程状态。当start()方法被调用时,线程就进入RUNNABLE状态。
//此时的线程可能正在运行,也可能在等待操作系统的 其他资源(如处理器)
RUNNABLE,
// 线程由于等待监视器锁而被阻塞在进入同步块/方法的过程中,或者因调用`Object.wait()`方法重入同步块/方法
BLOCKED,
//由于调用Object.wait或Thread.join或LockSupport.park而进入等待状态
//等待状态的线程通常是在等待其他线程完成工作,如调用了Thread.join()的线程正在等待指定的线程终止
WAITING,
//带有等待时间的WAITING状态。通常是由于被调用了如下方法:
//Thread.sleep
//Object.wait with timeout
//Thread.join with timeout
//LockSupport.parkNanos
//LockSupport.parkUntil
TIMED_WAITING,
//线程已结束
TERMINATED;
}
下面是线程状态的转换图(为了便于理解,加了一个RUNNING状态,即线程获取到了CPU时间片时的状态)
java线程可以有优先级的设定,高优先级的线程比低优先级的线程有更高的几率得到执行(不完全正确,请参考下面的“线程优先级的问题“)。
MIN_PRIORITY
,MAX_PRIORITY
,NORM_PRIORITY
来设定优先级Jvm并不保证高优先级的线程一定先执行,所以不要有逻辑依赖于线程优先级,否则可能产生意外结果
start()用来启动一个线程,当调用start方法后,系统才会开启一个新的线程来执行用户定义的子任务,在这个过程中,会为相应的线程分配需要的资源。
run()方法是不需要用户来调用的,当通过start方法启动一个线程之后,当线程获得了CPU执行时间,便进入run方法体去执行具体的任务。注意,继承Thread类必须重写run方法,在run方法中定义具体要执行的任务。
sleep方法有两个重载版本:
sleep(long millis) //参数为毫秒
sleep(long millis,int nanoseconds)//第一参数为毫秒,第二个参数为纳秒
sleep相当于让线程睡眠,交出CPU,让CPU去执行其他的任务。
但是有一点要非常注意,sleep方法不会释放锁,也就是说如果当前线程持有对某个对象的锁,则即使调用sleep方法,其他线程也无法访问这个对象。
调用yield方法会让当前线程交出CPU权限,让CPU去执行其他的线程。它跟sleep方法类似,同样不会释放锁。但是yield不能控制具体的交出CPU的时间,另外,yield方法只能让拥有相同优先级的线程有获取CPU执行时间的机会。
注意,调用yield方法并不会让线程进入阻塞状态,而是让线程重回就绪状态,它只需要等待重新获取CPU执行时间,这一点是和sleep方法不一样的。
join方法有三个重载版本:
join()
join(long millis)//参数为毫秒
join(long millis,int nanoseconds)//第一参为毫秒,第二参为纳秒
假如在main线程中,调用thread.join方法,则main方法会等待thread线程执行完毕或者等待一定的时间。如果调用的是无参join方法,则等待thread执行完毕,如果调用的是指定了时间参数的join方法,则等待一定的时间。
在同一个线程中可以多次join其他线程,如果join了多个线程,则等待所有join的线程执行完毕之后当前线程才会继续执行
实际上调用join方法是调用了Object的wait方法,这个可以通过查看源码得知:
public final synchronized void join(long millis)throws InterruptedException {
//...
if (millis == 0)
while (isAlive()) wait(0);
//...
}
注意 这三个方法都是java.lang.Object的方法。
这三个方法用于协调多个线程对共享数据的存取,所以必须在synchronized语句块内使用。synchronized关键字用于保护共享数据,阻止其他线程对共享数据的存取,但是这样程序的流程就很不灵活了,如何才能在当前线程还没退出synchronized数据块时让其他线程也有机会访问共享数据呢?此时就用这三个方法来灵活控制。
wait()方法使当前线程暂停执行并释放对象锁标示,让其他线程可以进入synchronized数据块,当前线程被放入对象等待池中。当调用notify()方法后,将从对象的等待池中移走一个任意的线程并放到锁标志等待池中,只有锁标志等待池中线程能够获取锁标志;如果锁标志等待池中没有线程,则notify()不起作用。
notifyAll()则从对象等待池中移走所有等待那个对象的线程并放到锁标志等待池中。
具体可参考Java Object对象中的wait,notify,notifyAll
interrupt,顾名思义,即中断的意思。单独调用interrupt方法可以使得处于阻塞状态的线程抛出一个异常,也就说,它可以用来中断一个正处于阻塞状态的线程;另外,通过interrupt方法和isInterrupted()方法来停止正在运行的线程。
下面看一个例子:
public class Test {
public static void main(String[] args) throws IOException {
Test test = new Test();
MyThread thread = test.new MyThread();
thread.start();
try {
Thread.currentThread().sleep(2000);
} catch (InterruptedException e) {
}
thread.interrupt();
}
class MyThread extends Thread{
@Override
public void run() {
try {
System.out.println("enter sleep");
Thread.currentThread().sleep(10000);
System.out.println("sleep complete");
} catch (InterruptedException e) {
System.out.println("InterruptedException ");
}
System.out.println("run complete");
}
}
}
运行的结果为
enter sleep
InterruptedException
run complete
但是用interrupt方法是没办法终止正在运行状态的线程的。但是可以通过配合isInterrupt方法类终止正在运行的线程:
public class Test {
public static void main(String[] args) throws IOException {
Test test = new Test();
MyThread thread = test.new MyThread();
thread.start();
try {
Thread.currentThread().sleep(2000);
} catch (InterruptedException e) {
}
thread.interrupt();
}
class MyThread extends Thread{
@Override
public void run() {
int i = 0;
while(!isInterrupted() && i" while循环");
i++;
}
}
}
}
运行会发现,打印若干个值之后,while循环就停止打印了。
但是一般情况下不建议通过这种方式来中断线程,一般会在MyThread类中增加一个属性 isStop来标志是否结束while循环,然后再在while循环中判断isStop的值。
Java并发编程:Thread类的使用介绍
java之Thread线程相关yield()、sleep()、wait()、join()、run和start区别详解
What is Java thread priority?,Java进程优先级和系统优先级的对应关系