一、线程相关类和接口
1、Thread类:
(1)继承关系:
java.lang.Object
|____java.lang.Thread
(2)类声明:
package java.lang;
public class Thread implements Runnable {
private static native void registerNatives();
static {
registerNatives();
}
}
(3)常用构造方法:
Thread类有很多个构造方法,这里仅仅列出常用的几个
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
public Thread(String name) {
init(null, null, name, 0);
}
public Thread(Runnable target, String name) {
init(null, target, name, 0);
}
无参构造 和 接受一个参数(Runnable 接口的实现类的实例)的构造方法,使用默认的 “Thread+nextThreadNum()" 作为线程的默认名称。
另外两个构造方法,都接受一个String类型的字符串作为线程的名称。
2、Runnable接口
(1)类声明:
package java.lang;
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
(2)@FunctionalInterface(函数式接口):
我们常用的一些接口Callable、Runnable、Comparator等在JDK8中都添加了@FunctionalInterface注解。
@FunctionalInterface注解的特点:
- 该注解只能标记在"有且仅有一个抽象方法"的接口上。
- JDK8接口中的静态方法和默认方法,都不算是抽象方法。
- 接口默认继承java.lang.Object,所以如果接口显示声明覆盖了Object中方法,那么也不算抽象方法。
- 该注解不是必须的,如果一个接口符合"函数式接口"定义,那么加不加该注解都没有影响。加上该注解能够更好地让编译器进行检查。如果编写的不是函数式接口,但是加上了@FunctionInterface,那么编译器会报错。
3、Callable接口
(1)接口声明:
package java.util.concurrent;
@FunctionalInterface
public interface Callable<V> {
V call() throws Exception;
}
该接口和Runnable接口类似,用于实现多线程。该接口也是一个函数式接口。Callable接口与Runnable接口相比,Callable接口的线程执行体为call()方法,该方法可以有返回值,也可以抛出异常。
二、常用方法
(1)设置获取线程的名称
设置:
- 使用构造方法;
- setName("XXX");
获取:
- getName();
- THread.currentThread.getName();推荐
(2)线程休眠
Thread.sleep()
使得当前正在执行的线程休眠一段时间,释放时间片,导致线程进入阻塞状态 sleep(5000),5000的单位是毫秒,设置了sleep就相当于将当前线程挂起5s,这个操作跟线程的优先级无关,当对应的时间到了之后,还会再继续执行。
该方法会抛出异常,需要使用 try-catch 语句进行异常处理。
try {
//线程休眠两秒
Thread.sleep(9000);
} catch (InterruptedException e) {
e.printStackTrace();
}
TimeUnit.SECONDS.sleep(2)
try {
//线程休眠两秒
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
TimeUnit类(JUC包中)
- TimeUnit 表示给定单元粒度的时间段,它提供在这些单元中进行跨单元转换和执行计时及延迟操作的实用工具方法。
- TimeUnit 不维护时间信息,但是有助于组织和使用可能跨各种上下文单独维护的时间表示形式。
- TimeUnit 主要用于通知基于时间的方法如何解释给定的计时参数。
类声明和继承关系:
java.lang.Object
|____java.lang.Enum<TimeUnit>
|____java.util.concurrent.TimeUnit
package java.util.concurrent;
public enum TimeUnit {}
枚举常量摘要:
枚举常量 | 描述 |
MICROSECONDS | 微秒 一百万分之一秒 (就是毫秒/1000) |
MILLISECONDS | 毫秒 千分之一秒 |
NANOSECONDS | 毫微秒 十亿分之一秒 (就是微秒/1000) |
SECONDS | 秒 |
MINUTES | 分钟 |
HOURS | 小时 |
DARS | 天 |
主要接口:
方法 | 描述 |
long convert(long duration, TimeUnit unit) | 将给定单元的时间转换到此单元 |
void sleep(long timeout) | 使用此单元执行Thread.sleep,这是将时间参数转换成 Thread.sleep 方法所需格式的便捷方法。 |
void timedJoin(Thread thread, long timeout) | 使用此单元执行计时的 Thread.join() |
void timedWait(Object obj, long timeout) | 使用此单元执行计时的Object.wait() |
long toMicros(long duration) | 等效于 MICROSECONDS.convert(duration, this) |
long toMillis(long duration) | 等效于 MILLISECONDS.convert(duration, this) |
long toNanos(long duration) | 等效于 NANOSECONDS.convert(duration, this) |
long toSeconds(long duration) | 等效于 SECONDS.convert(duration, this) |
static TimeUnit valueOf(String name) | 返回带有指定名称的该类型的枚举常量 |
static TimeUnit[] values() | 按照声明该枚举类型的常量的顺序,返回包含这些常量的数组。 |
注意:
- 它的常量MICROSECONDS,MILLISECONDS,NANOSECONDS,SECONDS都是TimeUnit类型;
- convert(long duration, TimeUnit unit)的意思将duration这个时间转化为本对象(this)所表示的时间形式。本对象可能MICROSECONDS,MILLISECONDS,NANOSECONDS,SECONDS的一种。至于duration是哪种形式的时间(MICROSECONDS,MILLISECONDS,NANOSECONDS,SECONDS的一种),则是通过参数TimeUnit unit来确定的
- TimeUnit是enum(枚举)类型,不能通过外部来实例化。
- 对于常量MINUTES,HOURS,DAYS,在Java1.5和Android中并没看到,但是在Java1.6中却有。
(3)设置线程优先级
Thread.setPriority(10);
可以通过设置优先级来改变线程抢到时间片的概率,优先级高的线程获得较多的执行机会。默认情况下,每个线程的优先级都与创建它的父线程具有相同的优先级,例如:main线程具有普通优先级,则由 main 线程创建的子线程也有相同的普通优先级。
Thread类中定义了三个常量,分别表示最高、最低和默认优先级。
public final static int MIN_PRIORITY = 1;
public final static int NORM_PRIORITY = 5;
public final static int MAX_PRIORITY = 10;
注意:
- 优先级范围1~10,默认为5,对应的数值越大,说明优先级越高,这个方法的设置一定要在 start 之前;
- 线程的优先级低并不意味着争抢不到时间片,只是抢到时间片的概率比较低而已。
(4)线程插队:join()
在执行原来线程的过程中,如果遇到了插队进来的线程,则优先执行插队进来的线程,执行完插队进来的线程后,再回到原来的任务中,继续执行原来的线程。
特点:
- 线程插队,当前线程一定会释放cpu时间片,cpu会将时间片分给要Join的线程;
- 哪个线程需要插队就在当前线程中,添加要插队的线程;
- join之前,一定要将线程处于准备状态start;
(5)线程让步:yield()
可以让当前正在执行的线程暂停,但它不会阻塞该线程,他只是将该线程转入就绪状态,完全可能出现的情况 是:当某个线程调用了yield方法暂停之后,线程调度器会重新根据线程的优先级进行调度线程执行。实际上,当某个线程调用了yield方法暂停之后,只有优先级与当前线程相同,或者优先级比当前线程更高的就绪状态的线程才会获得执行的机会。
(6)线程中断:interrupt()
线程中断:在线程运行(执行run()方法)中间打断它。在Java中,提供三个有关线程中断的方法:
//中断线程(实例方法)
public void Thread.interrupt();
//判断线程是否被中断(实例方法)
public boolean Thread.isInterrupted();
//判断是否被中断并清除当前中断状态(静态方法)
public static boolean Thread.interrupted();
线程中断可以分两种情况:
(1)中断阻塞状态的线程(睡觉过程中被interrupt()方法打醒,并抛出一个异常)
当一个想线程处于阻塞状态或试图执行一个阻塞操作时,使用 Thread.interrupt() 方式中断该线程。
注意此时将会抛出一个 InterruptException 的异常,同时中断状态将会复位(由中断状态改为非中断状态),演示如下:
public class InterruputSleepThread3 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread() {
@Override
public void run() {
//while在try中,通过异常中断就可以退出run循环
try {
while (true) {
//当前线程处于阻塞状态,异常必须捕捉处理,无法往外抛出
//线程睡眠2s
TimeUnit.SECONDS.sleep(2);
}
} catch (InterruptedException e) {
System.out.println("Interruted When Sleep");
boolean interrupt = this.isInterrupted();
//中断状态被复位
System.out.println("interrupt:"+interrupt);
}
}
};
t1.start();
TimeUnit.SECONDS.sleep(2);
//中断处于阻塞状态的线程
t1.interrupt();
/**
* 输出结果:
Interruted When Sleep
interrupt:false
*/
}
}
如上述所示,创建一个线程,并调用了sleep() 方法从而使线程进入阻塞状态,启动线程后,调用线程实例对象的interrupt() 方法中断阻塞线程,并抛出 InterruptedException 异常,此时中断状态也将复位。使用 TimeUnit.SECONDS.sleep(2) 让线程睡眠两秒,语义很清晰,推荐使用,注意 TimeUnit 是枚举类型。
除了阻塞中断的情景,可能还会遇到处于运行期且非阻塞状态的线程。
(2)中断非阻塞状态下运行的线程
中断非阻塞状态下运行的线程,这种情况下,直接调用 Thread.interrupt() 方法中断线程是不会得到任何响应的,如下代码,将无法中断非阻塞状态下的线程:
import java.util.concurrent.TimeUnit;
public class InterruptThread {
public static void main(String[] args) throws InterruptedException {
Thread t1=new Thread(){
@Override
public void run(){
while(true){
System.out.println("未被中断");
}
}
};
t1.start();
TimeUnit.SECONDS.sleep(2);
t1.interrupt();
/**
* 输出结果(无限执行):
未被中断
未被中断
未被中断
......
*/
}
}
虽然我们调用了 interrupt() 方法,但线程 1 并未中断,因为处于非阻塞状态的线程需要我们手动进行中断检测并结束程序,改进后的代码:
public class InterruputThread {
public static void main(String[] args) throws InterruptedException {
Thread t1=new Thread(){
@Override
public void run(){
while(true){
//判断当前线程是否被中断
if (this.isInterrupted()){
System.out.println("线程中断");
break;
}
}
System.out.println("已跳出循环,线程中断!");
}
};
t1.start();
TimeUnit.SECONDS.sleep(2);
t1.interrupt();
/**
* 输出结果:
线程中断
已跳出循环,线程中断!
*/
}
}
在代码中使用实例方法 isinterrupted() 判断线程是否已被中断,如果被中断将跳出循环以结束线程。注意非阻塞状态下调用 interrupt() 方法不会导致中断状态重置。
综上所述,中断分为两种情况,一种是中断处于阻塞状态或者视图执行一个阻塞操作的线程,另一种是中断非阻塞状态的运行的线程。如果需要两种情况兼顾,可以如下编程:
public void run(){
try {
//判断当前线程是否已中断,注意interrupted方法是静态的,执行后会对中断状态进行复位
while (!Thread.interrupted()) {
TimeUnit.SECONDS.sleep(2);
}
} catch (InterruptedException e) {
}
}