多线程的由来

我们在之前,学习的程序在没有跳转语句的前提下,都是由上至下依次执行,那现在想要设计一个程序,边打游戏边听歌,怎么设计?要解决上述问题,咱们得使用多进程或者多线程来解决.

多线程的好处:

  • 提高程序运行效率,让CPU的 使用率更高。
  • 多个线程之间互不影响

关于多线程的一些名词解释

  • 并发:指两个或多个事件在同一个时间段内发生。
  • 并行:指两个或多个事件在同一时刻发生(同时发生)。
  • 进程:是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程;进程也是程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序即是一个进程从创建、运行到消亡的过程。简而言之:进入内存中的程序就是进程。
  • 线程:线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。 简而言之:一个程序运行后至少有一个进程,一个进程中可以包含多个线程。我们可以理解为:应用程序到CPU的执行路径,CPU可以通过这个路径执行功能,这条路径就是线程。

线程调度

当系统只有一个CPU时,线程会以某种顺序执行多个线程,我们把这种情况称之为 线程调度。
  • 分时调度:所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。
  • 抢占式调度:优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为 抢占式调度。

抢占式调度详解

大部分操作系统都支持多进程并发运行,现在的操作系统几乎都支持同时运行多个程序。比如:现在我 们上课一边使用编辑器,一边使用录屏软件,同时还开着画图板,dos窗口等软件。此时,这些程序是 在同时运行,”感觉这些软件好像在同一时刻运行着“。 实际上,CPU(中央处理器)使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核而言,某个时刻,只能执行一个线程,而 CPU的在多个线程间切换速度相对我们的感觉要快,看上去就是 在同一时刻运行。 其实,多线程程序并不能提高程序的运行速度,但能够提高程序运行效率,让CPU的 使用率更高。

主线程

主线程:执行主(main方法)的线程。JVM执行main方法,main方法会进入到栈内存,JVM会找操作系统开辟一条main方法通向cpu的执行路径,cpu就可以通过这个路径来执行main方法, 而这个路径有一个名字,叫main(主)线程。

单线程程序:java程序中只有一个线程,执行从main方法开始,从上到下依次执行

创建多线程程序

创建多线程程序的第一种方式:创建Thread类的子类

Java使用 java.lang.Thread 类代表线程,所有的线程对象都必须是Thread类或其子类的实例。每个线程的作用是完成一定的任务,实际上就是执行一段程序流即一段顺序执行的代码。Java使用线程执行体来代表这段程序流。Java中通过继承Thread类来创建并启动多线程的步骤如下:
  1. 创建一个Thread类的子类
  2. 在Thread类的子类中重写Thread类中的run方法,设置线程任务(开启线程要做什么?)
  3. 创建Thread类的子类对象
  4. 调用Thread类中的方法start方法,开启新的线程,执行run方法。调用 start()方法使该线程开始执行,Java 虚拟机调用该线程的 run 方法。结果是两个线程并发地运行;当前线程(main线程)和另一个线程(创建的新线程,执行其 run 方法)。多次启动一个线程是非法的。特别是当线程已经结束执行后,不能再重新启动。java程序属于抢占式调度,那个线程的优先级高,那个线程优先执行;同一个优先级,随机选择一个执行

代码举例

自定义Thread的子类

package threadTest;

// 1.创建一个Thread类的子类
public class MyThread extends Thread {
    // 2.在Thread类的子类中重写Thread类中的run方法,设置线程任务(开启线程要做什么?)
    @Override
    public void run() {
        //打印三次小强
        for (int i = 0; i < 3; i++) {
            System.out.println("小强" + i);
        }
    }
}

定义测试类

package threadTest;

public class MyThreadTest {
    public static void main(String[] args) {
        //3.创建Thread类的子类对象
        MyThread mt = new MyThread();
        // 4.调用Thread类中的方法start方法,开启新的线程,执行run方法
        mt.start();
        //主线程里for循环打印三次旺财
        for (int i = 0; i < 3; i++) {
            System.out.println("旺财" + i);
        }
    }
}

代码执行后的结果(每次执行结果不一样,因为Java属于抢占式调度)

