目录
🍍3.5使用lamda表达式创建 Runnable 子类对象
哥几个来学线程啦~~
🌴1.多线程的概念
多进程是一个操作系统同时执行多个任务(程序)。
而多线程指的是一个进程(程序)里执行多个顺序流。简单来说就是进程里有多个任务,我们创建了多个线程来相互协助完成任务。
线程在进程里是并发+并行的,具体怎么实现看系统调度。
我们以Java程序中的main线程为例:
public class Demo1 {
public static void main(String[] args) {
System.out.println("Hello Thread!!!");
}
}
在执行以上代码的时候,操作系统会创建出一个Java进程,同时Java进程就会有其中一个线程去执行main方法,在这个进程中,我们并没有手动去创建多个线程,但是Java进程运行的过程中,创建了多个线程来辅助代码的运行和调试。
🥝2.第一个多线程程序
class MyThread extends Thread {//1
@Override//2
public void run() {//3
while (true) {
System.out.println("Hello " + Thread.currentThread().getName());//4
try {
sleep(1000);//5
} catch (InterruptedException e) {//6
e.printStackTrace();
}
}
}
}
public class Demo2 {
public static void main(String[] args) {
MyThread thread0 = new MyThread();//7
thread0.start();//8
MyThread thread1 = new MyThread();
thread1.start();
MyThread thread2 = new MyThread();
thread2.start();//9
}
}
执行结果:
说明:
1.通过继承了Tread类来创建一个线程。
2.我们需要重写Thread类里的run方法。
3.run方法就像是一个任务清单,我们把线程要执行的任务写进去,在start方法执行时就会调用run方法。
4.Thread.currentThread()表示的是调用Thread类中的静态方法(类方法)来获取正在调用的当前线程对象,getName方法是获取线程名,如没有初始化线程名,则默认为"Thread-编号",编号从0开始。
5.让线程“睡”一会儿,别那么快打印。
6.使用sleep方法需要抛出InterruptedException异常,表示线程可能被提前唤醒。
7.创建线程对象。
8.调用start方法启动线程,如果说run方法是一张任务清单,那么start方法就是起身去完成任务清单里的任务。
9.创建多个线程并启动。
我们可以使用JDK里的jconsole工具(只能识别Java进程,不能识别非Java进程)来查看线程的执行情况:
🍕1.
🍔2.
🍟3. 我们连接我们自己写的Java进程
🌭4.
🍿5.
🥓6.
🌈start和run方法的区别
1.run方法只是一个普通方法而已,它的执行不会创建一个新的线程。不会分配新的分支栈。
2.start方法的作用是启动一个分支线程,在JVM中开辟一个新的栈空间,只要新的栈空间开辟出来了,start方法就结束了,线程就启动成功了。启动成功的线程会调用run方法,并且run方法在分支栈的栈底部(压栈)。
3.同一个线程不能重复调用start方法,否则抛出IllegalThreadStateException异常。
4.run方法必须是public void修饰的
🌲3.创建线程的五种方法
🍇3.1继承Thread类
class MyThread extends Thread {
@Override
public void run() {
System.out.println("Hello " + Thread.currentThread().getName());
}
}
public class Demo3 {
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
}
}
创建一个Thread类,重写run方法。
🍉3.2实现Runnable接口
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("Hello " + Thread.currentThread().getName());
}
}
public class Demo4 {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);//要传入Runnable对象
thread.start();
}
}
创建一个类,实现Runnable接口,重写Runnable里的方法
🔥Thread和Runnable的区别
如果一个类继承Thread类,则不适合资源共享。但是如果实现了Runnable接口,则更容易实现资源共享。
实现Runnable接口比继承Thread类所具有的优势:
🍕a.适合多个相同的程序代码的线程去处理同一个资源
🍔b.可以避免Java中单继承的限制
🍟c.增加程序的健壮性,代码可以被多个线程共享,代码和数据独立
🌭d.线程池只能放入实现Runnable或callable类线程,不能直接放入继承Thread的类
🍊3.3匿名内部类继承Thread对象
public class Demo5 {
public static void main(String[] args) {
Thread thread = new Thread() {
@Override
public void run() {
System.out.println("Hello " + Thread.currentThread().getName());
}
};
thread.start();
}
}
🍋3.4匿名内部类创建Runnable子类对象
public class Demo6 {
public static void main(String[] args) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Hello " + Thread.currentThread().getName());
}
});
thread.run();
}
}
🍍3.5使用lamda表达式创建 Runnable 子类对象
public class Demo7 {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
System.out.println("Hello " + Thread.currentThread().getName());
});
thread.start();
}
}
🥦4.Thread的常见方法及属性
🍓4.1Thread的常见构造方法
方法 | 说明 |
Thread() | 创建线程对象 |
Thread(Runnable target) | 使用 Runnable 对象创建线程对象 |
Thread(String name) | 创建线程对象,并命名 |
Thread(Runnable target, String name) | 使用 Runnable 对象创建线程对象,并命名 |
Thread(ThreadGroup group, Runnable target) | 线程可以被用来分组管理,分好的组即为线程组,这 个目前我们了解即可 |
🍎4.2Thread的几个常见属性
属性 | 获取方法 |
ID:ID是线程唯一标识工具,不同线程不会重复 | getId() |
名称:名称会在各种调试工具中用到 | getName() |
状态:表示线程当前所处的情况 | getState() |
优先级:优先级高的理论上更容易被调度到 | getPriority() |
是否后台线程:JVM会在所有非后台线程结束后才会结束 | isDaemon() |
是否存活:简单理解为run方法是否运行结束 | isAlive() |
是否被中断:判断对象关联的线程的标志位是否设置,调用后不清除标志位 | isInterrupted() |
🍅4.3start方法
我们在创建出一个线程对象并不意味着系统创建了一个线程,只有在start方法被调用了才系统才真正创建了这个线程并执行run方法。
我们在创建线程和调度线程的时候都会有开销。
🍒4.4中断一个线程
🚗4.4.1 通过共享的标记来进行沟通
public class Demo8 {
static boolean isQuit = false;//标志位
public static class MyThread extends Thread {
@Override
public void run() {
while (!isQuit) {
System.out.println("正在执行 " + Thread.currentThread().getName() + "线程");
try {
sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
public static void main(String[] args) throws InterruptedException {
MyThread myThread = new MyThread();
myThread.start();
Thread.sleep(2000);//main线程先睡眠2秒
isQuit = true;//再唤醒线程
System.out.println("线程已被唤醒");
}
}
🚓4.4.2 使用 Thread.interrupted() 或者 Thread.currentThread().isInterrupted() 代替自定义标志位
在Thread类中,包含了一个 boolean 类型的变量作为线程是否被中断的标记!!!
方法 | 说明 |
public void interrupt() | 中断对象关联的线程,如果线程正在阻塞,则以异常方式通知, 否则设置标志位 |
public static boolean interrupted() 注意:该方法为静态方法,可以通过 类名直接调用->Thread.interrupt() | 判断当前线程的中断标志位是否设置,调用后清除标志位,如果为true置为false,如果为false则不变 |
public boolean isInterrupted() 注意:该方法为实例方法,需要通过 对象来调用->Thread.currentThread().isInterrupted() | 判断对象关联的线程的标志位是否设置,调用后不清除标志位,如果是true还是true,如果是false还是false |
🥗1.使用Thread.interrupted()
class MyThread extends Thread {
@Override
public void run() {
while (!Thread.interrupted()) {//1
System.out.println("Hello " + Thread.currentThread().getName());
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Demo9 {
public static void main(String[] args) throws InterruptedException {
MyThread myThread = new MyThread();
myThread.start();
Thread.sleep(2000);//main线程先睡眠2秒
myThread.interrupt();//再唤醒线程
System.out.println("线程已被唤醒");
}
}
执行结果:
原因是因为我们这个程序里使用了sleep方法使线程“睡眠”,
也就是阻塞挂起了(后续学到的wait、join方法也会使线程阻塞挂起),那么我们在使用interrupt方法(作用:中断对象关联的线程,如果线程正在阻塞,则以异常方式通知, 否则设置标志位)的时候就会将标志位设置为true,由于当前线程正在阻塞中(在sleep),就会把阻塞状态唤醒,通过抛出异常的方式让sleep立即结束。
那照理来说应该会抛出异常就结束才是呀,那为什么抛出了异常之后线程还没有结束呢?
这就是sleep方法的锅~~
sleep方法被唤醒了之后,会自动把isInterrupted标志位给清除(true -> false),这样就导致了下次循环还能继续进行。
这样子说好像会把你们给绕晕了,我来写一个流程图来解释一下:
(其实如果在线程正在执行(即不在sleep)的时候将标志位置为true的话,sleep就不用被唤醒,也就是不会将标志位置为false,此时循环就不会继续进行,但是这种情况的概率太小太小~~)
解决这种问题有两种方法:
🍕a.不在循环中使用sleep方法
class MyThread extends Thread {
@Override
public void run() {
while (!Thread.interrupted()) {
System.out.println("Hello " + Thread.currentThread().getName());
// try {
// sleep(100);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }//把sleep都给注释掉了
}
}
}
🍔b.在catch中退出循环体
class MyThread extends Thread {
@Override
public void run() {
while (!Thread.interrupted()) {//1
System.out.println("Hello " + Thread.currentThread().getName());
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
break;//使用break语句退出循环体
}
}
}
}
执行结果:抛出异常之后就执行结束了
🍄4.5 join方法
有时候,我们需要等待一个线程结束才继续执行自己的下一步工作,比如线程1需要等待线程2执行完毕才执行自己的工作,那这时候就可以使用join方法了。
在一个线程中,谁调用join方法谁先执行。
方法 | 说明 |
public void join() | 等待线程结束 |
public void join(long millis) | 等待线程结束,最多等 millis 毫秒 |
public void join(long millis, int nanos) | 同理,但可以更高精度 |
使用join方法也会抛出InterruptedException异常~~
示例:使用join方法前:
public class Demo10 {
public static void main(String[] args) {
Thread myThread = new Thread(() -> {
for (int i = 0; i < 10; i++) {
System.out.println("myThread线程在执行");
}
});
myThread.start();
for (int i = 0; i < 10; i++) {
System.out.println("main线程在执行");
}
}
}
可以看到main线程和myThread线程交替运行,myThread线程先运行完。
使用join方法后:
public class Demo10 {
public static void main(String[] args) throws InterruptedException {
Thread myThread = new Thread(() -> {
for (int i = 0; i < 10; i++) {
System.out.println("myThread线程在执行");
}
});
myThread.start();
myThread.join();//使用了join方法
for (int i = 0; i < 10; i++) {
System.out.println("main线程在执行");
}
}
}
myThread线程先执行完,main线程再执行。
🍷4.6获取当前线程
方法 | 说明 |
public static Thread currentThread() | 返回当前线程对象的引用 |
这个我们在前面的例子已经使用了很多次了,不过多解释了,嘻嘻~~
🥤4.7sleep方法
方法 | 说明 |
public static void sleep(long millis) throws InterruptedException | 休眠当前线程 millis 毫秒 |
public static void sleep(long millis, int nanos) throws InterruptedException | 可以更高精度的休眠 |
由于线程的调度是无序的、随机的,因此这个方法只能保证休眠时间大于设置的时间。
🥩4.8yield方法
yield方法可以使线程出让CPU,相当于重新排队
示例:
yield方法没使用之前:
public class Demo11 {
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
while (true) {
//Thread.yield();先不放开,看看效果
System.out.println(Thread.currentThread().getName() + " 正在执行");
}
}, "1 号线程");
Thread thread2 = new Thread(() -> {
while (true) {
System.out.println(Thread.currentThread().getName() + " 正在执行");
}
}, "2 号线程");
thread1.start();
thread2.start();
}
}
结果是两个线程交替进行,将近均匀。
使用yield方法之后
大部分时间执行的都是2号线程,但也会少量执行1号线程
🍏5.线程的状态
方法 | 说明 |
getState() | 获取线程状态 |
线程一共有6种状态
💐5.1. 初始状态(NEW)
实现了Runnable接口和继承Thread可以得到一共线程类,new一个实例出来,线程就进入了初始状态。
🌸5.2. 就绪状态(RUNNABLE)
🍃5.2.1READY
🍕a.就绪状态只代表了该线程有资格运行,调度程序的时候如果没有调度该线程,那么该线程就永远是就绪状态。
🍔b.调用线程的start方法后,该线程进入就绪状态。
🍟c.当前线程sleep方法结束 / 其他线程join方法结束 / 等待用户输入完毕 / 某个线程拿到对象锁,那么这些线程也将进入就绪状态。
🌭d.当前线程时间片用完了 / 调用当前线程的yield方法,当前线程进入就绪状态。
🍿e.锁池里的线程拿到对象锁之后,进入就绪状态。
🍂5.2.2RUNNING
线程调度程序可运行池中选择一个线程作为当前线程时线程所处的状态,这也是线程进入运行状态的唯一的一种方式。
🌹5.3.阻塞状态(BLOCKED)
阻塞状态时线程阻塞在进入synchronized关键字修饰的方法或者代码块(获取锁)时的状态。
🌺5.4.等待(WAITING)
处于这种状态的线程不会被分配CPU执行时间,它们要等待被显示地唤醒,否则会处于无限期等待的状态。
🌻5.5.超时等待(TIMED_WAITING)
处于这种状态的线程不会分配CPU执行时间,不过无须无限期等待被其他线程显示地唤醒,在到达一定时间后它们会自动唤醒。
🌼5.6.终止状态(TERNINATED)
🍕a.当线程的run方法完成时,或者主线程的main方法完成时,我们就默认它终止了。
🍔b.线程一旦终止了就不能复生,在一个终止的线程上调用start方法,会抛出IllegalThreadStateException异常
抛IllegalThreadStateException异常
经典老图:
多线程初阶(一)的部分就到这啦,后面还有两篇滴,告辞~~