多线程2-多线程编程
引入
当我们想要代码能够实现并发执行时,我们可以使用多进程进行并发编程(在Java中并不推荐这种方式,许多API在Java标准库中都没有提供),也可以使用多线程进行并发编程(系统提供了相关的API,Java标准库已经对这些API进行了封装,在代码中可以直接使用)。
创建线程
方法1 继承Thread类
//1、创建一个自己的类,继承Thread
//java.lang是默认导的
class MyThread extends Thread{@Overridepublic void run() {//run方法是该线程的入口方法,不需要手动调用,会在合适的时机(线程创建好之后)被jvm自动调用执行->回调函数System.out.println("hello world");}
}
public class ThreadDemo1 {public static void main(String[] args) {//main方法是java进程的入口方法//2、根据刚才的类创建出实例(线程实例,才是真正的线程)Thread thread = new MyThread();//3、调用Thread的start方法,才会真正调用系统api,在系统内核中创建出线程,线程就会执行上面的run方法thread.start();}
}
注意:(1)这里的Thread类是在java.lang这个包中的,这个包包含了Thread类,这个包是JVM自动导入的无需声明。(2)这里如果调用的是run()方法是没有创建新的线程的,而使用start()方法是创建了新的线程的。(3)run()方法,并不需要程序员手动调用,该方法会在线程创建好之后,在合适的时机被JVM自动调用执行。(这种方法也被称为回调函数,如使用PrioriyQueue的时候,它会自动调用Comparator接口的compareTo方法)。
知识补充:什么是操作系统的内核?
通过之前的学习,我们知道了操作系统的作用:1、管理硬件资源。2、为软件提供稳定的运行环境。为了完成上述两项作用,就需要有操作系统的内核:操作系统中,最核心部分的功能模块。
操作系统大致可以分为内核空间(内核态)和用户空间(用户态),平时运行的普通的应用程序,QQ音乐,idea,抖音等等都,都是运行在用户态的。但是这些应用程序有时需要针对一些系统提供的硬件资源来进行操作。这些操作,都不是应用程序直接操作的,需要调用操作系统提供的API,进一步在内核中完成这样的操作。
为什么要分出用户态和内核态呢?
主要目的还是稳定。防止某些应用程序把硬件设备或者软件资源搞坏了。系统封装了一些API,这些API都属于一些“合法”的操作,不会对硬件设备或者软件资源有所破坏,应用程序只能提供这些API来实现对应的功能,从而不至于对系统/硬件设备产生太大的危害。假如让应用系统直接操作硬件,在极端条件下,代码会出现bug,把硬件整坏。
把操作系统比作是银行,办公窗口里的工作人员就是操作系统的内核,用户要办理各种业务,就需要在办事窗口前,给工作人员说清楚需求,由工作人员来处理这些业务。
多线程演示:
每个线程都是一个独立的执行流,每个线程都能够独立的去CPU上调度执行。
代码:
package Thread;
class MyThraed2 extends Thread{@Overridepublic void run() {while(true){System.out.println("hello Thread");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}
public class ThreadDemo2 {//可通过jdk—bin—jconsol来监视线程public static void main(String[] args) {//t线程中MyThraed2 t = new MyThraed2();t.start();//主线程中//两个独立的执行流while(true){System.out.println("hello true");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}
注意:这里使用sleep方法,是为了让执行的结果能够被我们看到,如果不添加,打印的结果会很快,我们看起来不明显。 这个sleep方法需要处理异常,这个异常是因为在sleep(1000)的过程中可能会被提前唤醒。(换句话说:这个程序可能在休眠1s的过程中,被其他操作提前唤醒·,也就是没休眠够1s,就被唤醒了),我们可以在catch方法中让编译器去处理这个异常,也可以直接通过throws关键字把这个异常抛出去。但是,在run方法中只能使用try—catch方法,这是因为run方法是一个重写方法,使用throws方法就相当于修改方法签名了。
下图所示的循环是在t线程中执行的:
下图所示的循环是在main线程中执行的:
以我们之前的理解,如果代码中出现了两个死循环,则只能执行一个循环,另一个循环就进不去了。但是我们把当前进程运行起来,可以发现,两个循环都在运行,且打印的顺序不确定。 该打印结果印证了刚开始那句话:每个线程都是一个独立的执行流,每个线程都能够独立的去CPU上调度执行。同时也说明了上一篇文章中的:线程的随机调度/抢占式执行。
下面我们来剖析线程的调度过程:在调用start方法,创建线程之后,兵分两路,一路沿着main方法,继续执行,打印hello true,另一路,进入到线程的run方法中,打印hello thread。这两个进程是相互独立,互不干扰的。
主线程,在调用start方法之后,就理科往下执行打印了,与此同时,内核要通过刚才API构建出线程,并调用run方法。由于创建线程有一定的开销。所以,在第一轮打印时,hello thread一般会比hello true 略慢一筹。
通过jconsole监视多个线程
我们可以通过jdk提供的第三方工具jconsole工具,来监视java进程的情况:
注意:要对线程进行监视,首先要把进程运行起来。
选择刚才的线程进行连接:
进去之后点击线程:
main对应的就是main方法的主线程。
Thread-0就是我们刚才所创建的t线程(这个Thread-0、1、2……是默认名称,可以修改)。
剩下的线程都是JVM自带的线程,这些自带的线程,要完成一些垃圾回收,监控统计各种指标……
点进具体的线程,就可以看到相关的调用栈(线程里当前执行到了那个方法的第几行代码,这个方法是如何如何一层调用过去的...)
这里这个线程的在不断的运行的,点击线程详细情况的这个瞬间,就相当于闪照一样,把这一瞬间的状态展示到这里了。
方法2 实现Runnable接口,重写run方法
package Thread;
class MyThread3 implements Runnable{//不仅仅可以搭配线程执行@Overridepublic void run() {while(true){System.out.println("hello runnable");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}
public class ThreadDemo3 {public static void main(String[] args) {//这种写法相当于把线程和要执行的任务解耦合了//Runnable runnable = new MyThread3();//只是一段可执行的的代码//Thread t = new Thread(runnable);Thread t = new Thread(new MyThread3());t.start();while(true){System.out.println("hello main");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}
方法3 继承Thread,重写run,但是使用匿名内部类
package Thread;public class ThreadDemo4 {public static void main(String[] args) {Thread thread = new Thread(){//{}意思是定义一个类,这个类继承自Thread,此处最主要的目的是重写run方法,与此同时还创建了一个子类的实例@Overridepublic void run() {while (true) {System.out.println("Hello Thread");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}};thread.start();while(true){System.out.println("hello main");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}
方法4 实现Runnable,重写 run,匿名内部类
package Thread;public class ThreadDemo5 {public static void main(String[] args) {Thread thread = new Thread(new Runnable() {@Overridepublic void run() {while(true){System.out.println("hello runnable");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}});thread.start();while (true){System.out.println("hello main");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}
方法5 使用lambda表达式(最常用)
package Thread;public class ThreadDemo6 {public static void main(String[] args) {Thread thread = new Thread(() ->{while (true){System.out.println("hello thread");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}});thread.start();while (true){System.out.println("hello main");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}