线程的相关概念

1、多线程

1.1 什么是进程?什么是线程?

进程是一个应用程序,一个进程是一个应用软件;
线程是一个进程中的执行单元,执行场景;
一个进程可以启动多个线程;

1.2 Java 程序中来讲,当在 dos 命令窗口中输入:

java HelloWorld 回车之后,会先启动 JVM ,JVM 是一个进程;
JVM 在启动一个主线程调用 main() 方法,同时启动一个线程进行负责看护,回收垃圾;
最起码,现在的 java 程序中有两个线程是并发的,一个是垃圾回收线程,一个是执行 main() 主方法的线程;

1.3 一个进程就是一个独立的软件,一个线程就是进程里面的执行单元

进程和进程之间的内容是独立不共享的(比如两个公司之间的相关东西是不会共享的);
同一个进程下面的线程之间有的内存是共享的;

在 Java 语言中:
线程A 和线程B 之间的堆内存和方法区之间是内存共享的,但是栈内存之间是独立的,一个线程一个栈;
假设同时启动 10 个线程,是同时会产生 10 个栈的,每个栈之间是不会干扰的,各自执行各自的东西,这就是多线程并发;

火车站可以看作是一个进程,每个售票窗可以看作是一个线程,可以同时在多个窗口中购票,是不需要相互等待的,所以多线程并发(同时执行,是可以提高效率的);

Java 中之所以存在多线程机制,目的就是为了提高程序的处理效率;

1.4 使用了多线程的机制之后,main() 方法的结束,程序也是有可能不会结束?

对的,因为 main() 方法结束之时主线程的结束,主栈空了,其他的线程(或者栈)还在继续的执行;

main() 方法的结束只是代表了主线程的结束,其他的方法仍然在执行;
栈空间是独立的,一个空间中有一个栈;
两个线程之间的栈内存之间独立的,不进行共享,但是堆内存和方法去两个线程之间是可以共享的;

1.5 多线程的并发,分析:对于单核的CPU来说,可以做到真正的多线程并发吗?

多核 CPU 可以做到真正的线程并发;
4核 CPU 在一个时间点上,可以真正的存在 4 个进程并发;

什么是多线程并发?
t1 线程执行 t1 的;
t2 线程执行 t2 的;
t1 线程是不会影响到 t2 的;
t2 线程是不会影响到 t1 的;
上面的描述为真正的多线程的并发;

单核的CPU 表示只有一个大脑,不能做到真正的多线程并发,可以做到多线程并发的感觉;
单核的 CPU 在某个单独的时间点上面,只能处理一个事情,但是由于 CPU 的处理速度比较快,在线程之间的切换速度是比较快的, 感觉就是在多线程的运作(同时并发的);

1.6 java 中的实现线程的方式,存在两种,哪儿两种方式?

Java 支持多线程的机制,Java的多线层呢已经实现,需要实现即可;

1、编写一个类,直接继承 java.lang.Thread,重写 run 方法

/**
 * 实现线程的第一个方式:
 *      编写一个类直接继承 java.lang.Thread 方法,进行 run 方法的重写即可
 *
 * 如何创建线程对象?怎么启动线程?
 *      创建:main 方法,这里的代码是属于主线程的,在主栈中运行
 *          new 即可
 *      启动:新建一个分支线程对象
 *          调用线程中的 start 即可
 *
 * 方法体里面的代码是从上到下面按照顺序执行的,是不会变的;
 */
public class ThreadTest02 {
    public static void main(String[] args) {
        // 在这里是main 方法,代码属于主线程,在主栈中运行
        MyThread myThread = new MyThread();

        // 线程的启动
        // start 方法中的作用:
        // 动一个分支线程,在 JVM 中开辟一个新的栈空间,启这段代码任务完成之后瞬间就结束了,
        // 这段代码的任务只是为了开启一个新的栈空间,只要新的栈空间开辟出来 start() 方法就结束了,线程就启动成功了
        // 启动成功的线程,会自动调用 run 方法,并且在 run 方法在分支栈的底部(压栈)最先进去的,所以在最底部;
        // run 方法在分支栈的底部,main 方法在主栈的底部,run 和 main 是平级别的;

//        myThread.run(); // 就是普通的单线程 不会启动分支栈,不会启动分支线程


        myThread.start(); // 瞬间结束这一句,开辟了栈空间就结束
        // myThread.run() 如果只是调用了这一个方法,那么就是只是调用了方法,没有使用新的线程,不会分配新的分支栈
        // 分支线程启动开始,主线程了分支线程都在执行了

        // 这里的代码运行在主线程中
        for (int i = 0; i < 1000; i++) {
            System.out.println("主线程-->" + i);
        }
    }
}

