- Java线程
- 线程的创建(Thread,Runnable,FutureTask)
- 线程的常用方法
- interrupt() / isInterrupted() / interrupted() 三者区别
- setDaemon() 守护线程
- 线程的生命周期和状态
- 操作系统层面:五种状态
- Java API层面:六种状态
- 锁对象的方法:wait()和notify()
- 执行原理
- WAITING与BLOCKED状态的区别
- wait()与sleep()的区别
- notify()与notifyAll()的区别
- 虚假唤醒
- LockSupport类的方法:park()和unpark(thread)
- 执行原理
2.1 线程创建的三种方式
- 1. 继承Thead类( java.lang.Thead ),重写run()方法
- 缺点:java不支持多继承,无法继承其他类;代码与任务没有分离
- 优点:在run()方法内获取当前线程直接用this,无需Thread.currentThread();
Thread t1 = new Thread(){
public void run(){
//线程方法
}
};
t1.start();
- 2. 实现Runnable接口( java.lang.Runnable ),重写run()方法,把runnable作为参数传递给thread()
- 原理:本质都是调用run()方法,如果传入了runnable对象,则会赋值给target,优先使用runnable中的run()方法
- 缺点:没有返回值
Runnable run = new Runnable(){
public void run(){
//代码
}
};
Thread t = new Thread(run);
t.start();
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
private void init(ThreadGroup g, Runnable target, String name, long stackSize) {
init(g, target, name, stackSize, null, true);
}
@Override
public void run() {
if (target != null) {
target.run();
}
}
- 3. FutureTask(java.util.concurrent.FutureTask):本质上FutureTask类实现了Runnable接口和Future接口。它与Runnable的区别在于它可以得到线程的返回值,适用于线程间通信
- 小结:更推荐Runnable,组合(接口)优于继承。可以使代码更加灵活。如果需要有返回值,需要FutureTask
- 原理:有两个构造方法,分别穿入Callable和Runnable对象,其中传Runnable的最后通过适配器的方式装换成了Callable,本质都是调用Callable方法
FutureTask<Integer> task = new FutureTask(new Callable<>()){
public Integer call(){
//代码
return 100;
}
};
Thread t = new Thread(task);
t.start();
Integer result = t.get();
2.2 Thread的常用方法
名称 | 介绍 |
thread.start() thread.run() |
|
thread.sleep(n) |
|
thread.yield() |
|
thread.getState() |
|
thread.setPriority() |
|
thread.join() thread.join(long n) |
|
thread.interrupt() thread.isInterrupted() thread.interrupted() |
|
thread.setDaemon(true) |
|
stop()/ suspend()/ resume() |
|
Object.wait() Object.wait(n) Object.notify() Object.notifyAll() |
|
- thread.interrupt() / thread.isInterrupted() / thread.interrupted() 三者区别
- 对于sleep, wait, join中的线程,如t1:
- 调用t1.interrupt()会让其重新回到RUNNABLE状态
- 同时t1会有InterruptedException异常,需要捕获
- 打断后,调用t1.isInterrupted()方法会返回false(标记清除)
- 对于正在运行态RUNNABLE的线程,如t1:
- 调用t1.interrupt()方法,并不会对t1有影响,t1仍然会继续执行
- 不会有异常
- 但是,t1的打断标记会变true,即t1.isinterrupted() = true
- 因此,对于正在运行的线程,需要配合打断标记一起使用
-
Thread t1 = new Thread(()->{ while(true){ boolean interrupted = Thread.currentThread().isInterrupted(); if(interrupted == true){ break; } } });
- 对于处于park的线程
- t1的打断标记会变true,即t1.isinterrupted() = true
- 不会报异常
- 调用t1.interrupt()会让其重新进入RUNNABLE状态
- 对于sleep, wait, join中的线程,如t1:
- setDaemon() 守护线程
- 常见的守护线程:gc线程
- 注:当最后一个非守护线程(用户线程)结束时,JVM会正常退出;守护线程是否正常结束不影响JVM的退出下
- main线程运行结束后,JVM 会自动启动个叫作 DestroyJavaVM 的线程,该线程会等待所有用户线程结束后终止JVM
- 总结:如果希望主线程结束后JVM进程立马结束,可以设置其他线程为守护线程
2.3 线程的生命周期和状态
- 从操作系统层面,有5种
- 从java API层面:根据Thread.State,分为6种
-
NEW 初始状态,线程被创建,但还没调用start()方法 RUNNABLE 包括 可运行状态(RUNNABLE)和 正在运行状态(RUNNING) BLOCKED 阻塞状态,被锁阻塞 WAITING 等待状态 TIME_WAITING 超时等待,超过等待时间后自行返回 TERMINATED 终止状态,表示该线程已执行完毕 -
1 - thread1.start()
2 - thread1线程获得了锁对象之后
- 调用object.wait() :thread1从RUNNABLE变成WAITING
- 调用object.notify(),object.notifyAll(),thread2.interrupt()
- 锁竞争成功:thread1线程从 WAITING变成RUNNABLE
- 锁竞争失败:thread1线程从 WAITING变成BLOCKED
3 - thread2.join():thread1从RUNNABLE变成WAITING,直到thread2结束,thread1再变回RUNNABLE
4 - LockSupport.park():让当前线程从RUNNABLE变成WAITING
- LockSupport.unpart(目标线程):让目标线程从WAITING变回RUNNABLE
5 - thread1线程获得了锁对象之后:调用object.wait(时间)
6 - thread2.join(时间)
7 - thread1.sleep(时间)
8 - LockSupport.parkNanos(时间) 或者 LockSupport.parkUntil(时间)
9 - thread1线程获取锁对象失败会进入BLOCKED。
- 如果锁被释放,会通知该对象上所有BLOCKED的线程重新竞争
10 - 代码执行完毕
-
2.4 锁对象的方法:wait()和notify()
- 执行原理
- wait是属于Object的方法,如果不带参数执行wait(),默认调用wait(0),最终给native本地方法执行
-
public final native void wait(long var1) throws InterruptedException;
- 当某一对象获得锁并执行时,如果想退出,执行wait方法,该线程会进入锁对象中的WaitSet等待再次被唤醒;状态由RUNABLE变为WAITING状态,详细见第三章:https://blog.csdn.net/qq_41157876/article/details/114930398
- WAITING与BLOCKED状态的区别
- BLOCKED线程会在Owner释放锁之后唤醒参与竞争,而WAITING需要被唤醒
- WAITING线程会在Owner线程调用notify或者notifyAll时唤醒,进入EntryList重新竞争
- wait()与sleep()的区别
- sleep是thread的静态方法,wait是Object的方法
- sleep不需和synchronized配合使用,wait需要和synchronized一起使用
- sleep睡眠时不会释放锁,wait会释放锁
- notify()与notifyAll()的区别
- notify()随机唤醒WaitSet中任意一个线程进入EntryList
- notifyAll()唤醒全部WaitSet的线程
- 虚假唤醒
- 概念:处于waiting状态的线程,没有经过notify(),notifyAll()等方法通知,或者没有被打断,仍然意外得被唤醒。称为虚假唤醒
- 常用解决方法
-
synchronized(obj){ //如果条件不满足,可能是被虚假唤醒,让出锁对象继续WAITING while(条件不满足){ obj.wait(); } }