详解 线程

在讲解线程之前,本人要先来讲解下进程
因为线程是依赖于进程存在的

那么,什么是进程呢?

进程就是正在运行的程序
是系统进行资源分配和调用独立单位
每一个进程都有它自己的内存空间系统资源

可能通过上述的讲解,同学们有这种疑惑:
线程是依赖进程存在的,那么,进程完全可以处理线程能做到的事,为什么还要存在线程呢?
答曰:

对于单核计算机来讲,同一时刻,CPU只能运行一个线程/进程。 但是 进程是只能一个进程运行完才运行下一个进程,
而多个线程可以同时竞争CPU,也就是我们常说的“多线程” 那么,就拿游戏来举个例:游戏进程 和 音乐进程 就会冲突。
这时,多线程就体现出了它的作用: 多线程使得 游戏进程和音乐进程 间做着频繁切换,且切换速度很快。
所以,我们感觉游戏和音乐在同时进行,其实并不是同时执行的



线程

====

基本概念:

首先,本人来介绍下什么是线程

概述

在一个进程内部又可以执行多个任务,而这每一个任务我们就可以看成是一个线程。
线程 是 程序使用CPU的基本单位
所以,进程拥有资源的基本单位线程CPU调度的基本单位


在上文中,本人提到了多线程这个名词。
现在,本人来总结下多线程的意义

多线程的意义

多线程的作用不是提高执行速度,而是为了提高应用程序的使用率

那么我们怎么来理解这个问题呢?
解释

我们程序在运行的使用,都是在抢CPU的时间片(执行权)
如果是多线程的程序,那么在抢到CPU的执行权的概率应该比较单线程程序抢到的概率要大
那么也就是说:
CPU在 多线程程序 中 执行的时间 要比 单线程 多
所以就提高了程序的使用率
但是即使是多线程程序,那么他们中的哪个线程能抢占到CPU的资源呢?
这个是不确定的,所以多线程具有随机性


现在,本人来介绍下 线程的五种状态

线程的五种状态:

  1. 创建态
    用new运算符和Thread类或其子类建立一个线程对象后,该线程对象就处于创建态。处于创建态的线程有自己的内存空间,通过调用start()方法进入就绪态。

  2. 就绪态
    处于就绪态的线程已经具备了运行条件,但是还没有分配到CPU,因而将进入线程就绪队列,等待系统为其分配CPU。一旦获得CPU,线程就进入运行状态并自动调用自己的run()方法。

  3. 运行态
    在运行态的线程执行自己的run()方法中的代码,直到调用其它方法而终止,或等待某资源而阻塞,或完成任务而销毁。

  4. 阻塞态
    处于运行状态的线程在某些情况下,如执行了sleep()方法,或等待I/O设备等资源,让出CPU并暂时终止自己的运行,进入阻塞态。
    在阻塞状态的线程不能进入就绪队列。只有当引起阻塞的原因消除时,如睡眠时间已到,或等待的I/O设备空闲下来,线程便转入就绪态,重新到就绪队列中排队等待CPU。当再次获得CPU时,便从原来中止位置开始继续运行。

  5. 销毁态:
    销毁态是线程生命周期中的最后一个阶段。线程销毁的原因有两个:

    1. 是正常运行的线程完成了它的全部工作;
    2. 是线程被强行终止运行,如通过stop()或destroy()方法来终止一个线程。

下图是这五种状态的转换关系
在这里插入图片描述那么,对于上图,本人来做以下几点说明

  • 进程创建后并不是立刻进入运行态,而实现进入就绪态,在就绪态的进程队列中,与其他进程一起竞争CPU
  • 只有处于就绪态的进程才有资格竞争CPU
  • 就绪态的进程,除了CPU以外,不需要等待其他计算机资源
  • 处于阻塞态的进程,只能由处于运行态的进程唤醒
  • 阻塞态进程被唤醒后进入就绪态,与就绪态队列内的进程一起竞争CPU
  • 一般地,有几个CPU,就能同时运行几个进程
  • 进程是在运行态时,将自己阻塞起来,使自己进入阻塞态的