class MyThread extends Thread {
    // 对于继承方法的重写,实现多线程
    @Override
    public void run() {
        // 编写程序,这段程序运行在分支栈中
        for (int i = 0; i < 1000; i++) {
            System.out.println("分支线程 -->" + i);
        }
    }
}

2、编写一个类,实现 java.lang.Runnable 接口,实现 run 方法(主要是实现了接口,这个是重要的)

/**
 * 实现线程的第二种方式:编写一个类实现下面的接口;使用了接口的方式实现了线程的并发;
 *      java.lang.Runnable
 */
public class ThreadTest03 {
    public static void main(String[] args) {
//        // 创建一个可以运行的对象
//        MyRunnable r = new MyRunnable();
//
//        // 将可以运行的对象进行封装
//        Thread t = new Thread(r);

        // 上面的代码进行合并
        Thread t = new Thread(new MyThread());

        // 启动线程
        t.start();

        for (int i = 0; i < 1000; i++) {
            System.out.println("主线程" + i);
        }
    }
}

// 下面不是一个线程,是一个可以运行的类
class MyRunnable implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            System.out.println("分支线程" + i);
        }
    }
}

1.7 关于两个线程之间的运行有先有后,有多有少

/**
 * 下面的程序的输出特点:
 *      有先有后,有多有少?如何解释?
 *          1、控制台只有一个
 *          2、线程抢到了执行权
 *              1、new 出来的线程对象(处于新建状态)
 *              2、调用 start 方法,进去就绪状态
 *                  就绪状态也叫做可运行状态,表示当前线程具有抢夺CPU 时间片的权利(CPU 时间片就是执行权)。当一个线程抢到了CPU
 *                  时间片之后,开始执行 run 方法,run 方法开始执行的时候,标志着线程进入到了运行状态
 *              3、运行状态
 *                  run 方法开始执行,标志者程序进去运行状态,但是在之前的CPU 时间片被使用结束之后,重新回去就绪状态准备抢CPU 时间片
 *              4、在运行状态和就绪状态时间之间,是需要使用 JVM 进行资源的分配的
 *              5、最后程序中的线程周期将会结束
 *              6、当一个线程遇到了阻塞状态的时候,比如接收用户的键盘输入,或者使用sleep() 方法等;
 *                 此时的线程会进入阻塞状态,阻塞状态的线程会放弃之前占有的CPU 时间片;
 *                 当阻塞状态结束后,当前的程序会进去就绪状态,在这里分配到到了CPU 时间片之后,才会进行运行状态;  
 */
 * 关于线程对象的生命周期?
 *      新建状态
 *      就绪状态
 *      运行状态
 *      阻塞状态
 *      死忙状态

1.8 线程生命周期图(重要!!!)

在这里插入图片描述

1.9 线程的调度模型

1.9.1 常用的线程调度模型:

1.9.1.1抢占调度模型

线程的优先级比较高,抢到的CPU 时间片的概率高一些(Java)

java 提供什么与线程的调度有关系?
实例方法

设置优先级 void setPriority(int newPriority) 设置线程的优先级别;
获取优先级 int getPriority() 获取线程的优先级别;

最低优先级 1
最高是10
默认是 5

void join()
合并线程
当前线程进入阻塞状态,当加进来的线程执行完毕之后,原来的线程进入执行状态;

静态方法

