创建和运行线程
方法一 直接创建Thread
public static void main(String[] args) {
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
//线程要执行的任务
log.info("你好,世界!");
}
},"线程1");
//启动线程就绪状态,等待Cpu分配时间片执行线程
thread1.start();
}
Java8以后可以用lambda简写,如下
public static void main(String[] args) {
Thread thread1 = new Thread(() -> log.info("你好,世界!"),"线程1");
//线程就绪,等待Cpu分配时间片执行线程
thread1.start();
}
方法二 使用Runnable配合Thread
把【线程】和【任务】(要执行的代码)分开
- Thread代表线程
- Runnable可运行的任务(线程要执行的代码)
public static void main(String[] args) {
//创建任务对象
Runnable runnable = new Runnable() {
@Override
public void run() {
log.info("你好,世界!");
}
};
//参数一是任务对象,参数二是线程名
Thread thread1 = new Thread(runnable,"线程1");
//线程就绪,等待Cpu分配时间片执行线程
thread1.start();
}
总结:Thread与Runnable的关系
- 方法一是把线程与任务合并在了一起,方法二是把线程和任务分开了
- 使用Runnable更容易与线程池等高效API配合
- 用Runnable让任务类脱离了Thread继承体系,更加灵活
方法 FutureTask配合Thread
FutureTask 能够接收 Callable 类型的参数,用来处理有返回结果的情况
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 创建任务对象
FutureTask<Integer> task3 = new FutureTask<>(() -> {
log.info("hello");
return 100;
});
// 参数1 是任务对象; 参数2 是线程名字,推荐
new Thread(task3, "t3").start();
// 主线程阻塞,同步等待 task3 执行完毕的结果
Integer result = task3.get();
log.info("结果是:{}", result);
}
输出
19:22:27 [t3] c.ThreadStarter - hello
19:22:27 [main] c.ThreadStarter - 结果是:100
主线程与守护线程
默认情况下,Java 进程需要等待所有线程都运行结束,才会结束。有一种特殊的线程叫做守护线程,只要其它非守护线程运行结束了,即使守护线程的代码没有执行完,也会强制结束。
例如:
log.info("开始运行...");
Thread t1 = new Thread(() -> {
log.info("开始运行...");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
log.info("运行结束...");
}, "daemon");
// 设置该线程为守护线程
t1.setDaemon(true);
t1.start();
Thread.sleep(1000);
log.info("运行结束...");
输出:
20:37:49.694 [main] org.juc.yren.Main - 开始运行...
20:37:49.781 [daemon] org.juc.yren.Main - 开始运行...
20:37:50.783 [main] org.juc.yren.Main - 运行结束...
可以看出daemon守护线程没有执行运行结束....线程就结束了
注意
Jvm垃圾回收器是一种守护线程
Tomcat中Acceptor和Poller线程都是守护线程,所以Tomcat接收到shutdown命令后,不会等待它们处理完当前请求
线程的状态
五种状态(从操作系统层面来描述)
这是从 操作系统 层面来描述的
- 【初始状态】仅是在语言层面创建了线程对象,还未与操作系统线程关联(可以理解为new Thread())
- 【可运行状态】(就绪状态)指该线程已经被创建(与操作系统线程关联),可以由 CPU 调度执行 (可以理解为调用了t.start())
- 【运行状态】指获取了 CPU 时间片运行中的状态
- 当 CPU 时间片用完,会从【运行状态】转换至【可运行状态】,会导致线程的上下文切换
- 【阻塞状态】
- 如果调用了阻塞 API,如 BIO 读写文件,这时该线程实际不会用到 CPU,会导致线程上下文切换,进入【阻塞状态】
- 等 BIO 操作完毕,会由操作系统唤醒阻塞的线程,转换至【可运行状态】
- 与【可运行状态】的区别是,对【阻塞状态】的线程来说只要它们一直不唤醒,调度器就一直不会考虑调度它们
- 【终止状态】表示线程已经执行完毕,生命周期已经结束,不会再转换为其它状态
六种状态(从JAVA API层面来描述)
这是从 JAVA API 层面来描述的
根据Thread.State枚举,分为六种状态
public enum State {
/**
* 尚未启动的线程的线程状态。
*/
NEW,
/**
* 可运行线程的线程状态。处于可运行状态的线程正在Java虚拟机中执行,
* 但它可能正在等待来自操作系统的其他资源,例如处理器
*/
RUNNABLE,
/**
* 该线程被阻止等待监视器锁定。处于阻塞状态的线程
* 正在等待监视器锁进入同步块/方法,或在调用Object.wait后重新进入同步块或方法。
*/
BLOCKED,
/**
* 等待线程的线程状态。
* 处于等待状态的线程正在等待另一个线程执行特定操作。
* 例如,对对象调用了Object.wait()的线程正在等待另一个线程
* 对该对象调用Object.notify()或Object.notifyAll()。
* 调用了thread.join()的线程正在等待指定的线程终止。
*/
WAITING,
/**
* 等待线程的线程状态。
* 具有指定等待时间的等待线程的线程状态
*/
TIMED_WAITING,
/**
* 终止线程的线程状态。线程已完成执行。
*/
TERMINATED;
}
- NEW 线程刚被创建,但是还没有调用 start() 方法
- RUNNABLE 当调用了 start() 方法之后,注意,Java API 层面的 RUNNABLE 状态涵盖了 操作系统 层面的【可运行状态】、【运行状态】和【阻塞状态】(由于 BIO 导致的线程阻塞,在 Java 里无法区分,仍然认为是可运行)
- BLOCKED , WAITING , TIMED_WAITING 都是 Java API 层面对【阻塞状态】的细分
- TERMINATED 当线程代码运行结束
线程(Thread)常用方法
| 方法名 | static | 功能说明 | 注意 |
|---|---|---|---|
| start() | 启动一个新线程,在新的线程运行 run 方法中的代码 | start 方法只是让线程进入就绪,里面代码不一定立刻运行(CPU 的时间片还没分给它)。每个线程对象的start方法只能调用一次,如果调用了多次会出现IllegalThreadStateException | |
| run() | 新线程启动后会调用的方法 | 如果在构造 Thread 对象时传递了Runnable 参数,则线程启动后会调用 Runnable 中的 run 方法,否则默认不执行任何操作。但可以创建 Thread 的子类对象,来覆盖默认行为 | |
| join() | 等待线程运行结束 | ||
| join(long n) | 等待线程运行结束,最多等待 n 毫秒 | ||
| getId() | 获取线程长整型的 id | id唯一 | |
| getName() | 获取线程名 | ||
| setName(String) | 设置线程名 | ||
| getPriority() | 获取线程优先级 | ||
| setPriority() | 修改线程优先级 | java中规定线程优先级是1~10 的整数,较大的优先级能提高该线程被 CPU 调度的机率 | |
| getState() | 获取线程状态 | Java 中线程状态使用 6 个 enum 表示,分别为:NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED | |
| isInterrupted() | 判断是否被打断(中断) | 不会清除打断标记 | |
| interrupted() | static | 判断是否被打断(中断) | 会清除打断标记 |
| isAlive() | 线程是否存活(还没有运行完毕) | ||
| interrupt() | 打断线程 | 如果被打断线程正在 sleep,wait,join 会导致被打断的线程抛出 InterruptedException,并清除 打断标记;如果打断的正在运行的线程,则会设置打断标记;park 的线程被打断,也会设置 打断标记 | |
| currentThread() | static | 获取当前正在执行的线程 | |
| sleep(long n) | static | 让当前执行的线程休眠n毫秒,休眠时让出 cpu 的时间片给其它线程 | 睡眠结束后的线程未必会立刻得到执行,需要等待CPU分配时间片,建议用 TimeUnit 的 sleep 代替 Thread 的 sleep 来获得更好的可读性 |
| yield() | static | 提示线程调度器让出当前线程对CPU的使用 | 主要是为了测试和调试 |
sleep()
- 调用sleep会让当前线程从Running进入Timed Waiting状态(阻塞)
- 其他线程可以使用interrupt方法打断该正在sleep的睡眠线程,这时sleep方法会抛出InterruptedException
- 睡眠结束后的线程也未必会立刻得到执行(虽然一直持有锁,但是需要获得CPU是时间片才可以继续执行)
- 建议使用TimeUnit的sleep代替Thread的sleep来获得更好的可读性
打断正在sleep的线程
Thread thread1 = new Thread() {
@Override
public void run() {
while (true) {
//休眠100秒
try {
TimeUnit.SECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (Thread.currentThread().isInterrupted()) {
System.out.println("我要退出了!");
break;
}
}
}
};
运行上面的代码,发现程序无法终止。为什么? 需要修改为
Thread thread1 = new Thread() {
@Override
public void run() {
while (true) {
//休眠100秒
try {
TimeUnit.SECONDS.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); //新增
e.printStackTrace();
}
if (this.isInterrupted()) {
System.out.println("我要退出了!");
break;
}
}
}
};
注意:sleep方法由于中断而抛出异常之后,线程的中断标志会被清除(置为false),所以在异常中需要执行this.interrupt()方法,将中断标志位置为true
sleep/wait区别
功能都相当于暂停当前线程,有什么区别?
- sleep是Thread的方法,阻塞当前线程不会释放锁
- wait是Object的方法,必须在synchronized同步代码块中执行,会使当前对象释放锁,进入Monitor ->waitSet等待唤醒(notify)
线程中断
在java中,线程中断是一种重要的线程写作机制,从表面上理解,中断就是让目标线程停止执行的意思,实际上并非完全如此。stop方法停止线程的过于暴力是直接停止,jdk中提供了更好的中断线程的方法。严格的说,线程中断并不会使线程立即退出,而是给线程发送一个通知,告知目标线程,有人希望你退出了!至于目标线程接收到通知之后如何处理,则完全由目标线程自己决定,这点很重要,如果中断后,线程立即无条件退出,我们又会到stop方法的老问题。
Thread类提供打断方法
Thread提供了3个与线程中断有关的方法,这3个方法容易混淆,大家注意下:
public void interrupt() //中断线程
public boolean isInterrupted() //判断线程是否被中断
public static boolean interrupted() //判断线程是否被中断,并清除当前中断状态
interrupt()方法是一个实例方法,它通知目标线程中断,也就是设置中断标志位为true,中断标志位表示当前线程已经被中断了。isInterrupted()方法也是一个实例方法,它判断当前线程是否被中断(通过检查中断标志位)。最后一个方法interrupted()是一个静态方法,返回boolean类型,也是用来判断当前线程是否被中断,但是同时会清除当前线程的中断标志位的状态。
清除打断状态则:打断状态true -> 未打断状态false
public static void main(String[] args) throws ExecutionException, InterruptedException {
Thread t1 = new Thread(() -> {
while (true) {
if (Thread.currentThread().isInterrupted()) {
System.out.println("我要退出了!");
break;
}
}
}, "t1");
t1.start();
//睡眠主线程
TimeUnit.SECONDS.sleep(1);
//打断t1线程
t1.interrupt();
}
上面代码中有个死循环,interrupt()方法被调用之后,线程的中断标志将被置为true,循环体中通过检查线程的中断标志是否为ture(
this.isInterrupted())来判断线程是否需要退出了。
LockSupport类打断
Park & Unpark
基本使用
它们是LockSupport类中的方法
//暂停当前线程
LockSupport.park();
//回复某个线程的运行
LockSupport.unpark(暂停线程对象(Thread))
可以先park再unpark,也可以先unpark再park,多次调用unpark相当于调用一次;