Java多线程
Java多线程的使用
Java多线程的七种状态
状态 | 描述 |
---|---|
新建(NEW) | 线程刚被创建时的状态 |
就绪(RUNNABLE) | 线程正在竞争CPU使用权 |
运行(RUNNING) | 线程获取了CPU使用权,正在运行 |
阻塞(BLOCKED) | 线程为了等待某个对象的锁,而暂时放弃CPU使用权,且不再参与争夺。直到条件满足(超时退出、被中断或唤醒)时,从新回到就绪状态,参与CPU争夺。 |
等待(WAITING) | 线程无限等待(wait)某个对象的锁 |
计时等待(TIME_WAITING) | 线程在一段时间内等待某个对象的锁,或主动休眠(sleep),或等待另一个线程结束(join)。除非被中断,时间一到线程将自动回到就绪状态。 |
终止(DEAD) | run()方法执行完毕、执行过程中抛出异常、受到外界干预中断执行 |
- 轻量级阻塞与重量级阻塞
- 轻量级阻塞:能够被中断的阻塞
- WAITING
- TIME_WAITING
- 重量级阻塞:不可被中断的阻塞
- BLOCKED
- 轻量级阻塞:能够被中断的阻塞
Thread实例方法及静态方法
方法类型 | 方法名称 | 描述 | 状态转换 |
---|---|---|---|
实例方法 | t.isDaemon | 判断是都是守护线程 | - |
实例方法 | t.setDaemon(true) | 设置为守护线程(必须在线程启动前) | - |
实例方法 | t.setPriority(int) | 设置线程优先级(必须在线程启动前) | - |
实例方法 | t.start() | 启动线程t,开始参数CPU使用权的竞争 | 新建状态(NEW) -> 就绪状态(RUNNABLE) |
实例方法 | t.isAlive() | 线程t是否存活 | - |
实例方法 | t.interrupt() | 线程t设置中断标志 | 如果线程处于等待或计时等待状态会抛出异常,并且线程被唤醒 |
实例方法 | t.isInterrupt() | 判断线程t是否有中断标志 | - |
静态方法 | Thread.currentThread() | 获取当前线程的信息 | - |
静态方法 | Thread.yield() | 使当前线程让出CPU | 运行状态(RUNNING) -> 就绪状态(RUNNABLE) |
静态方法 | Thread.sleep(int) | 使当前线程进入休眠状态 | 运行状态(RUNNING)-> 计时休眠状态(TIME_WAITING) -> 就绪状态(RUNNABLE) |
静态方法 | Thread.currentThread().isInterrupt() | 获取当前线程中断标志,并清除标志 | - |
sleep与yield的区别
- 关于状态
- sleep后进入到计时等待状态,在一段时间后变成就绪状态
- yield直接进入就绪状态。
- 关于优先级
- sleep后其他所有优先级线程均有机会获取到CPU
- yield后只有相同或更高优先级才有机会获取到CPU
- 关于异常
- sleep需要声明InterruptedException异常
- yield无需异常
- 缺陷
- 循环中使用yield后,当前线程可能立即又抢到了线程,造成其他线程得不到执行。
创建多线程
在Java的JDK开发包中,已经自带了对多线程技术的支持,可以很方便的使用多线程编程。
实现多线程的方式主要有两种,两种方式创建的线程在工作时的性质是一样的,没有本质区别。
- 继承Thread类
- 实现Runnable接口
- 建议使用此种实现方式
- 线程池只接受Runnable对象和Callable对象
- 易于扩展
- 建议使用此种实现方式
继承Thread类
Thread类的结构如下,从源码中可以发现,Thera的类实现了Runnable接口,它们之间具有多态关系。
public class Thread implements Runnable {
...
}
实现Runnable接口
使用继承Thread类的方式实现多线程时,最大的局限就是不可再继承其他类,因为Java的特点是单根继承,所有需要继承其他类时,应使用实现Runnable接口的方式。
public class MyRunnable implements Runnable {
@Override
public void run() {
// run()执行结束,当前线程终止
System.out.println("运行中");
}
}
运行Runnable实现类的两种方法:
- Runnable实现类做为参数构造Thread对象实例。
- 将Runnable实现类提交到线程池中运行。
启动多线程 start()
- 启动线程
- 线程状态从 新建 -> 就绪
- 通知"线程规划器"此线程已经准备就绪,等待调用线程规划器调用该线程对象的run()方法。
- 直接调用run()为同步操作,与多线程无关。
- 线程启动的顺序与执行start()方法的顺序无关。
- 先提交的线程不一定先之先执行。
检查线程是否存活 isAlive()
isAlive()的功能是判断当前的进程是否存活。
- 存活:线程已经启动且尚未停止。即处于正在运行或准备开始运行的状态,表示此线程是"存活"的。
public class MyThread extends Thread {
@Override
public void run() {
System.out.println("run:" + this.isAlive());
}
public static void main(String[] args) throws InterruptedException {
MyThread myThread = new MyThread();
System.out.println("begin == " + myThread.isAlive());
myThread.start();
// Thread.sleep(1000); // 如果此处main线程休眠1s,则myThread线程必定执行完毕,myThread.isAlive()必定为false
System.out.println("end == " + myThread.isAlive()); // 线程尚未执行完毕,所以为true
}
}
// 结果
begin == false
end == true
run:true
休眠线程 sleep()
- sleep()的作用是在指定的时机毫秒数内让当前"正在执行的线程"休眠(暂停执行)。
- 这个正在执行的线程是this.currntThread()返回的线程。
public class MyThread extends Thread {
@Override
public void run() {
try {
System.out.println("run threadName=" + this.currentThread().getName() + " begin");
Thread.sleep(2000);
System.out.println("run threadName=" + this.currentThread().getName() + " end");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static void main(String[] args) throws InterruptedException {
MyThread myThread = new MyThread();
System.out.println("begin == " + System.currentTimeMillis());
myThread.start();
System.out.println("end == " + System.currentTimeMillis());
}
}
// 结果
begin == 1584418257689
end == 1584418257689
run threadName=Thread-0 begin
run threadName=Thread-0 end
由于main线程与Thread线程是异步执行的,所以先打印begin和end。而MyThread是随后运行的。
停止线程
停止线程是在多线程开发时很重要的技术点,此技术点可对线程的停止进行有效的处理。
- stop()、destory()函数
- 官方明确不建议使用
- 使用中断标志中断线程。
- interrupt():设置中断标志
设置中断标志 interrupt()
- 大多数停止一个线程使用interrupt(),尽管方法的名称是停止,但这个方法不会终止一个正在运行的线程,还需加入一个判断才可完成线程的停止。
- 调用interrupt()仅仅是在当前线程中打了一个停止的"标记",并不是真的停止线程。
从以下代码打印的信息可以看出,线程并没有真的停止。
public class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 500000; i++) {
System.out.println(i);
}
}
public static void main(String[] args) throws InterruptedException {
MyThread thread = new MyThread();
thread.start();
Thread.sleep(100);
thread.interrupt();
}
}
判断线程是否是停止状态
- 实例方法
- t.isInterrupted():测试线程对象Thread是否已经是中断状态,不会清除状态标志。
- 静态方法
- Thread.interrupted():测试当前线程(代码所在线程)是否已经中断,执行后具有将标志状态清除为false的功能。
Thread.interrupted()
从以下代码可以看出,线程的中断状态由Thread.interrupted()方法清除了中断标志。
public class MyThread extends Thread {
public static void main(String[] args) throws InterruptedException {
Thread.currentThread().interrupt();
System.out.println("是否停止1? = " + Thread.interrupted()); // true
System.out.println("是否停止2? = " + Thread.interrupted()); // false
}
}
t.isInterrupted()
测试线程对象Thread是否已经是中断状态,不会清除状态标志。
public class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 500000; i++) {
// System.out.println(i);
}
}
public static void main(String[] args) throws InterruptedException {
MyThread thread = new MyThread();
thread.start();
Thread.sleep(1000);
thread.interrupt();
System.out.println("是否停止1? = " + thread.isInterrupted()); // true
System.out.println("是否停止2? = " + thread.isInterrupted()); // true
}
}
通过中断标志与抛异常停止线程
建议使用此形式停止异常,因为可以将异常向上抛,使得线程停止的事件得以传播。
public class MyThread extends Thread {
@Override
public void run() {
try {
for (int i = 0; i < 500000; i++) {
if (this.interrupted()) {
System.out.println("已经停止了,我退出了");
throw new InterruptedException();
}
System.out.println(i);
}
System.out.println("for循环外代码");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
System.out.println("进入catch");
e.printStackTrace();
}
}
public static void main(String[] args) throws InterruptedException {
MyThread thread = new MyThread();
thread.start();
Thread.sleep(500);
thread.interrupt();
}
}
通过中断标志与return停止线程
public class MyThread extends Thread {
@Override
public void run() {
while (true) {
if (this.isInterrupted()) {
System.out.println("停止了");
return;
}
System.out.println(System.currentTimeMillis());
}
}
public static void main(String[] args) throws InterruptedException {
MyThread thread = new MyThread();
thread.start();
Thread.sleep(2000);
thread.interrupt();
}
}
处于轻量级阻塞状态(WAITING、TIME_WAITING)的进程调用中断
处于轻量级阻塞状态的进程调用中断,会抛出InterruptedException异常,并清除停止标志。主线程并不会进入catch中。
public class MyThread extends Thread {
@Override
public void run() {
try {
System.out.println("run begin");
Thread.sleep(200000);
System.out.println("run end");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
System.out.println("进入catch");
e.printStackTrace();
}
System.out.println("end");
}
public static void main(String[] args) throws InterruptedException {
MyThread thread = new MyThread();
thread.start();
Thread.sleep(2000);
thread.interrupt();
}
}
// 结果
run begin
end
进入catch
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at com.sijing.netty.MyThread.run(MyThread.java:9)
放弃运行 Thread.yield()
yield()的作用是放弃的当前的CPU资源,将它让给其他的任务去占用CPU运行时间片段。但放弃的时间不确定。也有可能刚刚放弃,马上有获得CPU时间片。
public class MyThread extends Thread {
@Override
public void run() {
long beginTime = System.currentTimeMillis();
int count = 0;
for (int i = 0; i < 5000000; i++) {
Thread.yield(); // 是否开启此方法,方法的用时不等
count = count + i;
}
long endTime = System.currentTimeMillis();
System.out.println(endTime - beginTime);
}
public static void main(String[] args) throws InterruptedException {
MyThread thread = new MyThread();
thread.start();
}
}
获取当前线程的信息 Thread.currentThread()
Thread.currentThread()返回当前代码段被哪个线程调用的信息。
public class TestBean {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName()); // main
}
}
结果说明main方法被名为mian的线程调用。
public class MyThread extends Thread {
public MyThread() {
System.out.println("调用构造方法的线程为:" + Thread.currentThread().getName());
}
@Override
public void run() {
System.out.println("调用run()方法的线程为:" + Thread.currentThread().getName());
}
public static void main(String[] args) {
MyThread myThread = new MyThread(); // 调用构造方法的线程为:main
myThread.start(); // 调用run()方法的线程为:Thread-0
myThread.run(); // 调用run()方法的线程为:main
}
}
上述结果说明
- MyThread的构造方法由main线程执行
- 通过启动子线程调用run()由名为Thread-0的线程执行,run()是自动调用的。
- 通过对象调用run(),有main进程执行。
Thread.currentThread() 与 this
Thread.currentThread() 表示执行它的线程。
this表示当前线程对象。
public class MyThread extends Thread {
public MyThread() {
System.out.println("Thread.currentThread().getName():" + Thread.currentThread().getName());
System.out.println("Thread.currentThread().isAlive():" + Thread.currentThread().isAlive());
System.out.println("this.getName():" + this.getName());
System.out.println("this.isAlive():" + this.isAlive());
}
@Override
public void run() {
System.out.println("run:" + this.isAlive());
}
public static void main(String[] args) throws InterruptedException {
MyThread myThread = new MyThread();
}
}
// 结果
Thread.currentThread().getName():main
Thread.currentThread().isAlive():true
this.getName():Thread-0
this.isAlive():false