多线程学习笔记(一)之线程创建与线程状态

待我称王封你为后i 2022-06-17 07:29 460阅读 0赞

基本概念

进程:正在进行中的程序
线程:就是进程中一个负责程序执行的控制单元(执行路径)
一个进程中可以有多个执行路径,称之为多线程
一个进程至少要有一个线程
开启多个线程是为了同时运行多部分代码
每一个线程都有自己运行的内容,这个内容可以称为线程要执行的任务。
(之前也概述过多线程相关内容,见http://blog.csdn.net/megustas_jjc/article/details/52205145)
多线程好处:解决了多部分同时运行的问题
弊端:线程太多会让效率降低
其实应用程序的执行都是cpu在做着快速的切换完成的,这个切换是随机的。

JVM启动时就启动了多个线程,至少有两个线程可以分析出来:
1、执行main函数的线程(该线程的代码都定义在main函数中)
2、负责垃圾回收的线程(代码定义在垃圾回收器中(底层))

  1. class Demo extends Object{
  2. public void finalize(){
  3. System.out.println("demo ok");
  4. }
  5. }
  6. public class ThreadDemo {
  7. public static void main(String[] args) {
  8. new Demo();
  9. new Demo();
  10. new Demo();
  11. System.gc();//运行垃圾回收器,告诉垃圾回收器可以回收垃圾了,但是何时运行不一定,垃圾回收器是守护线程
  12. System.out.println("Main Thread");//主线程
  13. }
  14. }

这里写图片描述

为什么使用多线程的一个demo

我们先看如下的一个例子:

  1. class Demo extends Object{
  2. private String name;
  3. Demo(String name){
  4. this.name = name;
  5. }
  6. public void show(){
  7. for (int i=0;i<99999999;i++){
  8. System.out.println(name+"...i="+i);
  9. }
  10. }
  11. }
  12. public class ThreadDemo {
  13. public static void main(String[] args) {
  14. Demo d1 = new Demo("小李");
  15. Demo d2 = new Demo("xiaoqiang");
  16. d1.show();
  17. d2.show();
  18. }
  19. }

程序在运行时,栈内存的状态是,先main()函数进栈,之后d1.show()方法进栈,执行结束之后d1.show()出栈,d2.show()方法进栈执行,执行结束出栈,之后main()出栈,因此d2.show()方法在d1.show()方法之后执行,但是此处等待d1.show()方法执行结束需要等待很久,因此我们可以为d2.show()方法再创建一个线程进行执行,这样cpu就在两个方法之间切换执行了,而不必等待d1.show()方法执行结束才开始。

多线程的创建方式—继承Thread类

创建线程方式一:继承Thread类
步骤:
1、定义一个类继承Thread类
2、覆盖Thread类中的run方法
3、创建线程对象,即直接创建Thread类的子类对象
4、调用start方法开启线程,并调用线程的任务run方法并执行

  1. class Demo extends Thread{
  2. private String name;
  3. Demo(String name){
  4. this.name = name;
  5. }
  6. public void run(){
  7. show();
  8. }
  9. public void show(){
  10. for (int i=0;i<9;i++){
  11. System.out.println(name+"...i="+i);
  12. }
  13. }
  14. }
  15. public class ThreadDemo {
  16. public static void main(String[] args) {
  17. /*
  18. 直接创建一个线程对象,创建线程的目的是开启一条执行路径去运行指定代码和其他代码实现同时运行
  19. jvm创建的主线程的任务都定义在主函数中,而自定义的线程它的任务在哪?
  20. Thread类用于描述线程,自定义线程的任务通过Thread类中的run方法来实现,就是说run方法就是封装自定义线程运行任务的函数
  21. run方法中定义的就是线程要运行的任务代码,
  22. 开启线程是为了运行指定代码,因此需要继承Thread并覆写run方法,将运行的代码定义在run方法中即可,而不是 Thread t1 = new Thread();
  23. */
  24. Demo d1 = new Demo("小李");
  25. Demo d2 = new Demo("xiaoqiang");
  26. d1.run();
  27. d2.run();
  28. }
  29. }

注意此时认识按照顺序执行,因为调用run()方法与常规调用方法一样,要通过start()方法来开启线程

start()方法的作用:使线程开始执行,Java虚拟机调用该线程的run方法

  1. class Demo extends Thread{
  2. private String name;
  3. Demo(String name){
  4. this.name = name;
  5. }
  6. public void run(){
  7. show();
  8. }
  9. public void show(){
  10. for (int i=0;i<9;i++){
  11. System.out.println(name+"...i="+i);
  12. }
  13. }
  14. }
  15. public class ThreadDemo {
  16. public static void main(String[] args) {
  17. Demo d1 = new Demo("小李");
  18. Demo d2 = new Demo("xiaoqiang");
  19. d1.start();
  20. d2.start();
  21. System.out.println("over"+);
  22. }
  23. }

获取线程名字

实际上是三个线程:主线程,线程d1,线程d2
可以通过Thread类中的getName()方法来获取线程对象的名称,线程创建名称即完成,因此无论是否开启,都是不同名字,例如:

  1. System.out.println(name+"...i="+i+"...name"+getName());

在d1.start();d2.start();与d1.run(); d2.run();的情况下,打印相同名字,都是Thread0与Thread1.
why:
当我们通过new方法创建Thread对象时,实际就已经完成了Thread的命名,通过查看Thread类的构造函数的源码可知:

  1. public Thread(){
  2. init(null,null,"Thread-"+nextThreadNum(),0);
  3. }

currentThread():返回当前正在执行的线程对象的引用

  1. System.out.println(name+"...i="+i+"...name"+Thread.currentThread().getName());

start方法与run方法区别

对于调用start()方法结果:

这里写图片描述

对于调用run()方法结果:

这里写图片描述

start方法与run方法区别
start()方法来启动一个线程,此刻线程处于就绪状态,而非运行状态,意味着这个线程可以被JVM来调度执行。在调度过程中,JVM通过调用线程类的run()方法来完成实际的操作,run()方法结束后,线程终止。

start()方法能够异步的调用run()方法,但是直接调用run()方法是同步的,因此无法达到多线程的目的。

自定义线程名字:调用Thread的带参构造方法Thread(String name)即可,我们可以对Demo类的构造函数做如下修改:

  1. Demo(String name){
  2. super(name);
  3. }

主函数在执行过程中又为创建的新的线程开启新的路径,即都有着自己独立的空间,在哪个线程中运行的,就在哪个线程对应的空间进行进栈出栈,当一个线程执行完,其空间便释放

这里写图片描述

cpu在三者之间切换执行

main()中进行如下修改:

  1. public static void main(String[] args) {
  2. Demo d1 = new Demo("小李");
  3. Demo d2 = new Demo("xiaoqiang");
  4. d1.start();
  5. d2.start();
  6. //抛出异常
  7. System.out.println(4/0);
  8. System.out.println("over"+Thread.currentThread().getName());
  9. }

这里写图片描述

主线程出现了异常,但是不影响另外两个线程的运行,因为main线程中出现异常,则main线程结束,释放空间,并不影响其他线程。

线程的状态

这里写图片描述
(上图不包含阻塞状态)
cpu的执行资格:可以被cpu处理,在处理队列中排队
cpu的执行权:正在被cpu处理
(运行状态的线程具有执行权与执行资格)
(冻结:释放执行权且释放执行资格,“时间一到”便又重新获得)

cpu在某一时刻只能处理一个线程,假设4个线程甲乙丙丁,当执行甲时,乙丙丁处理阻塞状态(具备执行资格,但是不具备执行权,在等待执行权)

这里写图片描述

创建线程——实现Runnable接口

Runnable接口应该由那些打算通过某一线程执行其实例的类来实现,类必须定义一个称为run的无参数方法。

创建线程方式二:实现Runnable接口
1、实现Runnable接口
2、覆盖接口中的run方法,将线程任务代码封装到run方法中
3、通过Thread类创建线程对象,并将Runnable接口的子类对象作为Thread类的构造函数的参数进行传递
因为线程的任务都封装在Runnable接口子类对象的run方法中,所以要在线程对象创建时就必须明确要运行的任务(不然执行默认的run)
4、调用线程对象的start方法开启线程

  1. class Demo implements Runnable{
  2. //实现Runnable接口,覆盖run方法
  3. public void run(){
  4. show();
  5. }
  6. public void show(){
  7. for (int i=0;i<9;i++){
  8. System.out.println("i="+i+"...name="+Thread.currentThread().getName());
  9. }
  10. }
  11. }
  12. public class ThreadDemo {
  13. public static void main(String[] args) {
  14. Demo d = new Demo();
  15. //Thread的构造函数:public Thread(Runnable target),分配新的Thread对象
  16. Thread t1 = new Thread(d);
  17. Thread t2 = new Thread(d);
  18. t1.start();
  19. t2.start();
  20. System.out.println("over...name="+Thread.currentThread().getName());
  21. }
  22. }

这里写图片描述

两种创建方法的对比

创建形式的本质不同可以通过Thread类的源码得知:

  1. class Thread{
  2. private Runnable r;
  3. Thread(){
  4. }
  5. Thread(Runnable r){
  6. this.r = r;
  7. }
  8. //直接继承Thread创建子类时,run方法直接被覆盖
  9. public void run(){
  10. if (r!=null)
  11. r.run();
  12. }
  13. public void start(){
  14. run();
  15. }
  16. }

(Thread类本质上也是实现了Runnable接口的一个实例,它代表一个线程的实例,并且启动线程的唯一方法就是通过Thread类的start()方法)
其实不管是通过继承Thread类还是通过使用Runnable接口来实现多线程的方法,最终都是通过Thread对象的API来控制线程的。

实现Runnable接口的好处:

  • 将线程的任务从线程的子类中分离出来,进行了单独的封装
  • 避免了Java单继承的局限性

所以往往Runnable方式比较常用。

发表评论

表情:
评论列表 (有 0 条评论,460人围观)

还没有评论,来说两句吧...

相关阅读

    相关 线学习线创建终止

    什么是线程 现代操作系统调度的最小单元是线程,也叫轻量级进程,在一个进程里可以创建多个线程,这些线程都拥有各自的计数器、堆栈和局部变量等属性,并且能够访问共享的内存变量。

    相关 线——线状态

    线程状态 线程是相对独立的、可调度的执行单元,因为在线程的执行过程中,会分别处在不同的状态。通常而言,线程主要有下列几种运行状态: 1.就绪状态: 即线程已经具备了