线程的生命周期
线程共包括一下5种状态
- 新建状态(New):线程对象被创建以后,就进入了新建创建,例如:Thread thread = new Thread()。
- 就绪状态(Runnable):也被称为:可执行的状态,线程对象被创建以后,其他线程调用了该对象的start()方法,从而来启动该线程,例如:thread.start(),处于就绪状态的线程可以被CPU调用
- 运行状态(Running):线程获取cpu权限进行执行,需要注意的是:线程只能从就绪状态进入运行状态
- 阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃了cpu的使用权,暂时停止运行,直到线程进入就绪状态,才有机会转到运行状态,阻塞分为三种:
- 等待阻塞 :通过调用线程的wait()方法,让线程等待某工作的完成
- 同步阻塞 :线程在获取synchornized同步锁失败(因为锁被其他线程所占用),他会进入同步阻塞状态
- 其他阻塞 :通过调用线程的sleep()或者join()或者发出了I/O请求时,线程会进入到阻塞的状态,当sleep()状态超时,join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
- 死亡状态(Dead): 线程执行完课或者因为异常退出了run()方法,该线程结束生命周期
线程的实现方式
常用的实现方式
- 继承抽象类Thread 重写run()方法
- 实现接口Runnable
线程池的实现
- 还可以通过java.util.concurrent包中的线程池来实现多线程。
Thread中start()和run()的区别
- start():它的作用就是启动一个新线程,新线程会执行相应的run()方法,start()不能被重复调用
- run():和普通的成员方法一样,可以被重复的调用,单独调用run()的话,会在当前线程中执行run(),而且并不会启动新线程
源码分析 基于jdk8
//工具的Java线程状态, 初始化表示线程'尚未启动'
private volatile int threadStatus = 0;
//这个线程的组
private ThreadGroup group;
public synchronized void start() {
//如果线程不是"就绪状态",则抛出异常!
if (threadStatus != 0)
throw new IllegalThreadStateException();
//将线程添加到ThreadGroup中
//通知组该线程即将启动,这样它就可以添加到组的线程列表中,并且该组的未启动计数可以递减
group.add(this);
boolean started = false;
try {
//通过该方法启动线程
start0();
//设置标记
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
复制代码
start()实际上是通过本地方法start0()启动线程的。而start0()会新运行一个线程,新线程会调用run()方法。
run()方法分析
/* What will be run. */
private Runnable target;
public void run() {
if (target != null) {
target.run();
}
}
复制代码
target是一个Runnable对象。run()就是直接调用Thread线程的Runnable成员的run()方法,并不会新建一个线程。
synchronized
原理
- 在java中,每一个对象有且仅有一个同步锁,这就意味着,同步锁是依赖于对象存在的
- 当我们调用某对象的synchronized方法时,就获取了该对象的同步锁
- 不同线程对同步锁的访问是互斥的
synchronized方法 和 synchronized代码块
“synchronized方法”是用synchronized修饰方法,而 “synchronized代码块”则是用synchronized修饰代码块。
synchronized 实例锁 和 全局锁
- 实例锁 -- 锁在某一个实例对象上。如果该类是单例,那么该锁也具有全局锁的概念。 实例锁对应的就是synchronized关键字。
- 该锁针对的是类,无论实例多少个对象,那么线程都共享该锁。全局锁对应的就是staticsynchronized(或者是锁在该类的class或者classloader对象上)。
线程的等待和唤醒
wait(), notify(), notifyAll()等方法介绍
- 三个方法都定义在Object对象中
- wait():让当前线程进入等待状态,即阻塞状态,同时会释放当前线程持有的锁,可以使用 notify() 方法或 notifyAll() 方法唤醒线程,wait()的作用是让“当前线程”等待,而“当前线程”是指正在cpu上运行的线程!
- notify():唤醒单个线程,唤醒当前对象上的等待线程
- notifyAll():唤醒当前对象上的所有等待线程
为什么notify(), wait()等函数定义在Object中,而不是Thread中
- Object中的wait(), notify()等函数,和synchronized一样,会对“对象的同步锁”进行操作。
- wait()会使“当前线程”等待,因为线程进入等待状态,所以线程应该释放它锁持有的“同步锁”,否则其它线程获取不到该“同步锁”而无法运行!
- 线程调用wait()之后,会释放它锁持有的“同步锁”;而且,根据前面的介绍,我们知道:等待线程可以被notify()或notifyAll()唤醒。现在,请思考一个问题:notify()是依据什么唤醒等待线程的?或者说,wait()等待线程和notify()之间是通过什么关联起来的?答案是:依据“对象的同步锁”。
- 负责唤醒等待线程的那个线程(我们称为“唤醒线程”),它只有在获取“该对象的同步锁”(这里的同步锁必须和等待线程的同步锁是同一个),并且调用notify()或notifyAll()方法之后,才能唤醒等待线程。虽然,等待线程被唤醒;但是,它不能立刻执行,因为唤醒线程还持有“该对象的同步锁”。必须等到唤醒线程释放了“对象的同步锁”之后,等待线程才能获取到“对象的同步锁”进而继续运行。
- notify(), wait()依赖于“同步锁”,而“同步锁”是对象锁持有,并且每个对象有且仅有一个!这就是为什么notify(), wait()等函数定义在Object类,而不是Thread类中的原因。
线程的让步
yield()介绍
- yield()的作用是让步,让当前线程由运行状态进入到就绪状态,从而让其他具有相同优先级的等待线程获取执行权
- 但是,并不能保证当前线程调用yield()之后,其他具有相同优先级的线程就一定能获取执行权;也有可能是当前线程又进入到运行状态继续运行
yield() 与 wait()的比较
- wait()是让线程由“运行状态”进入到“等待(阻塞)状态”,yield()是让线程由“运行状态”进入到“就绪状态”。
- wait()是会线程释放它所持有对象的同步锁,而yield()方法不会释放锁。
线程的休眠
sleep()介绍
- sleep() 定义在Thread.java中。
- sleep() 的作用是让当前线程休眠,即当前线程会从“运行状态”进入到“休眠(阻塞)状态”。
- sleep()会指定休眠时间,线程休眠的时间会大于/等于该休眠时间;在线程重新被唤醒时,它会由“阻塞状态”变成“就绪状态”,从而等待cpu的调度执行。
sleep() 与 wait()的比较
- wait()的作用是让当前线程由“运行状态”进入“等待(阻塞)状态”的同时,也会释放同步锁。
- sleep()的作用是也是让当前线程由“运行状态”进入到“休眠(阻塞)状态”。 但是,wait()会释放对象的同步锁,而sleep()则不会释放锁。
线程的礼让
join()介绍
- join() 定义在Thread.java中。
- join() 的作用:让“主线程”等待“子线程”结束之后才能继续运行
线程的终止方式和interrupt()
interrupt()说明
- 作用:中断本线程
- 本线程中断自己是被允许的,其他线程调用本线程的interrupt()方法时,会通过checkAccess()检查权限。这有可能抛出SecurityException异常。
终止线程的方式
- Thread中的stop()和suspend()方法,由于固有的不安全性,已经建议不再使用!
终止处于“阻塞状态”的线程
- 通常,我们通过“中断”方式终止处于“阻塞状态”的线程。
- 当线程由于被调用了sleep(), wait(), join()等方法而进入阻塞状态;若此时调用线程的interrupt()将线程的中断标记设为true
- 处于阻塞状态,中断标记会被清除,同时产生一个InterruptedException异常。将InterruptedException放在适当的为止就能终止线程,
@Override
public void run() {
while (true) {
try {
// 执行任务...
} catch (InterruptedException ie) {
// InterruptedException在while(true)循环体内。
// 当线程产生了InterruptedException异常时,while(true)仍能继续运行!需要手动退出
break;
}
}
}
复制代码
终止处于“运行状态”的线程
- 通常,我们通过“标记”方式终止处于“运行状态”的线程。其中,包括“中断标记”和“额外添加标记”。
终止线程的示例
- interrupt()常常被用来终止“阻塞状态”线程。参考下面示例:
// Demo1.java的源码
class MyThread extends Thread {
public MyThread(String name) {
super(name);
}
@Override
public void run() {
try {
int i=0;
while (!isInterrupted()) {
Thread.sleep(100); // 休眠100ms
i++;
System.out.println(Thread.currentThread().getName()+" ("+this.getState()+") loop " + i);
}
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() +" ("+this.getState()+") catch InterruptedException.");
}
}
}
public class Demo1 {
public static void main(String[] args) {
try {
Thread t1 = new MyThread("t1"); // 新建“线程t1”
System.out.println(t1.getName() +" ("+t1.getState()+") is new.");
t1.start(); // 启动“线程t1”
System.out.println(t1.getName() +" ("+t1.getState()+") is started.");
// 主线程休眠300ms,然后主线程给t1发“中断”指令。
Thread.sleep(300);
t1.interrupt();
System.out.println(t1.getName() +" ("+t1.getState()+") is interrupted.");
// 主线程休眠300ms,然后查看t1的状态。
Thread.sleep(300);
System.out.println(t1.getName() +" ("+t1.getState()+") is interrupted now.");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
复制代码
interrupted() 和 isInterrupted()的区别
- interrupted() 和 isInterrupted()都能够用于检测对象的“中断标记”。
- interrupted()除了返回中断标记之外,它还会清除中断标记(即将中断标记设为false);而isInterrupted()仅仅返回中断标记。
线程的优先级和守护线程
线程优先级的介绍
- java 中的线程优先级的范围是1~10,默认的优先级是5。
- “高优先级线程”会优先于“低优先级线程”执行。
- java 中有两种线程:用户线程和守护线程。可以通过isDaemon()方法来区别它们
- 如果返回false,则说明该线程是“用户线程”;否则就是“守护线程”。
- 用户线程一般用户执行用户级任务,而守护线程也就是“后台线程”,一般用来执行后台任务
- Java虚拟机在“用户线程”都结束后会后退出。
//设置该线程的优先级
thread.setPriority(1);
复制代码