多线程的原理

分析上面代码,流程图如下所示:

程序启动运行main时候,java虚拟机启动一个进程,主线程main在main()调用时候被创建。随着调用mt的对象的start方法,另外一个新的线程也启动了,这样,整个应用就在多线程下运行。通过这张图我们可以很清晰的看到多线程的执行流程,那么为什么可以完成并发执行呢?我们再来讲一讲原理。多线程执行时,到底在内存中是如何运行的呢?以上个程序为例,进行图解说明:多线程执行时,在栈内存中,其实每一个执行线程都有一片自己所属的栈内存空间。进行方法的压栈和弹栈。
 

当执行线程的任务结束了,线程自动在栈内存中释放了。但是当所有的执行线程都结束了,那么进程就结束了。

多线程程序为什么会产生随机性打印结果?

  • 对于CPU而言,多线程程序有多条执行路径,cpu可以选择执行任意一条路径,我们无法控制CPU的运行,(多个线程一起抢夺CPU的执行权,谁抢夺到了执行谁的)所以有了随机性打印结果。

Thread类

API中该类中定义了有关线程的一些方法,具体如下:
构造方法:
  • public Thread() :分配一个新的线程对象。
  • public Thread(String name) :分配一个指定名字的新的线程对象。
  • public Thread(Runnable target) :分配一个带有指定目标新的线程对象。
  • public Thread(Runnable target,String name) :分配一个带有指定目标新的线程对象并指定名字。
常用方法:
  • public String getName() :获取当前线程名称。
  • public void start() :导致此线程开始执行; Java虚拟机调用此线程的run方法
  • public void run() :此线程要执行的任务在此处定义代码。
  • public static void sleep(long millis) :使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。
  • public static Thread currentThread() :返回对当前正在执行的线程对象的引用。

如何获取/设置线程的名称

获取线程的名称:

  • 方式1:使用Thread类中的方法String getName() 返回该线程的名称。
  • 方式2:可以先使用方法static Thread currentThread() 获取到当前正在执行的线程,在使用线程中的方法getName()获取线程的名称

设置线程的名称:

  • 方式1:使用Thread类中的方法void setName(String name) 改变线程名称,使之与参数 name 相同。
  • 方式2:创建一个带参数的构造方法,参数传递线程的名称;调用父类的带参构造方法,把线程名称传递给父类,让父类(Thread)给子线程起一个名字Thread(String name) 分配新的 Thread 对象。

代码举例

package demo01GetThreadName;

public class Demo01Thread extends Thread {
    public Demo01Thread() {
    }

    public Demo01Thread(String name) {
        super(name);//把线程名称传递给父类,让父类(Thread)给子线程起一个名字
    }

    @Override
    public void run() {
        //1:使用Thread类中的方法String getName() 返回该线程的名称。
        System.out.println("当前线程的名字是:" + getName());
        /*  2.可以先使用方法,static Thread currentThread() 获取到当前正在执行的线程,
        在使用线程中的方法getName()获取线程的名称
        */
        System.out.println("当前线程的名字是:" + Thread.currentThread().getName());

    }
}

定义测试类

package demo01GetThreadName;

public class Demo01ThreadTest {
    public static void main(String[] args) {
        Demo01Thread thread = new Demo01Thread("小学");
        thread.start();
        //获取主线程的名字
        System.out.println("主线程的名字是:" + Thread.currentThread().getName());
        //重新给主线程设置名称,使用Thread类中的方法void setName(String name) 改变线程名称,使之与参数 name 相同。
        Thread.currentThread().setName("小青");
        //获取主线程的名字
        System.out.println("主线程的名字是:" + Thread.currentThread().getName());
    }
}

代码执行后的结果

创建多线程程序的第二种方式:实现Runnable接口

