关于线程,在JVM开启后自然要有一个执行路径--------主线程,肯定就是由系统/JVM创建开启的(不然程序怎么执行)
创建线程
创建线程的需求环境:当在执行主线程时,遇到循环导致在指定位置停留太久。此时就需要创建并开启线程
(关于创建线程的思想引入理解:
假设处于需求环境中,我们要明确,1.多线程并不能缩短时间,因为CPU在某一时刻只能执行一个任务,开启的线程越多效率会越多,因为CPU切换会有时间消耗。多线程开启的意义,1.程序上,提供同时打开多个任务的“便利错觉”,2.在内存上,创建一个路径,为CPU提供切换路径
如果按照只有一个线程,在前面遇到长时间的循环,那么会导致后面的程序长时间无法执行
例如:
在运行QQ时如果只有一个主线程(聊天在游戏之前),那么只有我们结束聊天。结束聊天这个程序,才能进入游戏。但是如果是使用多线程,当对方在忙没有与你对话时,不用结束聊天这个程序,我们还是可以打开游戏
多线程的意义还是很大的)
在程序中我们看见的只有主线程,而主线程是由系统创建的(系统为程序开辟空间,CPU进行处理。都是依赖于系统)
创建线程的方法一:
1.继承Thread
2.重写Thread 中的Run 方法(public void Rund())
3.创建线程对象
4.调用start 方法,开启线程路径,并使JVM调用重写的Run 方法(一个线程对象对应了一条路径/线程)
package Thread;
class Demo extends Thread{
private String name;
Demo(String name){
this.name=name;
}
public void run(){
for(int i=1;i<=20;i++){
System.out.println("name : "+name+Thread.currentThread().getName()+" "+i);
}
}
}
public class ThreadDemo1 {
public static void main(String[] args){
Demo D1 = new Demo("Liu");
Demo D2 = new Demo("Li");
D2.start();
D1.run();
}
注意:
线程对象调用run方法和调用start 的区别:
一个是简单的对象调用方法,一个是开启线程并调用run方法
1. 在主函数中创建线程对象,不开启线程就不会有路径,就只有主线程进行
2. 主函数只负责线程的开启,在主线程(任何一个线程中,代码是一步执行完才能执行下一步)
3.
关于创建线程的相关原理:
1. 为什么要继承Thread类:
在程序中我们首先只看得到主线程(主函数)可以明确主线程是由系统创建的,那么我们要思考Java是否为我们提供了一中创建线程的方式——通过查找API文档——找到继承Thread 这个方法
Thread 描述了线程事物所该具备的功能。
2. 为什么不直接用thread 创建对象:
直接用thread 创建的对象调用 start 方法,JVM调用的run 方法是没有任何执行的。不符合需求/在这个run 中没有我们需要执行的代码。
Run 中的代码就是这个线程要执行的部分,是这个线程的任务
创建线程的原因:是为了给一部分的代码建立单独执行的路径,实现多部分代码同时进行
3. 这部分关于内存:
1.对象还是在堆内存中
2.关于线程:
多线程时,在栈中会有分区(start 方法运行结束后),主线程区,其他线程区的名字自动生成:Thread-n(整数)
在创建线程对象时就会给线程自定义名字(整数是从0开始的),但是是否开启(对象是否调用start 方法才是决定这片栈空间是否存在)
在每个栈分区中代码的执行和内存的变化跟没有多线程时是一样的(方法进栈/弹栈),当线程中的方法全部出栈后,线程结束,相应的线程空间就会释放。线程与线程之间的运行是相互不干扰的。
【当栈中有多个线程时,CPU会在线程间随机的切换(执行是随机的/CPU在某一时刻是不可能同时执行多个程序的)】
————————————————————————————————————————————————————————————————————————————————————————————————————————
关于线程的名字:
自动生成 Thread-n(为整数)
返回正在执行的线程对象的引用:Thread.currentThread();
|
返回对当前正在执行的线程对象的引用。 |
获取引用的名字:
Thread.currentThread().getName();
在主函数中 new 出的对象是在堆内存中的,可以理解为是这个线程的所运行时要的数据,他被开启后就对应了一天路径/一个线程。获取线程对象引用获取的是路径的名字而不是堆中对象的名字(是实际运行的路径的名字)
————————————————————————————————————————————————————————————————————————————————————————————————————————
关于线程中的异常:
在异常学习中,JVM给出的异常提示:Exception in thread "main" java.lang.RuntimeException: 传入的年龄不合法!!
会给出异常所在的线程
————————————————————————————————————————————————————————————————————————————————————————————————————————
创建线程的方法二:
1.定义类实现接口:Runnable(只有一个方法Run )——避免单继承的局限性
2.实现Run 方法:将线程的任务写到Run 方法中,写出线程任务
3.创建实现类的对象
4.创建Thread 类的对象,将实现类的对象作为参数传入——只有创建Thread的对象才能创建线程
5.用Thread 的对象调用start 方法
package Thread;
class Demo1 implements Runnable{
private String name;
Demo1(String name){
this.name=name;
}
public void run(){
for(int i=1;i<=20;i++){
System.out.println("name : "+name+Thread.currentThread().getName()+" "+i);
}
}
}
public class ThreadDemo2 {
public static void main(String[] args){
Demo1 D = new Demo1("LI");//不是线程对象
Demo1 d = new Demo1("Liu");
Thread T = new Thread(D);
T.start();
d.run();
}
}
实现接口的原理:
1.实现接口Runnable 是为了避免单继承的局限性
2.为什么要将实现类的实例作为参数传入Thread类的实例
原因:(源码是王道)
package Thread;
class Demo1 implements Runnable{
private String name;
Demo1(String name){
this.name=name;
}
public void run(){
for(int i=1;i<=20;i++){
System.out.println("name : "+name+Thread.currentThread().getName()+" "+i);
}
}
}
public class ThreadDemo2 {
public static void main(String[] args){
Demo1 D = new Demo1("LI");//不是线程对象
Demo1 d = new Demo1("Liu");
Thread T = new Thread(D);
T.start();
d.run();
}
}
在Thread类的代码中,有参数为Runnable型的构造函数,在start方法中调用了 run 方法,run方法中有判断如果传入的Runnble 型的成员变量不为空则执行变量的Run方法
在Thread 类的源码中有这样的一段代码
class Thread{//实现了接口Runnable
private Runnable target = null;
Thread(Runnable tareget){
this.target = tareget;
}
public void run(){
if(target!=null){
target.run();
}
}
public void start(){
run();
}
}
Thread的实例才能创建线程,在线程创建时就要让线程知道自己的线程任务——始终明确线程开启的意义——执行代码/执行线程任务
自我总结:Thread或者其子类才能创建线程,开启线程任务的函数start 而start 函数里只有调用Run 方法这一个执行,但是Thread 本类的Run 方法只有一个判断语句,因此要写run 方法的内容,一:继承Thread 重写run 方法 二:实现Runnable 接口,实现 run 方法,再将实现类传入Thread 的实例
两种创建方法的使用区别:
继承Thread :单独的一个类,没有继承谁
实现Runnable :要被线程执行的类向上抽取有父类(继承了某个类)避免了单继承的局限性,更加的常用