第一章 Java多线程技能
本章主要介绍Thread类。重点掌握以下知识点。
- 线程的启动
- 如何暂停线程
- 如何停止线程
- 线程优先级
- 线程安全问题
1.1 进程与多线程概述
进程:操作系统分配资源的基本单位。
线程:进程中可独立运行的子任务。
进程负责向操作系统申请资源。在一个进程中,多个线程可以共享进程中的资源。
一个进程中至少有一个线程。
多线程使用场景:
- 系统出现阻塞
- 多个任务的执行没有任何依赖关系时,执行这几个任务可以使用多线程。
1.2 使用多线程
1、Java主线程(main线程)
public class Test1 {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName());
}
}
main方法在执行的时候,JVM会创建一个main线程。
2、多线程的优点
多线程能够更加充分的利用CPU和系统资源,能够加快程序的响应。虽然多线程能够充分利用系统资源,但是我们不能够无限制的使用多线程,因为多个线程在CPU中的切换也需要消耗系统资源和时间,所以当线程过多的时候,程序的效率也可能降低。
1.3 多线程实现的其中两种方式
1、两种方式的介绍
方式一:继承Thread
类,并重写run
方法。
public class MyThreadA extends Thread{
private int sum;
@Override
public void run() {
for (int i = 0; i < 100; i++) {
sum+=i;
}
}
}
方式二:实现Runnable
接口,并实现run
方法。
class MyThreadB implements Runnable{
private int sum;
@Override
public void run() {
for (int i = 0; i < 100; i++) {
sum+=i;
}
}
}
2、Thread
类和Runnable
接口的联系。
Thread
类实现了Runnable
接口
public
class Thread implements Runnable {
........
}
Thread
类的构造器可以接收Runnable
接口
- 这两种方式创建多线程本质并没有区别。
3、使用Runnable接口
Runnable接口的优点:我们既然可以使用Thread类来实现多线程,那为什么还要使用Runnable接口来实现多线程呢?究其原因是因为java只能够单继承,不支持多继承。所以为了突破这些限制,我们使用Runnable接口实现多线程。
问题:我们知道Thread类的run方法是线程任务的入口,那为什么我们重写了Runnable接口的run方法,线程依然可以正确执行任务呢?
Thread.java类的run方法
private Runnable target;
@Override
public void run() {
if (target != null) {
target.run();
}
}
**答:**通过以上run方法的源码可知,当我们在构造器中传入一个Runnable对象时,该Runnable对象就会赋值给target,由于target不等于null,所以就会执行我们传入Runnable对象的run方法,所以线程就可以正确执行我们设置的任务了。当我们通过继承实现多线程的时候,我们会重写run方法,线程会直接执行我们重写的run方法。
4、Thread
类的核心方法
1、线程的启动start
方法
public class MyThreadA extends Thread{ //通过继承Thread类定义了一个线程对象
private int sum;
@Override
public void run() { //重写run方法,run方法就是该线程要执行的任务
for (int i = 0; i < 100; i++) {
sum+=i;
}
}
}
class Run{
public static void main(String[] args) {
MyThreadA myThreadA = new MyThreadA(); //创建了一个线程对象
myThreadA.start(); //使用start方法将当前线程提交给操作系统,让操作系统对该线程进行调度。
}
}
使用start方法来启动一个线程,线程启动之后会自动调用线程对象中的run
方法。run
中的代码就是当前线程要执行的任务。是执行任务的入口。
start
方法的作用:start
方法将会把当线程对象进行处理,使线程进入就绪状态,然后将线程提交给从操作系统。操作仙童将会随机调度该线程,并执行该线程的run
方法。star
方法源码如下:
public synchronized void start() {
if (threadStatus != 0)
throw new IllegalThreadStateException();
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
}
}
}
private native void start0();
通过上述可知,start
方法并不能立即使线程开始执行,start
方法仅仅是将线程提交给操作系统,而线程具体什么时候开始执行,就看操作系统何时调度该线程了。
start
执行的大概步骤:
- 通过JVM告知操作系统创建一个线程
- 操作系统开辟内存并使用本地方法创建一个线程
- 操作系统对线程进行调度,以确定执行时机
- 线程在操作系统中被成功执行
注意:
- 不能多次调用
start
方法,否则会出现异常:java.lang.IllegalThreadStateException
。原因如下: - 执行
start
方法的顺序不等于执行run
的顺序。
start方法仅仅是将线程提交给操作系统,但是操作系统何时调度该线程并执行run方法这是未知的,run方法的执行顺序完全取决于操作系统如何调度线程。
2、currentThread方法
作用:返回代码段正在被那个线程执行。
该方法是一个静态本地方法。public static native Thread currentThread();
3、isAlive()方法
作用:判断当前线程是否存活
4、sleep(long millis)
作用:使线程休眠指定毫秒数,休眠过程中不会释放所获取的资源。
5、sleep(long mills,int nanosf)
作用:使线程休眠指定毫秒数+指定纳秒数,休眠过程中不会释放所获取的资源。
6、StackTraceElement[] getStackTrace()
作用:获取该线程堆栈跟踪元素数组。
7、static void dumpStack()
作用:将当前线程的堆栈跟踪信息输出至标标准错误流。
8、static Map<Thread,StackTraceElement[]> getAllStackTrace()
作用:获取所有活动线程的堆栈跟踪信息数组,并映射一个数组。
9、getId()
作用:获取线程的唯一标识
10、
5、停止线程
必看简介
Java提供了多种停止线程的方式,其中某些方法已被淘汰我们将不会再使用。
Java中三种方法可以使线程终止运行
- 使用退出标志(return)使线程正常退出。
- 使用stop()方法强行终止线程退出,不推荐使用。
- 使用interrupt()方法中断线程。
方式一:interrupt()方法中断线程
使用interrupt()方法不能真正的停止正在运行的线程,只是给当前线程做了一个停止的标记。
代码验证interrupt()方法能够否真正中断线程
public class InterruptTest {
/**
* 该段代码仅仅是一个for循环,当循环变量i==3的时候,调用interrupt()方法
* 来中断main线程。通过看循环是否会输出i=3后面的值来判断main线程是否被真正的中断
*/
public static void main(String[] args) {
for (int i=0;i<200;i++){
System.out.println(i);
if(i==3){
//预期使用interrupt()方法中断main线程
Thread.currentThread().interrupt();
System.out.println("线程已被中断");
}
}
}
}
通过代码我们可以看出 interrupt()方法不能使当前线程中断。interrupt()方法仅仅是给当前线程设置了一个停止的标志。
如何真正的使线程停止呢?
通过上述论述我们可知interrupt()方法不能使线程真正停止。那如何使线程真正停止呢?我们可以通过异常法来使线程停止运行。
通过异常法使线程停止
我们知道当线程发生异常时,线程就会停止运行,并释放其获得的资源和锁。有了这个想法我们就可以通过主动抛出一个异常的方法来停止正在运行的线程。
我们何时抛出一个异常来使线程停止运行呢?
我们可以通过主动抛出一个异常的方式使线程停止运行,那我们何时抛出一个异常呢?这里就可以使用我们前面介绍的interrupt()方法了。我们知道interrupt()方法可以给线程设置一个停止的标志,那我们就可以通过判断线程是否有停止标志来得到抛出异常的时机了。当我们检测到线程有了停止标志的时候我们就可以抛出异常使得线程停止。接下来我们介绍如何检测线程是否有停止的标志。
判断线程是否为停止状态(即线程是否有停止标志)
Thread.java 提供了两个判断方法来判断线程是否为停止状态。
方法一:public static boolean interrupted() 源码如下
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}
作用:判断当前线程是否为停止状态。
**方法二:**public boolean isInterrupted()
public boolean isInterrupted() {
return isInterrupted(false);
}
作用:判断当前this
所对应的线程是否为停止状态。(该方法为非静态的,所以判断的this
所对应的线程)
代码演示:
public class InterruptTest {
/**
* 在i=3的时候调用interrupt()方法,循环每次检测main线程是否停止状态(是否有停止标志)
* 观察i=3前后的Thread.currentThread().isInterrupted()的输出可以得出结论。
*/
public static void main(String[] args) {
for (int i = 0; i < 200; i++) {
System.out.println(i);
if (i == 3) {
//预期使用interrupt()方法中断main线程
Thread.currentThread().interrupt();
}
//判断main线程是否为停止状态
System.out.println("Thread.currentThread().isInterrupted()=" + Thread.currentThread().isInterrupted());
}
}
}
接下来我们说啥如何使用异常方法来真正停止线程。
使用异常法真正停止线程
稍等,我们先理清一下本小节说了的什么,这对异常方法停止线程有很大帮助。
本小节基本内容:
- interrupt()方法不能真正停止线程,只能给当前线程设置一个停止的标志。
- 当线程发生异常的时候,线程将会停止运行,并释放获取的所有资源(锁)。
- public static boolean interrupted() 可以检测当前线程是否有停止标志。
- public boolean isInterrupted() 可以判断this多对应的线程是否有停止标志。
有了以上知识我们就可以使用异常法真正停止线程了。直接上代码,后面有解释。
public class InterruptTest {
public static void main(String[] args) throws Exception {
for (int i = 0; i < 200; i++) {
System.out.println(i);
if (i == 3) {
//预期使用interrupt()方法中断main线程
Thread.currentThread().interrupt();
System.out.println("已经调用interrupt()方法给main线程设置停止标志");
}
//循环每次检测main线程是否被设置了停止标志
if(Thread.currentThread().isInterrupted()){
//当检测到main线程设置了停止标志,则抛出一个异常,使main线程停止
throw new Exception("isInterrupted()方法检测到main线程已经被设置停止标志了,抛出异常使线程停止");
}
}
}
}
总结:
- interrupt()方法给线程设置停止标志
- isInterrupted()方法 或 interrupted()方法判断线程是否被设置了停止运行的标志。
- 抛出异常的时机就是isInterrupted()方法 或 interrupted()方法检测到线程被设置停止运行标志的时候。
- 通过抛出异常来停止线程。
**方法二:**使用stop()方法暴力停止线程(强烈不推荐)
代码示例:
public class InterruptTest {
public static void main(String[] args) throws Exception {
for (int i = 0; i < 200; i++) {
System.out.println(i);
if (i == 3) {
//使用stop()方法暴力停止线程
Thread.currentThread().stop();
}
}
}
}
stop()方法被废除的原因:stop()方法是删除线程的状态,容易造成业务处理的不确定性。
方式三:使用return标志退出线程
使用return也可以使线程停止。在方式一中我们使用抛出一个异常来停止线程,我们完全可以使用一个renturn;
来使线程停止运行。但我们不推荐使用该方法。因为return仅仅是简单的停止线程,而抛出异常既可以停止线程也可以将我们想要的信息通过异常给打印(日志)出来。
使用return语句结束异常
public class InterruptTest {
public static void main(String[] args) throws Exception {
for (int i = 0; i < 200; i++) {
System.out.println(i);
if (i == 3) {
//预期使用interrupt()方法中断main线程
Thread.currentThread().interrupt();
System.out.println("已经调用interrupt()方法给main线程设置停止标志");
}
//循环每次检测main线程是否被设置了停止标志
if(Thread.currentThread().isInterrupted()){
//当检测到main线程设置了停止标志,则抛出一个异常,使main线程停止
//throw new Exception("isInterrupted()方法检测到main线程已经被设置停止标志了,抛出异常使线程停止");
//使用return替代抛出异常信息
return;
}
}
}
}
未完待续。。。。。。。。