Java基础之多线程4月打卡day08

举报
java厂长 发表于 2022/04/08 22:08:53 2022/04/08
【摘要】 Java基础之多线程4月打卡day08关于作者作者介绍🍓 博客主页:作者主页🍓 简介:JAVA领域优质创作者🥇、一名在校大三学生🎓、在校期间参加各种省赛、国赛,斩获一系列荣誉🏆。🍓 关注我:关注我学习资料、文档下载统统都有,每日定时更新文章,励志做一名JAVA资深程序猿👨‍💻。1、多线程要使用多线程必须有一个前提,有一个线程的执行主类。从多线程开始,Java正式进入到应用部分...

Java基础之多线程4月打卡day08

关于作者

  • 作者介绍


🍓 博客主页:作者主页

🍓 简介:JAVA领域优质创作者🥇、一名在校大三学生🎓、在校期间参加各种省赛、国赛,斩获一系列荣誉🏆。

🍓 关注我:关注我学习资料、文档下载统统都有,每日定时更新文章,励志做一名JAVA资深程序猿👨‍💻。

1、多线程

要使用多线程必须有一个前提,有一个线程的执行主类。从多线程开始,Java正式进入到应用部分,而对于多线程的开发,从JavaEE上表现的并不是特别多,但是在Android开发之中使用较多,并且需要提醒的是,鄙视面试的过程之中,多线程所问道的问题是最多的。

1.1 多线程的基本概念

如果要想解释多线程,那么首先应该从单线程开始讲起,最早的DOS系统有一个最大的特征:一旦电脑出现了病毒,电脑会立刻死机,因为传统DOS系统属于单进程的处理方式,即:在同一个时间段上只能有一个程序执行,后来到了windows时代,电脑即使(非致命)存在了病毒,那么也可以正常使用,只是满了一些而已,因为windows属于多进程的处理操作,但是这个时候的资源依然只有一块,所以在同一时间段上会有多个程序共同执行,而在一个时间点上只能有一个程序在执行,多线程实在一个进程基础之上的进一步划分,因为进程的启动所消耗的时间是非常长的,所以在进程之上的进一步划分就变得非常重要,而且性能也会有所提高。

所有的线程一定要依附于进程才能够存在,那么进程一旦消失了,线程也一定会消失,但反过来不一定,而Java是为数不多的支持多线程的开发语言之一。

1.2 多线程的实现

在Java之中,如果要想实现多线程的程序,就必须依靠一个线程的主体类(叫好比主类的概念一样,表示的是一个线程的主类),但是这个线程的主体类在定义的时候也需要有一些特殊的要求,这个类可以继承Thread类或实现Runnable接口来完成定义。

image-20210816122306265

1.3 继承Thread类实现多线程

Java.lang.Thread是一个线程操作的核心类负责线程操作的类,任何类只需要继承了Thread类就可以成为一个线程的主类,但是既然是主类必须有它的使用方法,而线程启动的主方法是需要覆写Thread类中的run()方法才可以(相当于线程的主方法)。

package com.day12.demo;
class MyThread extends Thread{
    private String title;
    public MyThread(String title){
        this.title = title;
    }
    public void run(){
        for (int i = 0; i < 5; i++) {
            System.out.println(this.title + ",i = " + i);
        }
    }
}
public class ThreadDemo {
    @SuppressWarnings("unused")
    public static void main(String[] args) {
        MyThread thread1 = new MyThread("线程1");
        MyThread thread2 = new MyThread("线程2");
        MyThread thread3 = new MyThread("线程3");
        thread1.run();
        thread2.run();
        thread3.run();
    }
}

我们只是做了一个顺序打印操作,而和多线程没有关系。多线程的执行和进程是相似的,而是多个程序交替执行,而不是说各自执行各自的。正确启动多线程的方式应该是调用Thread类中的start()方法。

  • 启动多线程只有一个方法:public void start(); 调用此方法会调用run()

正确启动多线程

