前言:
前面了解了线程与进程的相关概念,尤其是线程中系统是如何"调度"的,还有创建PCB的过程,以及PCB中的一些性质,这些都必须掌握清楚!!
接下来我们就来实际实现以下"多线程"编程,并且掌握Java中提供的多线程编程的类(Thread)及方法!!
Thread类:
Thread类在Java中可以理解为对操作系统提供的API的封装形式!
Thread 类是 JVM ⽤来管理线程的⼀个类,换句话说,每个线程都有⼀个唯⼀的 Thread 对象与之关联。
如何实现多线程呢?
有如下的几个简单步骤:
实现一个多线程:
1、实例化一个Thread对象:
Thread t = new Thread();
2、描述线程任务:
方法一:
继承Thread类并重写run()方法:
class MyThread extends Thread {
@Override
public void run() {
//描述任务
while(true) {
System.out.println("执行线程1");
//每执行一次睡眠1s
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
方式二:
实现Runnable接口并重写run()方法:
class MyRunnable implements Runnable {
@Override
public void run() {
while(true) {
System.out.println("执行线程一任务");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
注意最后需要将MyRunnable作为参数传入Thread()中。
方式三:
针对方式一直接使用匿名内部类:
Thread t = new Thread() {
@Override
public void run () {
while(true) {
System.out.println("执行线程一任务");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
};
方式四:
针对方式二直接在传参时传入Runnable匿名内部类的子类对象:
Thread t = new Thread(new Runnable() {
@Override
public void run() {
while(true) {
System.out.println("执行线程一任务");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
});
方式五:
使用lambda表示,创建Runnable的子类对象:
Thread t = new Thread(()->{
while(true) {
System.out.println("执行线程一任务");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
3、创建线程:
当我们描述好线程的任务之后,接下来就要进行对线程的创建了。
我们调用Thread类中的start()方法进行线程的创建:
t.start();
注意:
我们的main线程不需要进行线程创建,这是由JVM会自动为我们创建main线程。
构造方法:
Thread的构造方法有很多:
第一种和第二种已经介绍过了。
至于第三种第四种,就是给该创建的线程起名字,为什么其名字呢?
如果我们不去手动给线程起名字,编译器会为我们自动命名(Thread-0,Thread-1...),起名字的目的就是为了我们后续方便观察每一个线程的运行情况。
在JDK的工具包中,有一个工具可以做到监视线程:
当我们启动程序,就可通过该程序对Java中线程的运行监视:
我们进重新命名为"lala" 我们可以在jconsole中监视:
中断线程的方式:
如何让一个线程终止呢??
首先我们需要明确的一点是线程什么时候停止是线程自己本身说了算,其他线程只能提醒一下线程一。
这里有两中方式让线程终止:
方式一:
在线程中添加"中断"条件,通过共享的标记进行沟通,例如:
private static boolean sign = true;//定义标记成员变量
public static void main(String[] args) {
Thread t = new Thread(()->{
while(sign) {
System.out.println("线程一执行");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
t.start();
for (int i = 0; i < 3; i++) {
System.out.println("执行main线程");
}
sign = false;//修改标记
}
这样在main线程结束后,就可以做到让线程1中断。
但是这样做有一个缺陷,就是当标志位修改后,但是线程一正在睡眠,此时线程一是不能及时中断,只有谁卖你结束,继续在CPU上执行时,运行到标志位的地方才能停止。
方式二:
通过interrupt()方法来通知:
首先调用isInterrupted()方法设置标志位,该方法返回值类型为boolean,当true时终止线程,
当返回值为false时线程要继续执行。
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(()->{
//设置标识位
//while(!Thread.currentThread().isInterrupted())
//true表示线程结束
//false表示线程执行
while(!Thread.currentThread().isInterrupted()) {
System.out.println("线程一执行");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
//throw new RuntimeException(e);
e.printStackTrace();
}
}
});
t.start();
//设置布尔值为true,并唤醒sleep
Thread.sleep(1000);
t.interrupt();
}
这里有两种情况:
1、当main线程中执行到interrupt处,线程一正好没有sleep,或者已将运行完sleep了,此时 当线程一在while处进行判断时,判断结果为false时,程序直接结束,或者直接运行while以外的代码。
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(()->{
//设置标识位
//while(!Thread.currentThread().isInterrupted())
//true表示线程结束
//false表示线程执行
while(!Thread.currentThread().isInterrupted()) {
System.out.println("线程一执行");
/* try {
Thread.sleep(100);
} catch (InterruptedException e) {
//throw new RuntimeException(e);
e.printStackTrace();
}*/
}
System.out.println("线程一end");
});
t.start();
//设置布尔值为true,并唤醒sleep
Thread.sleep(1000);
t.interrupt();
}
2、当main线程运行到interrupt时,此时线程一正在sleep时,此时线程一会被提醒线程要结束,并且抛出异常,让catch捕获到,之后是否要结束就看catch中的代码了,并且此时while中的标志位被复原。
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(()->{
//设置标识位
//while(!Thread.currentThread().isInterrupted())
//true表示线程结束
//false表示线程执行
while(!Thread.currentThread().isInterrupted()) {
System.out.println("线程一执行");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
//throw new RuntimeException(e);
e.printStackTrace();
break;
}
}
System.out.println("线程一end");
});
t.start();
//设置布尔值为true,并唤醒sleep
Thread.sleep(10);
t.interrupt();
}
等待一个线程join():
根据线程所学的内容,每一个线程在CUP的内核上运行的时候,都是"抢占式"运行,他们的运行顺序是不确定的,因此在多线程的话题下,如何让他们的运行顺序按照我们预期的执行是一个大话题,这个话题在后续会展开,接下来我们首先来谈论一下我们如何决定每个线程的结束顺序呢?
也就是假设有3个线程A,B,C,此时我要让A最后结束,或者我要按顺序C,B,A结束我应该怎么做?
这里就涉及到线程之间的等待问题。
假设A调用了B的join,意思就是当B结束我才能结束。
示例如下,main线程调用t线程的join():
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(()->{
for(int i = 0;i<10;i++) {
System.out.println("线程t开始执行");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("t线程结束");
});
t.start();
t.join();
System.out.println("main has end");
}
此时只能等t线程结束后main线程才能结束。
那么此时有main,t1,t2三个线程,此时我只想让main线程最后结束,不用去管t1和她
谁先谁后,可以怎么做,就是在main线程中调用t1.join()和t2.join():
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(()->{
for(int i = 0;i<10;i++) {
System.out.println("线程t1开始执行");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("t1线程结束");
});
Thread t2 = new Thread(()->{
for(int i = 0;i<10;i++) {
System.out.println("线程t2开始执行");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("t2线程结束");
});
t1.start();
t2.start();
t2.join();
t1.join();
System.out.println("main has end");
}
3.如何做到按t2,t1,main的顺序结束,可以t1里面调用t2.join(),main里面继续调用t1.join()
public static void main(String[] args) throws InterruptedException {
Thread t2 = new Thread(()->{
for(int i = 0;i<10;i++) {
System.out.println("线程t2开始执行");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("t2线程结束");
});
Thread t1 = new Thread(()->{
for(int i = 0;i<10;i++) {
System.out.println("线程t1开始执行");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
try {
t2.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("t1线程结束");
});
t1.start();
t2.start();
t1.join();
System.out.println("main has end");
}
Thread常见的属性:
ID:
每个线程都有属于在自己的ID,当然值得注意的是这个ID与PCB中的ID不是同一个,一般main线程的ID为1.
名称:
每个线程都有自己的名称,前面在构造函数中我们可以手动命名线程的名字,如果没有手动命名,编译器会自动命名,一般名字为Thread-0、Thread-1......
状态:
这里的状态有以下几种:
NEW: 安排了⼯作, 还未开始⾏动(一般在start之前)RUNNABLE: 可⼯作的(随叫随到). ⼜可以分成正在⼯作中和即将开始⼯作.(情况一:正在CPU上进行工作 情况二:虽然没在CPU上执行,但是可以调度到CUP上运行)BLOCKED: 这⼏个都表⽰排队等着其他事情(阻塞,带有锁)WAITING: 这⼏个都表⽰排队等着其他事情(阻塞,死等)TIMED_WAITING: 这⼏个都表⽰排队等着其他事情(阻塞,超时时间)TERMINATED: ⼯作完成了(线程终止了,该线程在内核中已被销毁,但是Thread对象还在)
New状态:
System.out.println(t.getState());//start之前调用
t.start();
Runnable状态:
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(()->{
for (int i = 0; i < 10; i++) {
System.out.println("线程1");
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
t.start();
System.out.println(t.getState());
while(true) {
System.out.println("main线程" );
Thread.sleep(2000);
}
}
Terminated状态:
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(()->{
for(int i = 0;i < 5;i++) {
System.out.println("线程1正在执行");
try {
Thread.sleep(1000);
}catch(InterruptedException e) {
throw new RuntimeException();
}
}
});
t.start();
t.join();
System.out.println("main has end");
System.out.println(t.getState());
}
也就是整个进程还没有结束,但是线程1先结束了(线程一工作完成)。
Wating状态:
这个可以在jconsole中进行观察,并且需要用到join()等待线程函数,该死等现象打印不出来,可以通过jconsole观察:
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(()->{
while(true) {
System.out.println("线程一执行");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
t.start();
t.join();
System.out.println("main has end");;
}
此时main线程就是一个死等现象,状态就是WAITING:
TIMED_WAITING状态:
该状态也是一个阻塞状态,超时时间等待,一个线程正在sleep,等待唤醒的状态:
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(()->{
for(int i = 0;i < 5;i++) {
System.out.println("线程1正在执行");
try {
Thread.sleep(10000);
}catch(InterruptedException e) {
throw new RuntimeException();
}
}
});
t.start();
System.out.println("main has end");
Thread.sleep(100);
System.out.println(t.getState());
}
BLOCKED状态:
该状态先不做过多的讨论,因为他的这个状态涉及到线程安全等问题,该状态的示范会放在下一章中!!
优先级:
该优先级不等于CUP调度的优先级,调度CPU的优先级是系统决定的,我们干预不了。
Thread
类的getPriority()
方法用于检查线程的优先级。 当创建一个线程时,它会为它分配一些优先级。线程的优先级可以由JVM或程序员在创建线程时明确指定。
线程的优先级在1
到10
的范围内。线程的默认优先级为5
是否后台线程(isDeamon):
该方法可以设置某个线程为后台线程,那么什么是后台线程?
线程分为前台线程和后台线程:
后台线程结束不会直接影响进程结束,但是前台线程的结束会直接影响进程的结束!!
尤其当我们开始运行代码,其实一直有一个后台线程在为我们服务——GC(垃圾回收管理),该线程能够为我们自动回收不再需要的内存资源等,可以很好的帮助我们节省资源!!
当我们手动创建一个新的线程,该新的线程都是前台线程!!
当然我们可以将线程t设置为后台线程(setDeamon()方法),也就是说线程t不管结不结束,只要main线程结束,直接结束进程:
注:在判断和设置后台线程应放在start之前!!
public static void main(String[] args) {
Thread t = new Thread(()->{
while(true) {
System.out.println("线程1正在执行");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
//没有设置之前
System.out.println(t.isDaemon());
t.setDaemon(true);
//设置之后
System.out.println(t.isDaemon());
t.start();
System.out.println(t.isDaemon());
System.out.println("main has end");
}
是否存活 isAlive():
该方法可以判断一个线程是否在运行:
public static void main(String[] args) {
Thread t = new Thread(()->{
while(true) {
System.out.println("线程一执行");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
//创建线程之前
System.out.println(t.isAlive());
System.out.println("main has end");
t.start();
//创建线程之后
System.out.println(t.isAlive());
}