采用 java.lang.Runnable 也是非常常见的一种,我们只需要重写run方法即可。通过实现Runnable接口,使得该类有了多线程类的特征。run()方法是多线程程序的一个执行目标。所有的多线程代码都在run方法里面。Thread类实际上也是实现了Runnable接口的类。在启动的多线程的时候,需要先通过Thread类的构造方法Thread(Runnable target) 构造出对象,然后调用Thread对象的start()方法来运行多线程代码。实际上所有的多线程代码都是通过运行Thread的start()方法来运行的。因此,不管是继承Thread类还是实现Runnabl接口来实现多线程,最终还是通过Thread的对象的API来控制线程的,熟悉Thread类的API是进行多线程编程的基础。Runnable对象仅仅作为Thread对象的target,Runnable实现类里包含的run()方法仅作为线程执行体。而实际的线程对象依然是Thread实例,只是该Thread线程负责执行其target的run()方法。

实现步骤:

  1. 创建一个Runnable接口的实现类
  2. 在实现类中重写Runnable接口的run方法,设置线程任务
  3. 创建一个Runnable接口的实现类对象
  4. 创建Thread类对象,构造方法中传递Runnable接口的实现类对象
  5. 调用Thread类中的start方法,开启新的线程执行run方法

代码举例

定义Runnable接口的实现类

package demo02Runnable;

    //1:创建一个Runnable接口的实现类
public class Demo01Runnable implements Runnable {
    //2:在实现类中重写Runnable接口的run方法,设置线程任务
    @Override
    public void run() {
        for (int i = 0; i < 3; i++) {
            //线程任务输出当前线程的名字3次
            System.out.println("当前线程的名字" + Thread.currentThread().getName() + i);
        }

    }
}

定义测试类

package demo02Runnable;

public class Demo01RunnableTest {
    public static void main(String[] args) {
        //3创建一个Runnable接口的实现类对象
        Demo01Runnable runnable = new Demo01Runnable();
        //4 创建Thread类对象,构造方法中传递Runnable接口的实现类对象
        Thread thread = new Thread(runnable);
        //5 调用Thread类中的start方法,开启新的线程执行run方法
        thread.start();
        //输出3次主线程的名字
        for (int i = 0; i < 3; i++) {
            System.out.println("主线程的名字是:" + Thread.currentThread().getName() + i);
        }
    }
}

代码执行后的结果

Thread和Runnable的区别

如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。
总结:
  • 实现Runnable接口比继承Thread类所具有的优势:
  • 适合多个相同的程序代码的线程去共享同一个资源。
  • 可以避免java中的单继承的局限性。
  • 增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立。
  • 线程池只能放入实现Runable或Callable类线程,不能直接放入继承Thread的类。
  • 在java中,每次程序运行至少启动2个线程。一个是main线程,一个是垃圾收集线程。因为每当使用java命令执行一个类的时候,实际上都会启动一个JVM,每一个JVM其实在就是在操作系统中启动了一个进程。

匿名内部类方式实现线程的创建

使用线程的内匿名内部类方式,可以方便的实现每个线程执行不同的线程任务操作。 
package demo02Runnable;

/*
    匿名内部类方式实现线程的创建

    匿名:没有名字
    内部类:写在其他类内部的类

    匿名内部类作用:简化代码
        把子类继承父类,重写父类的方法,创建子类对象合一步完成
        把实现类实现类接口,重写接口中的方法,创建实现类对象合成一步完成
    匿名内部类的最终产物:子类/实现类对象,而这个类没有名字

    格式:
        new 父类/接口(){
            重复父类/接口中的方法
        };
 */
public class Demo01InnerClassThread {
    public static void main(String[] args) {
        //简化接口的方式
        new Thread(new Runnable() {
            //重写run方法,设置线程任务
            @Override
            public void run() {
                //设置线程任务
                for (int i = 0; i < 3; i++) {
                    System.out.println(Thread.currentThread().getName() + "-->" + "java");
                }
            }
        }).start();
    }
}

