栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

第十七节 多线程

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

第十七节 多线程

多线程 1.进程 1.1 啥是进程!?
  1. 在学习多线程之前需要了解一个重要的概念,什么是进程!?

    举个栗子:关键词: 工厂 cpu --> 车间(进程)–> 工人(线程)
    假设一个工厂的能源有限,只能供一个车间工作,这个车间工作的时候,其他车间停止生产。一个车间里有很多工人,他们一起协同完成一个任务。车间里的空间是工人可以共享的,每个人都可以使用车间里的资源。有些资源空间很大,大到可以让固定数量的个工人进行使用,有些空间资源很小,小到只能让一个人使用,其他人得等到使用完,才能进入。
    - 空间小的资源怎么防止他人进入呢!?
    解释:一个防止他人进入的简单方法,就是门口加一把锁。先到的人锁上门,后到的人看到上锁,就在门口排队,等锁打开再进去。这就叫“互斥锁”(Mutual exclusion,缩写 Mutex),防止多个线程同时读写某一块内存区域。
    - 固定数量的工人怎么能确定数量呢!?
    解释 就是在门口挂n把钥匙。进去的人就取一把钥匙,出来时再把钥匙挂回原处。后到的人发现钥匙架空了,就知道必须在门口排队等着了。这种做法叫做“信号量”(Semaphore),用来保证多个线程不会互相冲突。

  2. 什么是进程!?:是操作系统进行资源分配和调度的一个独立单位,好比正在运行的程序,打开一个软件,就会在任务管理器中增加一个进程。它会占用对应的内存区域,由CPU进行执行与计算。

    参考:Windows 中任务管理器(查看相应程序的进程)。
    多进程,允许多个任务同时运行。
    多线程,允许单个任务分成不同的部分运行。
    注:它们之间有协调机制,确保二者不会冲突,又能保证资源共享。到底是依赖一个进程还是多个进程是由软件开发时决定的。

1.2. 进程的特点
  • 并发性:
    多个进程可以在单个处理器CPU上并发执行,多个进程之间不会互相影响。(例如:如google浏览器,在windows中可以看到有多个进程)

  • 动态性:
    进程与程序的区别在于,程序只是一个静态的指令集合,而进程是一个正在系统中活动的指令集合,程序加 入了时间的概念以后,称为进程,具有自己的生命周期和各种不同的状态,这些概念都是程序所不具备的.

  • 独立性:
    进程是系统中独立存在的实体,它可以拥有自己独立的资源,每个进程都拥有自己私有的地址空间,在没有经过进程本身允许的情况下,一个用户进程不可以直接访问其他进程的地址空间。

2.线程 2.1 啥是线程?
  1. 线程 是操作系统OS能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。

  2. 一个进程可以开启一个线程去完成任务,也可以开启多个。注意:但其中肯定有一条主线程,执行程序,并调度资源,进行具体操作。如:java中main()方法的入口。用户在次创建的可以称之为子线程。

  3. 多线程还需要基于 软件和硬件的支持,提升的性能处理。

    举个栗子: 关键词 人
    宏观上来说:人从出生就开始运行直到人生的尽头。在运行期间人体,需要在这个有限的资源中进行工作,如,呼吸,血液循环,思考,学习… 相当于 “多个线程工作”。

2.2 单线程与多线程之间区别
  1. 单线程,就好像上面工厂中的工人一样,是对代码具体执行的。当分配给这个线程一个任务时,只有当这个线程完成了任务,才会对下一个任务有所响应。 执行期间是对其他任务没有任何响应的。
  2. 咱们白话解释下区别:
  • 单线程,当执行任务的主线程按照执行流程执行任务,等一个动作完成后,才能执行下一个代码,主线程在等待期间,用户界面看起来就是“忙碌状态”。
  • 多线程, 将原本的任务分成了很多个子任务去执行。这样防止了“阻塞”,一个线程在焖米饭期间,cpu会分给另一个线程时间片,让去执行下一个任务。也就是说允许单个程序创建多个并行执行的线程来完成各自的任务,提高了程序的执行效率。
2.3 线程的2种调度机制

1.多线程特性,线程执行有随机性 cpu选择线程随机分配的。
首先要明确一件事,只有cpu发出指令的时候,线程得到cup授权,才能使用资源执行任务。但是注意: 多线程工作,其中并发运行( 指两个或多个事件在同一个时间段内发生),在宏观上看似同时执行,实际上是各个线程轮流获取 CPU 使用权才能执行指令。

  • cpu有自己的底层分配机制。java 默认抢占式的调度模型。
  1. 分时调度模型:是指让所有的线程轮流获得CPU的使用权,并且平均分配每个线程占用的CPU时间片(可以理解为cpu分给的线程的时间段)。但是,Java 中的线程调度不是分时的,同时启动多个线程后,并不能保证各个线程轮流获取均等的时间片。
  2. 抢占式调度模型:使优先级高的线程获取更多的运行机会来占用cpu更好的资源, 如果可运行池种的线程优先级相同,则随机选中一个线程去使用,当它失去了CPU的使用权后,再随机选择其他线程获取CPU试用权。Java使用的为CPU使用抢占式调度模式在多个线程间进行着高速的切换,让CPU的使用率更高。
3. 进程与线程关系

3.1 多线程并发和并行
  1. 并发和并行在多线程技术上是绕不开的词汇,首先,都利用多线程技术。其次,目的都是提高CPU的使用率。
  • 概念上区别:
    并行:强调一个处理器同时处理多个任务,并不是正在同时运行。(容易高并发)
    并发:强调多个处理器或者是多核的处理器同时处理多个不同的任务,同时运行多段代码。

  • 场景区别:
  1. io场景: 查询数据库,io的操作等,数量大单是不消耗cpu资源,可以使用并发技术。
  2. 计算场景: 大量的数据计算,加减乘除,数据加密等,需要大量占用cpu资源的,使用cpu并行技术。