package com.day12.demo;
class MyThread extends Thread{
    private String title;
    public MyThread(String title){
        this.title = title;
    }
    public void run(){
        for (int i = 0; i < 5; i++) {
            System.out.println(this.title + ",i = " + i);
        }
    }
}
public class ThreadDemo {
    @SuppressWarnings("unused")
    public static void main(String[] args) {
        MyThread thread1 = new MyThread("线程1");
        MyThread thread2 = new MyThread("线程2");
        MyThread thread3 = new MyThread("线程3");
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

此时再次执行代码发现所有所有的线程对象变为交替执行。所以得出结论:要想启动线程必须依靠Thread类的start()方法执行,线程启动之后会默认调用了run()方法。

问题:为什么线程启动的时候必须调用start()而不是直接调用run()?

如果想要了解必须打开Java源代码进行浏览(在JDK按照目录下)

public synchronized void start() {
    /**
         * This method is not invoked for the main method thread or "system"
         * group threads created/set up by the VM. Any new functionality added
         * to this method in the future may have to also be added to the VM.
         *
         * A zero status value corresponds to state "NEW".
         */
    if (threadStatus != 0)
        //这个异常的产生只有在你重复启动线程的时候才会发生
        throw new IllegalThreadStateException();
​
    /* Notify the group that this thread is about to be started
         * so that it can be added to the group's list of threads
         * and the group's unstarted count can be decremented. */
    group.add(this);
​
    boolean started = false;
    try {
        //只声明未实现的方法,同时使用native关键字定义,native调用本机的原生系统函数
        start0();
        started = true;
    } finally {
        try {
            if (!started) {
                group.threadStartFailed(this);
            }
        } catch (Throwable ignore) {
            /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
        }
    }
}

image-20210816125455104

多线程的实现一定需要操作系统的支持,那么异常的start0()方法实际上就和抽象方法很类似没有方法体,而这个方法体交给JVM去实现,即:在windows下的JVM可能使用A方法实现了start0(),而在Linux下的JVM可能使用了B方法实现了start0(),凡是在调用的时候并不会去关心集体是何方式实现了start0()方法,只会关心最终的操作结果,交给JVM去匹配了不同的操作系统。

所以多线程操作之中,使用start()方法启动多线程的操作是需要进行操作系统函数调用的。

1.4 Runnable接口实现多线程

Thread类的核心功能就是进行线程的启动,但是如果一个类直接继承Threa类就会造成单继承的局限,在Java中又提供了有另外一种实现Runable接口。

public interface Runnable{
    public void run();
}

分享:如何区分新老接口?

在JDK之中个,由于其发展的时间较长,那么会出现一些新的接口和老的接口,这两者有一个最大的明显特征:所有最早提供的接口方法里面都不加上public,所有的新接口里面都有public。

通过Runnable接口实现多线程

package com.day12.demo;
class MyThread implements Runnable{
    private String title;
    public MyThread(String title){
        this.title = title;
    }
    public void run(){
        for (int i = 0; i < 5; i++) {
            System.out.println(this.title + ",i = " + i);
        }
    }
}

这个时候和之前的继承Thread类区别不大,但是唯一的好处就是避免了单继承局限,不过现在问题也就来了,刚刚解释过,如果要想启动多线程依靠Thread类的start()方法完成,之前继承Thread()类的时候可以将此方法直接继承过来使用,但现在实现的是Runnable接口,没有这个方法可以继承了,为了解决这个问题,还是需要依靠Thread类完成,在Thread类中定义一个构造方法:public Thread(Runnable target),接收Runnable接口对象。

利用Thread类启动

public class ThreadDemo {
    @SuppressWarnings("unused")
    public static void main(String[] args) {
        MyThread thread1 = new MyThread("线程1");
        MyThread thread2 = new MyThread("线程2");
        MyThread thread3 = new MyThread("线程3");
        new Thread(thread1).start();
        new Thread(thread2).start();
        new Thread(thread3).start();
    }
}

这个时候就实现了多线程的启动,而且没有了单继承局限。

1.5 Thread类和Runnable接口实现多线程的区别

现在Thread类Runnable接口都可以作为同一功能的方式来实现多线程,那么这两者如果从Java的十年开发而言,肯定使用Ruanable接口,因为可以有效的避免单继承的局限,那么除了这些之外,这两种方式是否还有其他联系呢?

为了解释这两种方式的联系,下面可以打开Thread类的定义:

Public class Thread Object implements Runnable

发现Thread类也是Runnable接口的子类,而如果真的是这样,那么之前程序的结构就变为了一下形式。所以说多线程非常类似于代理模式。

image-20210816131643041

这个时候表现出来的代码模式非常类似于代理设计模式,但是它不是严格意义上的代理设计模式,因为从严格意义上来讲代理设计模式之中,代理主体所能够使用的方法依然是接口中定义的run()方法,而此处代理主题调用的是start()方法,所以只能够说形式上类似于代理设计模式,但本质上还是有差别的。

但是除了以上的联系之外,对于Runnable和Thread类还有一个不太好区分的区别:使用Runnable接口可以更加方便的表示出数据共享的概念。

买票程序

package com.day12.demo;
class MyTicket extends Thread{
    private int ticket = 10;
    public void run(){
        for (int i = 0; i < 20; i++) {
            if(ticket > 0)
            System.out.println("买票 =" + this.ticket--);
        }
    }
}
public class TicketDemo {
    public static void main(String[] args) {
        new MyTicket().start();
        new MyTicket().start();
        new MyTicket().start();
    }
}

image-20210816132621510

运行后发现,数据没有共享。

package com.day12.demo;
class MyTicket extends Thread{
    private int ticket = 10;
    public void run(){
        for (int i = 0; i < 20; i++) {
            if(ticket > 0)
            System.out.println("买票 =" + this.ticket--);
        }
    }
}
public class TicketDemo {
    public static void main(String[] args) {
        MyTicket mt = new MyTicket();
        new Thread(mt).start();
        new Thread(mt).start();
        new Thread(mt).start();
        
    }
}

经过改进之后,发现数据进行共享,但是对于逻辑是解释是不好理解的,MyTicket类继承了Thread类,自己拥有了start()方法但是不执行自己的start()方法,而是通过匿名方法共用MyTicket()实例化对象mt调用匿名方法的start()方法。

再次经过改进之后

package com.day12.demo;
class MyTicket implements Runnable{
    private int ticket = 10;
    public void run(){
        for (int i = 0; i < 20; i++) {
            if(ticket > 0)
            System.out.println("买票 =" + this.ticket--);
        }
    }
}
public class TicketDemo {
    public static void main(String[] args) {
        MyTicket mt = new MyTicket();
        new Thread(mt).start();
        new Thread(mt).start();
        new Thread(mt).start();
        
    }
}

通过MyTicket类实现Runnable接口来进行改进,原因是因为Runnable接口里面只有一个自己的run()方法,而此处的start()的方法,是通过匿名类进行调用start()方法来实现线程的启动。

image-20210816135933824

面试题:请解释多线程的两种实现方式区别?分别编写程序验证两种实现。

多线程的两种实现方式都需要一个线程的主类,而这个类可以实现Runnable接口或继承Thread,不管使用何种方式都必须在子类之中覆写run()方法,此方法为线程的主方法;

Thread类是Runnable接口的子类,而且使用Runnable接口可以避免单继承局限,以及更加方便的实现数据共享的概念。

Runnable 接口:

class MyThread implements Runnable{
    private int ticket=5;
    public void run(){
        for(int x=0;x<50;x++)
            if(this.ticket>0){
                System.out.println(this.ticket--);
            }
    }
}
​
MyThread mt = new MyThread();
new Thread(mt).start();

Thread 类:

class MyThread extends Thread{
    private int ticket=5;
    public void run(){
        for(int x=0;x<50;x++)
            if(this.ticket>0){
                System.out.println(this.ticket--);
            }
    }
}
​
MyThread mt = new MyThread();
mt.start();

# 后语

厂长写博客目的初衷很简单,希望大家在学习的过程中少走弯路,多学一些东西,对自己有帮助的留下你的赞赞👍或者关注➕都是对我最大的支持,你的关注和点赞给厂长每天更文的动力。

对文章其中一部分不理解,都可以评论区回复我,我们来一起讨论,共同学习,一起进步!

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。