Java之多线程创建方式的更多相关文章

  1. 二、java实现多线程的方式?

    一.同步?异步? 下面两幅图解释了同步异步. 二.实现多线程的方式 1.继承Thread package threaddemo; class CreateThreadDemo extends Thre ...

  2. Java线程的创建方式三:Callable(四)

    一.Java实现多线程的三种方式 方式一:继承Thread类: public class Test extends Thread { public static void main(String[] ...

  3. Java的多线程创建方法

    1. 直接使用Thread来创建 package com.test.tt; public class ThreadEx extends Thread{ private int j; public vo ...

  4. JAVA\Android 多线程实现方式及并发与同步

    转载:https://blog.csdn.net/csdn_aiyang/article/details/65442540 概述     说到线程,就不得不先说线程和进程的关系,这里先简单解释一下,进 ...

  5. 详解三种java实现多线程的方式

    java中实现多线程的方法有两种:继承Thread类和实现runnable接口. 1.继承Thread类,重写父类run()方法 ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 ...

  6. Java基础--线程创建方式

    线程的创建主要有两种形式,通过继承Thread或者实现Runnable接口,本质上没有太大区别. /** * @date: 2019/7/16 **/ public class ThreadOne i ...

  7. java和javaScript创建方式

    Java创建对象的几种方式:http://blog.csdn.net/u013230804/article/details/25828339 javascript三种创建对象的方式:https://w ...

  8. C++多线程の8*2重多线程创建方式

  9. Java多线程引例及实现多线程的方式

    多线程(英语:multithreading),是指从软件或者硬件上实现多个线程并发执行的技术. Java多线程是由JVM来实现,不必关心操作系统的调用问题. 假如我们要实现如下功能: public c ...

随机推荐

  1. Dubbo一致性哈希负载均衡的源码和Bug,了解一下?

    本文是对于Dubbo负载均衡策略之一的一致性哈希负载均衡的详细分析.对源码逐行解读.根据实际运行结果,配以丰富的图片,可能是东半球讲一致性哈希算法在Dubbo中的实现最详细的文章了. 文中所示源码,没 ...

  2. Python3 函数基础1

    目录 定义函数 定义函数的三种形式 空函数 有参函数 无参函数 函数的调用 函数的返回值 函数的参数 形参 (parameter) 实参(argument) 位置形参与位置实参 默认形参 关键字实参 ...

  3. 【CuteJavaScript】Angular6入门项目(3.编写服务和引入RxJS)

    本文目录 一.项目起步 二.编写路由组件 三.编写页面组件 1.编写单一组件 2.模拟数据 3.编写主从组件 四.编写服务 1.为什么需要服务 2.编写服务 五.引入RxJS 1.关于RxJS 2.引 ...

  4. 【JS】306- 深入理解 call,apply 和 bind

    作者:一像素 链接:https://www.cnblogs.com/onepixel/p/6034307.html 在JavaScript 中,call.apply 和 bind 是 Function ...

  5. LRU算法与增强

    概要本文的想法来自于本人学习MySQL时的一个知识点:MySQL Innodb引擎中对缓冲区的处理.虽然没有仔细研究其源码实现,但其设计仍然启发了我. 本文针对LRU存在的问题,思考一种增强算法来避免 ...

  6. [Python]实现字符串倒序的三种方法

    a=" 1: print(a[::-1]) 2: b=list(a) b.reverse() print(''.join(b)) 3: c=len(a)-1 str_1=[] while(c ...

  7. java基础-控制流程语句

    一 前言 周末睡觉好舒服,都不想动了,就想睡睡,晒晒太阳,作者劳碌命还是过来写文章了.基础系列文章已经已经出到控制流程,感觉也挺快的,我很自信全网没都多少系列文章能有我这基础系列写的这么好,易于初学者 ...

  8. 起言-----UE4学习方法

    1.bilibili 2.官网教程 3.我觉得以上两个就够了 官方文档链接 https://docs.unrealengine.com/ 官网在线视频链接 https://learn.unrealen ...

  9. 【CV现状-3.2】纹理与材质

    #磨染的初心--计算机视觉的现状 [这一系列文章是关于计算机视觉的反思,希望能引起一些人的共鸣.可以随意传播,随意喷.所涉及的内容过多,将按如下内容划分章节.已经完成的会逐渐加上链接.] 缘起 三维感 ...

  10. WinForm WebBrowser 设置cookie

    [DllImport("wininet.dll", CharSet = CharSet.Auto, SetLastError = true)] public static exte ...