4. 多线程的状态
  • 也可以当做多线程的生命周期。

线程生命周期,总共有五种状态:

  1. 新建状态(New):当线程对象对创建后,即进入了新建状态,如:Thread t = new MyThread();

  2. 就绪状态(Runnable):也被称为“可执行状态”,当调用线程对象的start()方法(t.start();),线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了t.start()此线程立即就会执行;

  3. 运行状态(Running):当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。
    注:就绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;

  4. 阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才有机会再次被CPU调用以进入到运行状态;

    根据阻塞产生的原因不同,阻塞状态又可以分为三种:
    4.1 等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态;
    4.2 同步阻塞 :线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;
    4.3 其他阻塞:通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。

  5. 死亡状态(Dead): 线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

5. 多线程创建方式一:继承Thread类 5.1 概述
  1. Thread类本质上是实现了Runnable接口的一个实例,代表一个线程的实例。
  2. 启动线程的唯一方法就是通过Thread类的 start() 实例方法,start()方法是一native方法,它将通知底层操作系统,.最终由操作系统启动一个新线程,操作系统将执行run()
  3. 不要用run()方法去启动线程 如果用run()会有什么效果!?
5.2 创建对象
	Thread() 
		分配新的Thread对象
	Thread(String name) 
			分配新的Thread对象
	Thread(Runnable target) 
			分配新的Thread对象
	Thread(Runnable target,String name) 
			分配新的Thread对象
5.3 常用方法
方法作用
static Thread currentThread( )返回对当前正在执行的线程对象的引用
long getId()返回该线程的标识
String getName()返回该线程的名称
void run()如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法
static void sleep(long millions)在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)
void start()使该线程开始执行:Java虚拟机调用该线程的run()
5.4 测试:多线程创建方式1
  • run() 方法启动和start()启动有什么区别!?
public class Test_Thread {
    public static void main(String[] args) {

        MyThread t = new MyThread();
        //t.run(); // 虽然调用run()也能启动但并不是真正的启动方法。
        
        t.start(); // 单个调用只是相当于单线程。
        
        MyThread t1 = new MyThread("张楚岚"); //通过含参构造设置名称
        MyThread t2 = new MyThread();
        t2.setName("宝儿姐");// 通过方法设置线程名称

        t1.start();
        t2.start();

    }
}

class MyThread extends Thread{
    // 空参构造方法,不用写默认存在
    public MyThread() {
        super();
    }

    //含参构造方法
    public MyThread(String name) {
        super(name);
    }

    //重写run()方法;
    @Override
    public void run() {
        //写一个循环20次的任务
        for (int i = 0; i <5 ; i++) {
            System.out.println(getName()+" : "+i);//执行线程+输出结果
        }
    }
}

输出结果: 注意不固定!

张楚岚 : 0
宝儿姐 : 0
Thread-0 : 0
宝儿姐 : 1
张楚岚 : 1
张楚岚 : 2
宝儿姐 : 2
Thread-0 : 1
Thread-0 : 2
Thread-0 : 3
Thread-0 : 4
宝儿姐 : 3
张楚岚 : 3
宝儿姐 : 4
张楚岚 : 4
6 多线程创建方式二: 实现Runnable接口 6.1 常用方法
方法作用
void run()当实现接口的对象 Runnable被用来创建一个线程,启动线程使对象的 run在独立执行的线程中调用的方法。
6.2 测试:多线程创建方法2
  • 创建多线程的方式有两种,哪种更好一些!?
public class Test_Runnable {
    public static void main(String[] args) {

        //1.创建对象
        MyRunnable myRunnable = new MyRunnable();
        //没有start()方法怎么启动!?看构造方法找实现类Thread
        Thread t = new Thread(myRunnable); //通过Thread含参数构造方法。
        t.start(); //通过Thread 嗲用start()  简写 new Thread(myRunnable).start();



        //模拟多线程
        Thread t1 = new Thread(myRunnable);
        Thread t2 = new Thread(myRunnable,"钢铁侠"); //通过构造方法设置线程名称
        t1.start();
        t2.start();
    }
}


class MyRunnable implements  Runnable{

    //重写run()方法
    @Override
    public void run() {
        //写一个循环的业务
        for (int i = 0; i <5 ; i++) {
            //线程名称通过谁来获取,Runnable只有一个run()方法!?拿到Thread即可通过Thread静态方法。
            System.out.println(Thread.currentThread().getName()+"="+i);

        }
    }
}

输出结果:结果不固定

Thread-0=0
钢铁侠=0
Thread-1=0
钢铁侠=1
Thread-0=1
钢铁侠=2
Thread-1=1
钢铁侠=3
Thread-0=2
钢铁侠=4
Thread-1=2
Thread-0=3
Thread-1=3
Thread-0=4
Thread-1=4
6.3 简述一下Thread和Runnable区别
  • 继承Thread类
    优点: 创建方式简单,可以直接new Thread(){重写run()},如果需要访问当前线程,getName()即可,直接获取线程名称。
    缺点: 只能单继承了,无法在继承其他类。

  • 实现Runnable接口 (推荐使用)
    优点: 自定义的线程类只是实现了Runnable接口,扩展性好,可以在继承其他类, 在这种方式下,多个线程可以共享同一个target对象, 所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码、还有数据分开(解耦),形成清晰的模型,较好地体现了面向对象的思想
    缺点: 编程稍微复杂,如想访问当前线程,则需使用Thread.currentThread().getName()方法

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1031310.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号