那么,现在,本人再来介绍两个名词 —— 并行并发

并行 与 并发:

并行

指应用能够 同时执行不同的任务
在这里插入图片描述
例:吃饭的时候,边吃饭边打电话

并发

指应用能够交替执行不同的任务
其实并发有点类似于多线程的原理(多线程并非是如果你开两个线程同时执行多个任务)
在这里插入图片描述
例:吃饭的时候,吃一口饭喝一口水

那么,现在,本人再来扩展一个知识点:

Java程序运行原理
Java命令会启动java虚拟机,
启动JVM,等于启动了一个应用程序,也就是启动了一个进程。
该进程会自动启动一个 “主线程” ,然后主线程去调用该类的 main 方法
所以:main方法运行在主线程中
并且,JVM启动至少启动了垃圾回收线程主线程,所以是多线程的。

在本人上面的讲解中,本人已经提到了:
线程只有得到 CPU时间片,也就是使用权,才可以执行指令。
那么Java是如何对线程进行调用的呢?

线程有两种调度模型:

  • 分时调度模型
    所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片
  • 抢占式调度模型
    优先让优先级高的线程使用 CPU,如果线程的优先级相同,
    那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些。

Java使用的是抢占式调度模型


现在,本人来介绍下 多线程程序实现的方式

多线程程序实现的方式:

方式1 —— 继承Thread类:

本人先来展示下这种实现方式:

首先本人给出一个 继承了Thread类 的自定义线程类

package edu.youzg.about_thread.core;

public class MyThread extends Thread {

    @Override
    public void run() {
        for (int i = 0; i < 4; i++) {
            //获取线程的名称
            Thread thread = Thread.currentThread();
            String name = thread.getName();
            System.out.println(name + ":" + i);
        }
    }

}

现在,本人来给出一个测试类

package edu.youzg.about_thread.core;

public class Test {
    
    public static void main(String[] args) {
        new MyThread().start();
    }
    
}

现在,本人来展示下运行结果
在这里插入图片描述
那么,可能同学们对上面的展示有如下疑问

  1. 启动线程使用的是那个方法?

答曰:start()方法

  1. run()和start()方法的区别 是什么?

答曰:
start()方法,使该线程开始执行;
然后Java 虚拟机运行run()方法 里的代码。、

  1. 为什么要重写run方法?

答曰:
run()方法中封装应该是必须被线程执行的代码

  1. 同一个线程能不能多次启动?

答曰:
不能。
会报 IllegalThreadStateException


那么,现在,本人来介绍下第二种实现方式:

方式2 —— 实现Runnable接口:

本人先来展示下这种实现方式:

首先,本人来给出一个 实现了 Runnable 接口的类

package edu.youzg.about_thread.core;

public class MyRunnable implements Runnable{
 
    @Override
    public void run() {
        for (int i = 0; i < 4; i++) {
            //获取线程的名称
            Thread thread = Thread.currentThread();
            String name = thread.getName();
            System.out.println(name + "线程的第" + i + "次输出");
        }
    }

}

现在,本人来给出一个测试类

package edu.youzg.about_thread.core;

public class Test {

    public static void main(String[] args) {
        new MyRunnable().run();
    }

}

现在,本人来展示下运行结果
在这里插入图片描述

优势

这种方式扩展性强 实现一个接口 还可以再去继承其他类
可以避免由于Java单继承带来的局限性


那么,现在,本人来介绍下第三种实现方式:

方式3 —— 实现 Callable 接口:

首先,本人来给出一个 实现了 Callable 接口的类

package edu.youzg.about_thread.core;

import java.util.concurrent.Callable;

public class MyCallable implements Callable<Integer>  {

    @Override
    public Integer call() throws Exception {
        return 666;
    }

}

现在,本人来给出一个测试类

