线程的初步认识
1.什么是线程
一个线程就是一个“执行流”,每个进程之间都可以按照顺序执行自己的代码,多个线程之间同时执行着多份代码。
2.进程和线程的关系
进程我们可以理解为是资源分配的基本单位,是一个程序的运行实例。
线程我们可以理解为是进程中的执行单元,是CPU调度的调度的基本单位,一个进程中可以包含多个线程(在进程管辖内的线程),这些线程可以共享进程中的资源。
进程和线程所涉及的资源都是独立的,不会相互影响,具有稳定性。
- CPU,内存,硬盘资源(文件描述符)网络宽带等都属于进程。
- 进程内部管辖着多个线程之间,会共享上述的内存资源和硬盘资源,网络宽带等......
一个进程至少包括一个线程。
进程是操作系统的基本单位
3.进程和线程的资源申请和释放
对于进程来说,进程的创建和销毁都需要申请和释放资源。
对于线程来说,进程的创建,只需要第一次创建时申请资源,以后的创建都不需要申请资源。只有将全部的线程销毁才会释放资源,只销毁一个或者一部分不会释放资源。
4.线程是CPU调度执行的基本单位
如果一个进程有多个线程,这些线程各自去CPU上调度执行,可能会出现多种情况。
比如:有线程1,2,3
- 线程1去CPU核心1去执行,线程2去核心2去执行,线程3去核心3去执行。
- 可能线程1,2,3在一个核心上来回的切换
- 也可能线程1,2在核心1上来回切换,线程3和别的线程在其他的核心上来回的切换。
5.线程的调度相关的数据(每一个线程都有这些数据)
描述线程的基本属性:
- 线程 ID(Thread ID):线程的唯一标识。
- 优先级(Priority):线程的调度优先级,通常用整数表示。
- 线程状态(Thread State):如就绪、运行、阻塞、终止等。
- 所属进程 ID(Parent Process ID):线程所在的进程标识。
6.线程的效率
比如有一个人吃100个面包,需要100分钟。
如果我们增加一个人,两个人同时吃100个面包,时间减少一半,需要50分钟。
那我们再添加两个人,四个人同时吃面包,时间还能再减少一半,需要25分钟,效率大大提高。
如果我们200个人吃100个面包呢,200个人则会对100面包进行争抢,大大的降低效率。
所以适当的线程的虽然会提高效率但是线程太多的话,线程的调度开销也是十分的明显,因为线程的调度开销降低程序的性能。
如果在吃的过程中,两个人看中了同一个面包,则会产生冲突,则会产生线程不安全的问题,这样的问题会是代码出现bug。
一个线程抛出异常,可能会带走整个进程,使所有的线程无法工作
服务器
服务器这个词不一定指的是服务器的软件(程序),也可能指的是部署了服务器软件的这一台机器(硬件)。
一个服务器可以给多个客户端提供服务,也可以只给一个客户端提供服务。
Thread类(标准库中提供的类)
线程是操作系统给出的概念,操作系统提供了一些Api给程序员使用
Api(Application programming interface)->别人写了一些类和函数,你拿过直接能用
操作系统提供的原生线程Api是由C语言的,不同操作系统线程Api不同。但是通过Java的统一封装,封装成了Thread类。
创建一个多线程的程序
java">class MyThread extends Thread{public void run() {while (true) {System.out.println("thread");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}}
public class demo1 {public static void main(String[] args) throws InterruptedException {Thread t=new MyThread();t.start();while (true){System.out.println("start");Thread.sleep(1000);}}
}
注解:
我们发现在创建对象时,不需要import包,就可以直接的使用Thread,这是因为有些包可以不用导入就直接使用,java.long默认import
使用了向上转型
通过start创建一个线程,多了一个执行流。
真正在系统中创建线程,(JVM调用系统的API~来完成创建线程的操作)
让当前的线程暂时的休息一会,等到1000毫秒之后,继续执行。
运行的结果
一直会重复的运行
我们可以通过第三方的工具来查看线程的详细情况
1.找到program files
2.打开java文件找到bin文件
3.找到jconsole
4.进行连接
5.查看线程
创建Thread的方式
1.继承Thread,重写run
java">class MyThread extends Thread{public void run() {while (true) {System.out.println("thread");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}}
public class demo1 {public static void main(String[] args) throws InterruptedException {Thread t=new MyThread();t.start();while (true){System.out.println("start");Thread.sleep(1000);}}
}
run方法相当于回调函数
回调函数是指将一个函数作为参数传递给另一个函数,在某些特定事件或条件满足时,由后者调用这个函数,回调函数本质上是一个函数,但它被传递到另一个函数中以便后续调用
2.实现Runnable,重写run
java">class myRunnable implements Runnable{@Overridepublic void run() {while (true) {System.out.println("myRunnable");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}
public class demo2 {public static void main(String[] args) throws InterruptedException {Runnable runnable=new myRunnable();Thread t=new Thread(runnable);t.start();while (true){System.out.println("demo1");Thread.sleep(1000);}}
}
表示一个“可以执行的任务”,这种写法可以更好地进行解耦合(高内聚低耦合)
3.使用匿名内部类
不知道名字的内部类,叫做匿名内部类
java">public class demo3 {public static void main(String[] args) throws InterruptedException {Thread t=new Thread(){public void run(){while (true) {System.out.println("thread");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}};t.start();while (true){System.out.println("start");Thread.sleep(1000);}}
}
如果一段代码是一次性的,可以使用匿名内部类来进行定义
4.使用Runnable,匿名内部类
java">public class demo4 {public static void main(String[] args) throws InterruptedException {Runnable runnable=new Runnable() {@Overridepublic void run() {while (true) {System.out.println("thread");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}};Thread t=new Thread(runnable);t.start();while (true){System.out.println("start");Thread.sleep(1000);}}
}
5.引用ambda表达式
本质上是匿名函数,最主要的用途是作为回调函数
java">public class demo5 {public static void main(String[] args) throws InterruptedException {Thread t=new Thread(()->{while (true){System.out.println("thread");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}});t.start();while (true){System.out.println("start");Thread.sleep(1000);}}
}
Thread类其他的属性和办法
Thread()使用这个写法,必须重写Thread中的run
Thread(Runnable target)使用Runnable对象创建线程对象,不用重写Thread中的run
Thread(Runnable target,String name)使用这个方法可以给线程起名字
java">public class demo6 {public static void main(String[] args) {Thread t1=new Thread(()->{while (true){System.out.println("thread 1");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}},"t1");t1.start();Thread t2=new Thread(()->{while (true){System.out.println("thread 2");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}},"t2");t2.start();Thread t3=new Thread(()->{while (true){System.out.println("thread 3");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}},"t3");t3.start();}
}
Thread中几个常见的属性
java">public class Demo7 {public static void main(String[] args) throws InterruptedException {Thread t=new Thread(()->{while (true){System.out.println("thread");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}});t.setDaemon(true);t.start();for (int i = 0; i < 3; i++) {System.out.println("start");Thread.sleep(1000);}System.out.println("结束");}
}
得放在start之前
前台线程:是程序的主要工作线程,它的存在会阻止应用程序的终止。只要有任何前台线程仍在运行,整个应用程序就会继续运行。
后台线程:是辅助性线程,不会阻止应用程序的终止。如果所有前台线程结束,应用程序将立即终止,无论后台线程是否完成。
这些是JVM自带的线程
java">public class Demo8 {public static void main(String[] args) throws InterruptedException {Thread t=new Thread(()->{for (int i = 0; i < 3; i++) {System.out.println("Thread");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}});t.isAlive();t.start();while (true){System.out.println(t.isAlive());Thread.sleep(1000);}}
}
运行结果:
注:此时的运行结果一定是false,因为还没有start,还没有创建线程
启动一个线程
start()
start()是java标准库的方法,JVM提供的,本质上是调用操作系统的API
查看start()的源码
注:1.带着native的方法,是在JVM内部的方法,是由c++实现的,调用了其方法,所以无法看到,如果想看到,必须额外下载其对应的源码。
2.每个Thread只能start一次,跟日抛的意思差不多,
中断一个线程
线程的入口执行完毕,自然就会中断
通过增加条件来判断是否完成,来进行中断。
java">public class Demo10 {private static boolean isFinished = false;public static void main(String[] args) throws InterruptedException {Thread t=new Thread(()->{while (!isFinished){System.out.println("Thread");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}System.out.println("结束");});t.start();Thread.sleep(3000);isFinished=true;}
}
如果把private static boolean isFinished = false;这个成员变量改为局部变量还可以吗?
不可以
因为lambda 是回调函数,执行时机,是很久之后(操作系统真正创建出线程之后,才会执行的)
很有可能,后续线程创建好了,当前 main 这里的方法都执行完了,对应的 isfinished 就销毁了....
为了解决上述的问题,
Java 的做法是,把被捕获的变量给拷贝一份,拷贝给 lambda 里面
外面的变量是否销毁,就不影响 lambda 里面的执行了
拷贝,意味着, 这样的变量就不适合进行修改
修改一方,另一方不会随之变化的(本质上是两个变量)
这种一边变,一边不变,可能给程序员带来更多的困惑
Java 大佬们想了个办法,压根不允许你这里进行修改
如果是成员变量
lambda 本质上是 函数式 接口
相当于一个 内部类
isFinished 变量本身就是外部类的成员
内部类本来就能够访问外部类的成员,所以定义为成员变量的可以
成员变量是由GC(垃圾回收 Garbage Collection)管理的,在lambda中,不用担心生命周期失效的问题,由于GC 搞的太好,让咱们写代码的时候,不太能感知到生命周期的问题。
线程终止
线程的Thread中提供了对象,所以不需要自己写,直接用就可以了
java">public class Demo11 {public static void main(String[] args) throws InterruptedException {Thread t=new Thread(()->{while (!Thread.currentThread().isInterrupted()){System.out.println("Thread");try {Thread.sleep(1000);} catch (InterruptedException e) {break;}}System.out.println("结束");});t.start();Thread.sleep(3000);System.out.println("main 线程尝试终止 t 线程");t.interrupt();}
}
通过 while (!Thread.currentThread().isInterrupted())
检查中断状态决定是否继续执行。但当线程调用 Thread.sleep()
时,被中断后抛出的 InterruptedException
会自动清除中断标志,导致 isInterrupted()
始终返回 false
。
改进后:
java">public class Demo11 {public static void main(String[] args) throws InterruptedException {Thread t=new Thread(()->{while (!Thread.currentThread().isInterrupted()){System.out.println("Thread");try {Thread.sleep(1000);} catch (InterruptedException e) {break;}}System.out.println("结束");});t.start();Thread.sleep(3000);System.out.println("main 线程尝试终止 t 线程");t.interrupt();}
}
希望能对大家有所帮助!!!!