目录
3.继承Thread类,重写run()方法.但使用匿名内部类
4.实现Runnable接口,重写run()方法,但使用匿名内部类:
在写代码的时候,可以用多进程编程,也可以用多线程编程.
多进程编在java中不太推荐,因为与多进程编程相关的api,在java标准库中都没有提供.
系统提供了多线程编程的api,在java标准库中,把这些api都封装了,在代码中可以直接使用.
并且在面对频繁创建和销毁进程的时候,多线程编程具有非常大的优势.效率非常高.
java 常见的包 :
1. java.lang:系统常用基础类(String、Object),此包从JDK1.1后自动导入。
2. java.lang.reflect:java 反射编程包;
3. java.net:进行网络编程开发包。
4. java.sql:进行数据库开发的支持包。
5. java.util:是java提供的工具程序包。(集合类等) 非常重要
6. java.io:I/O编程开发包
Thread类就是java.long包下的类,不需要引入包就能直接使用.
一个.java文件中,只能有一个类被public修饰,若该类没有被public修饰,就只能在该包中,被别的类引用.
一个进程至少会有一个线程,该进程的第一个线程就是main线程,也就是主线程的入口方法.
回调函数:
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
简单来说:回调函数就是把一段代码,向传参一样,传递给其它代码,这段代码会在某个时刻被调用执行.这就叫做回调.
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。
回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。如果代码立即被执行就称为 同步回调,如果过后再执行,则称之为 异步回调。
什么是线程:
⼀个线程就是⼀个"执⾏流".每个线程之间都可以按照顺序执⾏⾃⼰的代码.多个线程之间"同时"执⾏ 着多份代码
第一个线程:
创建一个Thread01类,让该类继承Thread类,重写Thread类的run()方法,
在主函数中实例化该类,调用该类的start()方法,这样,不需要我们手动调用run()方法,待线程创建好后,会在合适的时机被jvm自动调用.(这种风格的函数,就被称为"回调函数"callback)
调⽤start()⽅法,才真的在操作系统的底层创建出⼀个线程.
package Thread_;
class MyThread01 extends Thread{
@Override
public void run() {//重写run()方法
//run()方法就是该线程的入口
while (true){
System.out.println("Thread run");
}
}
}
public class Thread01 {
public static void main(String[] args) {
Thread thread01 = new MyThread01();
thread01.start();//必须要调用start()方法,才能开启线程
while(true){
System.out.println("main ");
}
}
}
当引入多线程之后,代码中就可以同时具备多个执行流了.
验证多线程执行:
可以通过jdk/bin/jconsloe.exe文件看进程状态.
先运行自己写的代码,让后进行下面的操作
内核:
操作系统的内核是操作系统最核心的功能模块.(管理硬件,给软件提供稳定的运行环境)
简单来说:内核态是非常重要的,不允许用户修改的,划分出用户态和内核态也是目的是为了稳定,以防自己的应用程序把硬件设备或软件资源给搞坏了.
操作系统=内核+应用程序.
一旦开始执行,线程就会飞快的循环起来,使cpu占用率较高,进一步提高电脑的功耗,为了不让循环跑的那么快,可以在循环体中设置Thread类的sleep()方法,让每隔一定时间运行一次,这样就能降低电脑的功耗.
调用sleep()方法:
sleep()方法调用时,会有受查异常,需要手动抛出.
Mythread类中的run()方法内的异常抛出只能以try-catch的形式,不能以throws的形式,因为run()方法是重写父类的方法,父类没有throws这个异常,若加上throws,就修改了方法签名,因此,子类重写run()方法的时候,也就只能以try-catch的形式抛出异常了.
以try-catch的形式抛出异常:
以throws的形式抛出异常:
时间转换单位:
1s=1000ms(毫秒)
1ms=1000(微妙)
1um=1000nm(纳秒)
上面的第一个线程代码,可以看出,main()方法中有一个死循环,在Thread01类的run()方法中也有一个死循环,一般来说,一个代码中出现两个死循环,只会运行一个,但实际上这两个死循环中的代码都被运行了,这就说明是两个线程在同时执行.
执行结果分析:
从执行的结果上可以看出,thread和main是随机交替出现的,也就是说,每隔一秒钟,thread和main被执行一次,但是谁先被执行,是无法确定的.
但能看出,每次最开始执行的时候,都是main第一次被执行,这是因为在创建MyThrow01线程的时候,还是会有开销的,这个开销比创建进程要低很多(但也不是没有),就是这一点点的开销,使得main线程每次在最开始的时候先执行.
(注意:main线程是第一线程)
线程创建的几种方式:
1.继承Thread类,重写run()方法.
也就是上面的这种方式.
package Thread_;
class MyThread01 extends Thread{
@Override
public void run() {
//run()方法就是该线程的入口
while (true){
System.out.println("Thread run");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
public class Thread01 {
public static void main(String[] args) {
Thread thread01 = new MyThread01();
thread01.start();//必须要调用start()方法,才能开启线程
while(true){
System.out.println("main ");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
2.实现Runnable接口,重写run()方法.
Runnable接口需要搭配Thread类使用,才能真正在系统中创建出线程来
package Thread_;
class MyThread02 implements Runnable{
@Override
public void run() {
while(true){
System.out.println("Thread01 run()");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
public class Thread02 {
public static void main(String[] args) {
MyThread02 runnable = new MyThread02();
Thread thread1 = new Thread(runnable);//Runnable接口需要搭配Thread类使用,才能真正在系统中创建出线程来
thread1.start();
while(true){
System.out.println("main()");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
3.继承Thread类,重写run()方法.但使用匿名内部类
Thread t=new Thread(){//这里写的{}表示要定义一个类,并且这个新类继承Thread,
//且没有名字,用一次就不能再用了
//{}中可以定义新类的属性和方法
//此处的目的就是重写父类Thread的run()方法.
@Override
public void run() {
while(true){
System.out.println("Thread run03");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
};
这里的thread变量名指向的是新创建的Thread的匿名内部类(子类),而不是Thread类.
4.实现Runnable接口,重写run()方法,但使用匿名内部类:
Thread构造方法的参数,传了Runnable的匿名内部类的实例.
5.使用lambda表达式(推荐)
lambda表达式原理:
Thread thread=new Thread(()->{ //这里的()是形参列表,在这里,不需要传参数
//()的前面应该有个函数名,这里是匿名的,所以没有名字
//->后面的{}是方法体,
while(true){
System.out.println("Thread run5");
try{
Thread.sleep(1000);
}catch(InterruptedException o){
throw new RuntimeException(e);
}
}
});
这里的规则是方法不能脱离类单独存在.
这几种方法都是等价的,作用都相同.
Thread的构造方法
1.构造方法:
前两个在前面创建线程的时候都提到了,
第三,四个:给线程起个名字:
在jcolsole.exe中就能看到线程名字被修改
自己创建的线程默认是按照Thread-0 1 2 ...创建的,线程之间的名字可以重复,起名字是为了方便调试.
Thread的常用的属性:
1.ID:jvm自动分配的身份标识,不同线程不会重复
2.名称是调试的时候用到的
3.状态:
线程有不同的状态:就绪状态,阻塞状态
4.优先级:
优先级⾼的线程理论上来说更容易被调度到,线程的优先级在java中,效果不是很明显
5.是否后台线程:
和后台线程相对应的是前台线程,当一个进程开始运行的时候,只有前台线程都运行结束,后台线程才会结束.
JVM会在⼀个进程的所有⾮后台线程结束后,才会结束运⾏。
目前代码创建的线程都是前台线程,都会阻止线程的结束,当全部都执行完了,才会结束整个进程.
设置线程为后台线程:在start之前setDaemon(true);此时,该线程就为后台线程
在start之前,给thread线程设置成了后台线程,在main函数之后,休眠2秒,thread线程就执行了2秒,打印了两遍,main线程结束了,thread线程为后台线程,也就结束了.
6.是否存活:
表示内核中的线程是否还存活,在start之前,还没有开始线程,isAlive为false,
只有调用了start,线程才开始执行,isAive才为true.
2s之后,run()已经结束,isAlive为false.