static void yield() 让位方法;
暂停了当前正在执行的线程对象,并切执行其他的线程;
yield() 方法不是阻塞方法,让当前的线程让位,让给其他线程进行使用;
yield() 方法,让当前的程序,从运行状态,回到就绪状态,回来之后,还有可能继续抢到CPU 时间片;

1.9.1.2均分式调度模型

平局分配的CPU 时间片,每个线程占有的CPU 时间片是一样的,平均分配,一切平等

1.10 多线程并发环境下面的数据安全问题

1.10.1 为什么是重点?

因为在开发过程当中,项目运行在服务器中,服务器已经把,线程的定义,线程对象的创建,线程的启动都已经实现结束了,都不需要编写;

重要的是:编写的程序需要放置在多线程的运行环境中,更加需要关注的是:数据在多线程的并发环境下面是否是安全的(*****重点)

1.10.2 什么时候数据在多线程并发的环境中会存在安全问题?

三个条件:
1、多线程并发
2、有共享数据
3、共享数据有修改的行为

满足上面的条件之后,就会存在线程的安全问题;

1.10.3 如何解决线程安全的问题?

当多线程并发的环境下面,有共享数据,并且这个数据会被修改,此时就会存在线程安全问题;
解决的办法是排队执行线程,(不能并发),使用排队执行解决线程的安全问题,这种机制叫做线程同步机制;

线程同步,就是线程需要进行同步,不能进行线程的并发;
线程排队了,会牺牲效率,只有数据是安全的,才能谈一谈线程的效率,数据不安全,效率无从谈起;

1.10.4 同步

专业术语:
同步编程模型:线程t1,t2在线程 t1 执行的时候,t2 线程想要执行,必须等待线程 t1 执行结束之后,才能继续执行;或者说在 t2 线程执行的时候,必须等待 t1 线程的执行结束;两个线程之间发生了等待关系;(排队)

异步编程模型:线程 t1,t2 各自执行各自的,谁也不需要等谁,就是多线程并发,效率是比较高的;(并发)

1.11 怎么解决线程安全的问题呢?

一上来就是选择线程同步吗?
不是,synchronized 会导致程序使用起来比较缓慢,不建议一上来就使用这种方法,用户网站的吞吐量会变低,不得已的时候,使用线程同步机制;

第一种方案:
尽量使用局部变量,从而代替掉实例变量以及静态变量;

第二种方案:
如果必须是实例变量,可以考虑创建多个对象,这样子的实例变量就不会共享了(一个线程使用一个对象,一百个线程对应使用的是一百个对象),对象不共享了,就不会出现数据安全的问题了;

第三种方案:
不能使用局部变量,不能创建多个对象的时候:
只能选择 synchronized 线程同步的机制;

1.12 线程这一块的其他内容

(1)守护线程

后台线程,垃圾回收线程类似;
Java 线程的分类:
1、用户线程
2、守护线程(后台线程)(垃圾回收线程)

守护线程的特点:
一般守护线程是一个死循环,所有的用户线程只要结束,守护线程自动结束;

注意:主线程 main 方法是一个用户线程

守护线程用在什么地方?
作用就是守护,用户线程结束了之后,就没有守护的必要性了;
每天 0点的时候,进行数据的备份,可以把定时器设置为守护线程;
每次到了0点的时候备份一次;

(2)定时器

定时器的作用:
间隔特定的时间,执行特定的程序;
每周进行银行账户的总账操作,每天进行数据的备份操作,此时就需要使用定时来实现相关的代码;

实际的开发中,隔多久时间一个特定的需求,是一种常见的需求方式;
Java中可以使用多种多样的方式来实现;

1、使用 sleep 睡醒了之后,即使的起来即可,是一种比较原始的计数器,需要自己写;

2、JDK 内置了一种定时器,叫做 java.lang.Timer ,拿过来直接使用即可,在开发中使用的比较少,目前使用的额比较多的定时器是:Spring 框架里面的 SpringTask 进行定时,Spring 进行简单的配置就能实现;

(3)实现线程的第三种方式:

FutureTask 方式,实现 Callanle 接口,(JDK8 新特性)

(4)Objec 类中的 wait() 方法和notify() 方法的相关使用 (生产者和消费者模式)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值