package edu.youzg.about_thread.core;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class Test {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        MyCallable myCallable = new MyCallable();
        FutureTask<Integer> task = new FutureTask<>(myCallable);
        Thread thread = new Thread(task);
        thread.start();
        //线程执行完之后,可以获取结果
        Integer integer = task.get();
        System.out.println(integer);
    }

}

现在,本人来展示下运行结果
在这里插入图片描述
本人现在来讲解下 第三种实现方式的条件

条件

  1. 调用get()方法,需要抛出 ExecutionException, InterruptedException 异常;
  2. 执行 Callable 方式,需要 FutureTask 实现类的支持,用于接收运算结果。

其实第三种实现方式,是第二种实现方式的变形,具体理由如下:

Callable接口 是 Runnable接口 的 子接口
实现了该接口,相当于变相地实现了 Runnable接口

现在本人来讲解下这种实现方式的优势

优势

  1. 这种方式扩展性强 实现一个接口 还可以再去继承其他类,可以避免由于Java单继承带来的局限性
  2. 实现线程的同时,可以有返回值

现在,本人来介绍下 我们处理多线程问题 时所要调用的方法:

常用方法:

Thread类基本获取和设置方法

  • public final String getName():
    获取线程名称
  • public final void setName(String name):
    设置线程名称

展示

package edu.youzg.about_thread.core;

public class Test {

    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        System.out.println(myThread.getName());
        myThread.setName("右转哥的展示线程");
        System.out.println(myThread.getName());
    }

}

运行结果
在这里插入图片描述


设置和获取线程优先级

  • public final int getPriority():
    获取线程的优先级
  • public final void setPriority(int newPriority):
    设置线程的优先级

展示

package edu.youzg.about_thread.core;

public class Test {

    public static void main(String[] args) {
        MyThread myThread1 = new MyThread();
        MyThread myThread2 = new MyThread();
        myThread1.setName("1号嘉宾");
        myThread2.setName("2号嘉宾");
        Thread.currentThread().setName("男主角");
        myThread1.setPriority(Thread.MAX_PRIORITY);
        myThread2.setPriority(Thread.MIN_PRIORITY);
        Thread.currentThread().setPriority(Thread.NORM_PRIORITY);
        System.out.println(myThread1.getName()+ "优先级为:" + myThread1.getPriority());
        System.out.println(myThread2.getName() + "优先级为:" + myThread2.getPriority());
        System.out.println(Thread.currentThread().getName() + "优先级为:" + Thread.currentThread().getPriority());
        myThread1.start();
        myThread2.start();
        System.out.println("男主角的代码1");
        System.out.println("男主角的代码2");
        System.out.println("男主角的代码3");
    }

}

运行结果
在这里插入图片描述

现在,本人来对这两个方法可能会出现的问题做下讲解:

  1. 有的时候我们给线程设置了指定的优先级,
    但是该线程并不是按照优先级高的线程执行,那是为什么呢?

因为线程的优先级的大小仅仅表示这个线程被CPU执行的概率增大了.
但是我们都知道多线程具有随机性,
所以有的时候一两次的运行说明不了问题

  1. 为什么主线程能够比所有线程都先运行

主线程就在CPU
所以,若是我们不对其他线程调用join()方法
即使主线程优先级最低,也会 大几率 优先执行完主线程


线程休眠:

  • public static void sleep(long millis) :
    线程休眠

展示

package edu.youzg.about_thread.core;

import java.util.concurrent.ExecutionException;

public class Test {

    public static void main(String[] args) throws InterruptedException, ExecutionException {
        long startTime = System.currentTimeMillis();
        Thread.sleep(1000);
        long endTime = System.currentTimeMillis();
        System.out.println("休眠了" + ((endTime - startTime) / 1000) + "秒");
        System.out.println("之后的代码");
    }

}

运行结果
在这里插入图片描述


