为了方便理解多线程的概念,我们先举一个例子:
假如我们把公司看做是一个进程,那么人就是其中的线程。进程必须得有一个主线程,公司在创业初期往往可能出现一人打天下的现象,但是,至少得有一个人,公司才能运作。公司创业初期,业务还不算太多,往往就是老板一个人身兼数职,一天如果只有1、2趟活儿,应该还是忙得过来的。时间长了,随着业务的发展、口碑的建立,生意越来越兴隆,一个人肯定就忙不过来了。假设一天有5个活儿,老板一个人必须搬完A家才能搬B家,搬到黄昏估计也就搬到C家,D和E家都还在焦急地等待着呢。老板一个人要充当搬运工、司机、业务联系人、法人代表、出纳等众多角色,累死累活公司的规模也上不去,人手不够制约了公司的发展。那么怎么办,很简单,增加人手,然后这些人各司其职,同时工作,很块就处理完了公司的业务。而这些人手就是所谓的线程,开启了的线程可以并行运行。
一,进程和线程的概念
进程:是一个正在执行的程序。每一个进程都有一个执行顺序,该顺序是一个执行路径,或者叫做执行单元。
线程:是进程中的一个独立的控制单元,线程控制进程的执行。
(一个进程至少有一个线程)
二,自定义线程的两种方式:一种是继续Thread类,另外一种是实现Runable接口。
第一种:继承Thread类
步骤: 1.定义类继承Thread类
2.重写Thread类中的run方法
为什么要重写run()方法? 将新定义的线程所要执行的代码存储在run()方法中,因为Thread类是用来描述线程的,而用于存储该线程运行时的代码的功能,就是由run()方法实现的,所以一般将新建的线程所要执行的代码,都放到run()方法中。注意主线程运行时的代码是存储在main()方法中的。(一般新建立的线程调用的start()方法,是由父类继承下来的,而start()方法中调用的run()方法,是被Thread子类重写过的方法。
3.调用线程的start()方法(该方法的作用:启动线程,并调用run方法)
1 class Demo extends Thread//1.继承Thread类 2 { 3 public void run() { //2.重写run方法 4 for(int i=0;i<10;i++){ 5 System.out.println("run..."+i); 6 } 7 } 8 } 9 public class TreadDemo { 10 public static void main(String[] args) {//main函数也是一个线程 11 Demo d=new Demo();//创建一个线程 12 d.start();//3.调用start方法 13 for(int i=0;i<20;i++){ 14 System.out.println("main...."+i); 15 } 16 } 17 }
运行结果部分图:
运行结果分析:
主线程(要执行代码存储在main()函数中)开始执行,当运行到这一句Demo d=new Demo(); 会新创建一个线程2,接着主线程调用新线程的start()方法,而start()方法内部又会区调用run()方法(这个新线程要执行的代码存储在run()方法中),此时新线程也开启了。而这一块打印结果为什么是main和run 的交替?因为当线程2开启后,只能说明线程2具备了运行条件,不一定立马就有cup的执行权,所以打印的结果先是main.....i ,这时线程2突然抢到了cup执行权,于是也进行了打印输出run.....i ,接着cup执行权又被主线程强走,然后打印main.....i ,所以打印结果就是他们的交替。下图是对上述代码执行过程的分析,要注意的是一个进程在开始至少有一个线程,而对于上面这个代码,刚开始的这个主线程就是由main()函数开启的。而且只要进程中还有线程未执行完毕,该进程就不会结束。
说到这一块可能就有人迷惑,那既然调用start()方法时,该方法会接着调用run()方法,那为什么不能直接调用run()方法来执行呢?要注意start()方法的作用不止调用run()方法,还用启动线程的作用。
先举一个直接调用run()方法的例子:
1 public static void main(String[] args) {//main函数也是一个线程 2 Demo d=new Demo();//创建一个线程 3 d.run();//3.调用start方法 4 for(int i=0;i<20;i++){ 5 System.out.println("main...."+i); 6 } 7 }
run()方法中的代码与上个例子中的相同。
执行结果:
不论运行多少次,都会发现结果和上图都是一样的。这和线程的随机性明显不符,为什么呢??(线程的随机性指的是多个线程的执行结果不唯一,谁抢到cup执行权谁执行)
因为当你直接调用run()方法时,虽然通过该句Demo d=new Demo()已创建新线程,但是并没有启动新线程!! 所以当主线程执行到这一句d.run(),因为新线程没有开启,所以run()方法中的内容是在主线程中执行了的,所以只有当run打印完,才会轮到main打印。
要注意,人们平时看到的多个线程在“同时”执行,其实在底层并不是多个线程同时一块执行的,而是通过快速的交替使用cup来执行自己的任务,因为其交替的速度非常快,快到人眼是感觉不到的,所以使我们在表面上看去,以为是多个线程在同时执行。这也就是为什么当我们电脑打开的程序也多时,电脑就会越卡。
补充:可通过Thread的getName()方法获得新线程的默认名字。 新线程的默认名字格式:Thread-0 编号从0开始。
//两种获得线程名字的方法
this.getName(); Thread.currentThread().getName();
Thread.currentThread()//可获得当前线程对象
那如何给新线程自定义名字呢?通过查资料,我们得知Thread有一个带参构造函数,所以我们可以直接在新建线程时,直接将名字赋给它。
Demo d=new Demo("one");//创建一个线程
要注意我们既然要使用它的带参构造函数,那么我们在子类中就必须定义一个带参构造函数。
1 class Demo extends Thread//1.继承Thread类 2 { 3 Demo(String name){ //定义一个带参构造函数。 4 super(name); 5 } 6 public void run() { //2.重写run方法 7 for(int i=0;i<10;i++){ 8 System.out.println(this.getName()+"run..."+i); 9 } 10 } 11 } 12 public class TreadDemo { 13 public static void main(String[] args) {//main函数也是一个线程 14 Demo d=new Demo("one");//创建一个线程 15 d.start();//3.调用start方法d.run() 16 for(int i=0;i<20;i++){ 17 System.out.println("main...."+i); 18 } 19 } 20 }
第二种:实现Runable接口(其实Thread也是实现Runnable接口的)
步骤: 1.定义类实现Runnable接口
2.重写Runnable接口中的run()方法
目的:将线程执行的代码存储在run()方法中
3.通过Thread类建立线程对象
4.将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数
为什么要将Runnable接口的子类对象传递给Thread的构造函数?
因为自定义的run()方法所属的对象是Runnable接口的子类对象,所以要让线程去执行指定对象的run()方法,就必须明确该run(方法的所属对象。
5.调用Thread类的start方法开启线程并调用Runnable接口子类的run()方法
实现方式和继承方式的区别:
1,实现方式避免了单继承的局限性。(因为一个类只能继承一个类,当继承了Thread类就无法在继承其他类,但因为实现多个接口,所以就可以继承其他类和实现其他接口。)
2,继承Thread:线程代码存放在Thread子类的run()方法中
实现Runnable:线程代码存放在Runnable接口子类的run()方法中
1 class Demo implements Runnable{// 1.定义类实现Runnable接口 2 public void run() { //2.重写run方法 3 for(int i=0;i<100;i++){ 4 System.out.println(Thread.currentThread().getName()+"run..."+i); 5 } 6 } 7 public class TreadDemo { 8 public static void main(String[] args) {//main函数也是一个线程 9 Demo d=new Demo(); 10 Thread t1=new Thread(d);//3.通过Thread类建立线程对象 4.将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数 11 Thread t2=new Thread(d); 12 t1.start();//5.调用Thread类的start方法开启线程并调用Runnable接口子类的run()方法 13 t2.start(); 14 for(int i=0;i<200;i++){ 15 System.out.println("main...."+i); 16 } 17 } 18 }
原文:https://www.cnblogs.com/ljl150/p/12203098.html