加入线程:

  • public final void join():
    等待该线程执行完毕了以后,其他线程才能再次执行
    (注:该方法需 待线程启动之后,才能调用方法)

展示

package edu.youzg.about_thread.core;

public class Test {

    public static void main(String[] args) throws InterruptedException {
        MyThread th1 = new MyThread();
        MyThread th2 = new MyThread();
        MyThread th3 = new MyThread();
        //也是说现在,三个线程是并发执行
        //join()在线程开启之后,去调用
        th1.setName("大傻");
        th1.start();
        th1.join();
        th2.setName("二傻");
        th2.start();
        th2.join();
        th3.setName("三傻");
        th3.start();
        th3.join();
        //串行:多个线程按顺序执行
    }

}

运行结果

在这里插入图片描述


礼让线程:

  • public static void yield():
    暂停当前正在执行的线程对象,并执行其他线程

展示

package edu.youzg.about_thread.core;

public class Test extends Thread {

    public Test() {
        super();
    }

    @Override
    public void run() {
        for (int i = 0; i <= 4; i++) {
            System.out.println("" + this.getName() + "-----" + i);
            // 当i为2时,该线程就会把CPU时间让掉,让其他或者自己的线程执行(也就是谁先抢到谁执行)
            if (i == 2) {
                this.yield();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Test th1 = new Test();
        Test th2 = new Test();
        th1.setName("孔融");
        th2.setName("孔融他弟");
        th1.start();
        th2.start();
    }

}

运行结果
在这里插入图片描述
上面的例子看起来貌似并没有礼让,反而有种更加争取CPU的样子了,这是为什么呢?

答曰:
这个礼让是要暂停当前正在执行的线程,并不会释放锁
这个暂停的时间是相当短的,
如果在这个线程暂停完毕以后,其他的线程还没有抢占到CPU的执行权,
那么这个时候这个线程应该再次和其他线程抢占CPU的执行权


中断线程

  • public final void stop():
    停止线程的运行
  • public void interrupt():
    当线程调用wait(),sleep(long time)方法的时候处于阻塞状态,
    可以通过这个方法清除阻塞(只能是wait()、sleep()造成的阻塞)

展示

package edu.youzg.about_thread.core;

public class Test2 extends Thread {

    public static void main(String[] args) throws InterruptedException {
        MyThread th1 = new MyThread();
        th1.setName("测试线程");
        th1.start();
        Thread.sleep(1000);
        //th1.stop(); //强行停止线程
        th1.interrupt();//打断线程的一个阻塞状态
    }

}

运行结果
在这里插入图片描述


守护线程:

  • public final void setDaemon(boolean on):
    将该线程标记为 守护线程用户线程 (被守护的线程)
    当正在运行的线程都是守护线程时,Java 虚拟机退出

展示

package edu.youzg.about_thread.core;

public class Test{

    public static void main(String[] args) {
        //主线程也称之为用户线程
        Thread.currentThread().setName("刘备");
        MyThread th1 = new MyThread();
        th1.setName("张飞");
        MyThread th2 = new MyThread();
        th2.setName("关羽");
        //在线程开启之前,可以设为守护线程
        th1.setDaemon(true);
        th2.setDaemon(true);
        th1.start();
        th2.start();
        System.out.println("刘备亡了");
    }

}

运行结果

在这里插入图片描述


那么,现在,本人就来讲解下 有关这个 用户线程 和 守护线程 的知识点吧:

用户线程 和 守护线程:

首先,本人来讲解下这两个知识点的异同吧:

相同点
用户线程和守护线程都是线程
不同点
Java虚拟机所有用户线程dead后,程序就会结束
不管是否还有守护线程还在运行
守护线程还在运行,则会马上结束
很好理解,守护线程是用来辅助用户线程的,
如公司的保安和员工,各司其职,当员工都离开后,保安自然下班了。

现在,本人来讲解下这两种线程的适用场景

适用场景

由两者的区别及dead时间点可知:
守护线程不适合用于输入输出或计算等操作
因为用户线程执行完毕,程序就dead了。
守护线程适用于辅助用户线程的场景
JVM的垃圾回收内存管理都是守护线程
还有就是在做数据库应用的时候,使用的数据库连接池,连接池本身也包含着很多后台线程,监听连接个数、超时时间、状态等。

那么,本人再来讲解下 如何创建守护线程

调用线程对象的方法setDaemon(true),设置线程为守护线程。
条件

  1. thread.setDaemon(true)必须在thread.start()之前设置
  2. Daemon线程中产生的新线程也是Daemon的。
  3. 不是所有的应用都可以分配给Daemon线程来进行服务
    (比如读写操作或者计算逻辑)
    因为Daemon Thread还没来得及进行操作,虚拟机可能已经退出了。

还有最后一点本人要强调一下:
可能会有同学学习过Linux,本人在这里要说明一点:

Java中的守护线程 与 Linux中的守护线程不是一个概念
Linux守护进程是后台服务进程没有控制台
Windows中,你可以运行javaw来达到释放控制台的目的
Unix下你加&在命令的最后就行了
所以守护进程并非一定需要的


那么,在最后,本人来通过一个例子来总结下我们这篇博文所学的内容:

题:
一部由大导演右转哥拍的电影《右转哥大战Bug星人》正在热映
但是,只有一家影院被授权可以播放这部电影。
于是,这家影院为了防止观众太多,导致卖票环节太过于耗时,就开放了三个售票窗口
请用代码实现下卖票过程。

要求:
创建3个线程,分别处理一个静态资源的分配。

那么,本人现在来展示下代码:

首先,本人来创建个售票线程

package edu.youzg.about_thread.core;

public class SellTicketsThread extends Thread {
    static int piao = 100;
    @Override
    public void run() {

        while (piao != 0){
            if(piao > 0){
                String name = this.getName();
                System.out.println(name+"正在出售"+(piao--)+"张票");
            }
        }
    }

}

现在,本人来给出一个测试类

       SellTicketsThread th1 = new SellTicketsThread();
       SellTicketsThread th2 = new SellTicketsThread();
       SellTicketsThread th3 = new SellTicketsThread();
       th1.setName("窗口1");
       th2.setName("窗口2");
       th3.setName("窗口3");
       th1.start();
       th2.start();
       th3.start();

现在,本人来展示下运行结果:
在这里插入图片描述

可以看到,出现了出售同一张票的错误。
那么,若是我们多次运行,还会出现这样的错误:
在这里插入图片描述

这是为什么呢?

本人现在来通过几张图来解释下:

起初,当售票线程都还没有跑起来的时候:
在这里插入图片描述
然后,假设th1争取到了CPU:
在这里插入图片描述
这时,假设th2争取到了CPU:
在这里插入图片描述
由于num--并不是原子操作,而是由对num的读、改、写 三步 组成的,
这时,假设th1完成了num--的操作,而th2还没完成:
在这里插入图片描述
这时,假设th3获得了CPU,读取到的num是99,然后将num--的操作也完成了:
在这里插入图片描述
这时,th2又争取到了CPU,然后,就会将num=99传回主存:
在这里插入图片描述
这就是上述两个问题的出现的原因。


那么,对于上述问题,我们有一个专门 的名称来形容 —— 线程安全问题

线程安全问题:

首先,本人来讲解下线程安全问题的出现原因

出现原因:

  • 多线程环境
  • 有共享数据
  • 有多条语句操作共享数据

那么,我们该如何处理这种问题呢?

把多个语句操作共享数据的代码给锁起来
任意时刻只能有一个线程执行即可


那么,线程安全问题该如何解决呢
请观看本人博文 —— 《详解 锁》

(本人 《详解 多线程》 博文 链接:https:////www.cnblogs.com/codderYouzg/p/12418935.html

posted @ 2020-03-05 10:51  在下右转,有何贵干  阅读(407)  评论(0编辑  收藏  举报