Day16 API05-1进程、线程、多线程、多线程安全隐患

1.进程

一、进程

1.1进程的定义:

进程指的是正在运行的程序,它代表了程序多占用的内存区域

1.2进程的特点:

1)独立性

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

2)动态性

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

3)并发性

多个进程可以在单个处理器CPU上并发执行,多个进程之间不会互相影响

1.3并发与并行的区别

HA(High Availability)高可用:指在高并发的情景中,尽可能的保证程序的可用性,减少系统不能提供服务的时间

二、线程

2.1线程的定义:

  1. 线程操作系统(OS)能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位.
  2. 一个进程可以开启多个线程,其中有一个主线程来调用本进程中的其他线程
  3. 我们看到的进程的切换,切换的也是不同进程的主线程
  4. 多线程扩展了多进程的概念,使同一个进程可以同时并发处理多个任务

2.2进程与线程的关系

  1. 一个操作系统中可以有单个进程也可以有多个进程,而一个进程中可以包含一个线程(单线程程序),也可以包含多个线程(多线程程序)--图1
  2. 每个线程在共享同一个进程中的内存的同时,又有自己独立的内存空间,所以想使用线程技术,得先有进程,进程的创建是OS操作系统来创建的,一般都是C或者C++完成--图2

2.3多线程的三个特性

1)随机性

  1. 线程的随机性指的是同一时刻,只有一个程序在执行;
  2. 宏观层面上,所有的进程/线程看似同时运行,但是微观层面上,同一时刻,一个CPU只能处理一件事.切换的速度甚至是纳秒级别的,非常快

2)CPU分时调度

CPU分配给各个线程的一个时间段,称作它的时间片

如果就绪队列中的线程被OS选中,会被执行,当时间片用完会被挂起,或者阻塞/结束

注意:我们不可以控制OS,OS有自己的调度规则【FCFS SJS】

  1. FCFS(First Come First Service 先来先服务算法)
  2. SJS(Short Job Service短服务算法)

3)线程的状态

分为三态和五态模型:

  1. 创建状态:线程的创建比较复杂,需要先申请PCB,然后为该线程运行分配必须的资源,并将该线程转为就绪状态插入到就绪队列中
  2. 终止状态:等待OS进行善后处理,最后将PCB清零,并将PCB返回给系统
  3. 就绪 → 执行:准备为就绪线程分配CPU即可变为执行状态"
  4. 执行 → 就绪:正在执行的线程由于时间片用完被剥夺CPU暂停执行,就变为就绪状态
  5. 执行 → 阻塞:由于发生人为或者某问题,使正在执行的线程受阻,无法执行,则由执行变为阻塞,(例如线程正在访问临界资源,而资源正在被其他线程访问)   
  6. 反之,如果获得了之前需要的资源,则由阻塞变为就绪状态,等待分配CPU再次执行

PCB(Process Control Block线程控制块:存储线程相关信息的模块):为了保证参与并发执行的每个线程都能独立运行,OS配置了特有的数据结构PCB来描述线程的基本情况和活动过程,进而控制和管理线程

2.4线程状态与代码对照

线程生命周期,主要有五种状态:

  1. 新建状态(New) : 当线程对象创建后就进入了新建状态.如:Thread t = new MyThread();
  2. 就绪状态(Runnable):当调用线程对象的start()方法,线程即为进入就绪状态.处于就绪(可运行)状态的线程,只是说明线程已经做好准备,随时等待CPU调度执行,并不是执行了t.start()此线程立即就会执行
  3. 运行状态(Running):当CPU调度了处于就绪状态的线程时,此线程才是真正的执行,即进入到运行状态。就绪状态是进入运行状态的唯一入口,也就是线程想要进入运行状态状态执行,先得处于就绪状态
  4. 阻塞状态(Blocked):处于运状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入就绪状态才有机会被CPU选中再次执行.根据阻塞状态产生的原因不同,阻塞状态又可以细分成三种:(1)等待阻塞:运行状态中的线程执行wait()方法,本线程进入到等待阻塞状态(2)同步阻塞:线程在获取synchronized同步锁失败(因为锁被其他线程占用),它会进入同步阻塞状态(3)其他阻塞:调用线程的sleep()或者join()或发出了I/O请求时,线程会进入到阻塞状态.当sleep()状态超时.join()等待线程终止或者超时或者I/O处理完毕时线程重新转入就绪状态
  5. 死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期

面试题: sleep 和wait 的区别

在这里插入图片描述

2.多线程代码的两种创建方式

多线程Thread常用方法
构造方法
Thread() 分配新的Thread对象
Thread(String name) 分配含有名称新的Thread对象
Thread(Runnable target) 接口实现类分配新的Thread对象,需要先创建接口实现类对象,进行绑定
Thread(Runnable target,String name)接口实现类分配含有名称新的Thread对象
普通方法
static Thread currentThread( )返回对当前正在执行的线程对象的引用
long getId()返回该线程的标识
String getName()返回该线程的名称
void run()如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法
static void sleep(long millions)在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)
void start()使该线程开始执行:Java虚拟机调用该线程的run()

2.1继承 Thread类

步骤:

1)自定义线程类 extends Thread

2)重写run()方法

3)方法中自定义功能+getName()方法

4)创建对象,每new一个对象代表一个线程(new对象对应的就是线程的新建状态)

5)通过start()方法实现多线程的抢占效果

6)new一个自定义名字的线程,需要手动添加无参构造和String类型的含参构造方法

TIPS:run()放着的是业务,但是想以多线程方式启动的话,需要调用start()

练习:继承Thread类创建多线程对象

package cn.tedu.thread;
/*本类练习多线程Thread创建方式1*/
public class TestThread1 {
    public static void main(String[] args) {
        //5.创建多线程对象
        MyThread t = new MyThread();/*new对象对应的就是线程的新建状态*/
        MyThread t1 = new MyThread();/*new对象对应的就是线程的新建状态*/
        MyThread t2 = new MyThread();/*每new对象一次对应的就是线程的新建状态(就是一个新线程)*/
        MyThread t3 = new MyThread("划水的鸭子");/*new一个自定义名字的线程*/
//        t.run();
//        t1.run();
//        t2.run();//只是普通的调用方法

        t.start();
        t1.start();
        t2.start();//使用start()才会有多线程抢占效果
        t3.start();
    }
}
//1.自定义多线程类
/*方式一:extends Thread继承父类*/
class MyThread extends Thread{

    //6.1创建一个子类的无参构造,防止被覆盖
    public MyThread() {//无参构造默认存在,如果有含参构造,要手动添加无参构造
        super();//默认存在super()
    }

    //6.2创建一个子类的含参构造,传入线程,具体业务是父类构造方法
    public MyThread(String name) {
        super(name);
    }

    //2.手动打出run,自动添加一个重写的run()方法,写自己的业务
    @Override//注解:子类调用父类方法
    public void run() {
        //3.super表示父类的引用,但我们不用调用父类功能,不需要父类方法就注释掉,
        //super.run();
        //4.自定义功能
        //需求:循环输出10次当前正在执行的线程的名称
        for (int i = 1;i<=10;i++){
            //5.getName()获取当前正在执行的线程的名称
            System.out.println(i+"="+getName());
        }

    }
}

2.2Runnable接口实现类对象绑定创建多线程

注意:一、Runnable接口中没有getName()方法,那么如何获取线程名称呢?

  1. 方案:可以从之前的Thread下的方法入手
  2. currentThread(),可以被类名直接调用【静态】,获取当前正在执行的线程
  3. 之后再进一步获取这个线程的名称getName()

二、接口和线程建立关系--绑定

练习:Runnable接口实现类对象绑定创建多线程

package cn.tedu.map;
/*本类测试多线程创建方式2:多个线程可以干同一件事*/
public class TestThread2 {
    public static void main(String[] args) {
        //2.创建线程对象进行测试
        //2.1创建子接口实现类对象
        MyRunnable target = new MyRunnable();
        //2.2接口和线程建立关系--绑定
        Thread t1 = new Thread(target,"jack");//接口线程可以直接定义名字
        Thread t2 = new Thread(target,"rose");
        Thread t3 = new Thread(target,"泰坦尼克");
        t1.start();
        t2.start();
        t3.start();
    }
}
//1自定义多线程类
/*方式二:实现Runnable接口完成*/
class MyRunnable implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i <=10; i++) {
            /*注意问题:Runnable接口中没有getName()方法,那么如何获取线程名称呢?
            * 方案:可以从之前的Thread下的方法入手
            * currentThread(),可以被类名直接调用【静态】,获取当前正在执行的线程
            * 之后再进一步获取这个线程的名称getName()*/
            System.out.println(i+Thread.currentThread().getName());
        }
    }
}

2.3多线程编程的两种实现方式区别:

1.继承的方式:extends Thread

优点:写法简单

缺点:后续无法再继承其他类【java中的类是的单继承

2.实现的方式:implements Runnable

优点:多实现,多继承,更加灵活且解耦

缺点: 编程稍微复杂,如想访问当前线程,则需使用Thread.currentThread()方法

2.4多线程创建过程需要注意的点:

1)new--对应的就是线程的新建状态

2)run()--这是一个必须重写/实现的方法,本方法里放着的是业务

3)start()--调用此方法,才会使线程以多线程的方式启动【会把本线程加入到就绪队】,start()在执行时,底层会自动调用run()完成业务,抢占效果完成

4)问题:调用start()时,线程会立即执行吗?

答:不会立即执行,只是加入到了就绪队列,等等OS选中

3.多线程案例

练习1:使用继承Thread类创建方式编程售票案例

package cn.tedu.tickets;
/*需求:设计多线程编程模型,4个售票窗口共计售卖100张票
* 本类通过继承Thread类的方式完成多线程编程案例*/
public class TestThread {
    public static void main(String[] args) {
        //5.创建线程对象
        TicketsThread t1 = new TicketsThread();
        TicketsThread t2 = new TicketsThread();
        TicketsThread t3 = new TicketsThread();
        TicketsThread t4= new TicketsThread();

        //6.以多线程的方式启动--将线程加入到就绪队列
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}
//1.自定义线程售票业务类
class TicketsThread extends Thread{
    //3.定义一个变量用来保存票数
    //7.将票数设置为静态,被所有对象共享,否则有几个线程就卖几百张票(重卖现象)
    //int tickets = 100;//
   static int tickets = 100;/*静态资源属于类资源,被全局共享,只有一份(只第一时间加载一次)*///超卖现象
    //2.完成业务,重写父类Thread的run()
    @Override
    public void run() {

        //4.在死循环结构中完成售票业务--售完为止
        while(true){
            try {
                //8.休眠--让程序休眠10ms--人为提升问题发生的概率
                /*我们可以人为让程序休眠10毫秒,为了增加线程状态切换的概率
                 * 但会产生问题1:重卖现象,同一张票卖给了两个人
                 * 产生问题2:超卖现象,票数出现了0 -1 -2,超出了实际售卖的票数*/
                Thread.sleep(10);//InterruptedException中断的异常,需要try-catch捕获
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //打印下当前正在售票的线程名称,并且票数-1
            System.out.println(getName()+"="+tickets--);//获取当前执行的线程名称+票数每次少一张
            //一定要设置循环出口!!!判断,没票了就停止售票
            if (tickets<=0) break;
        }
    }
}

练习2:使用实现Runnable类创建多线程方式编程售票案例

package cn.tedu.tickets;
/*需求:设计多线程编程模型,4个售票共计售卖100张票
* 本类通过实现Runnable接口的方式完成多线程编程案例*/
public class TestRunnable {
    public static void main(String[] args) {
        //5.创建实现类(目标类)对象
        TicketsRunnable target = new TicketsRunnable();//因为接口只创建一个对象,所以只调用一次100张票
        //6.以多线程的方式启动线程
        /*若想以多线程的方式启动实现类业务,需要借助Thread
        *触发的是Thread的含参构造,将目标业务对象target与Thread作绑定 */
        Thread t1 = new Thread(target);//100张票给到4个售票员
        Thread t2 = new Thread(target);
        Thread t3 = new Thread(target);
        Thread t4 = new Thread(target);

        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}
//1.自定义多线程实现类
class TicketsRunnable implements Runnable{

    //3.创建变量保存票数
    int tickets = 100;
    //2.添加未实现的方法run(),run()里是要实现的业务
    @Override
    public void run() {
        //4.创建循环结构,实现卖票的功能
        while(true){
            try {
                //7.休眠--让程序休眠10ms--人为提升问题发生的概率
                /*我们可以人为让程序休眠10毫秒,为了增加线程状态切换的概率
                 * 但会产生问题1:重卖现象,同一张票卖给了两个人
                 * 产生问题2:超卖现象,票数出现了0 -1 -2,超出了实际售卖的票数*/
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //4.1想获取当前正在售票的线程名,并且票数-1
            System.out.println(Thread.currentThread().getName()+"="+tickets--);
            //4.2注意死循环需要设置出口!!!当没票时,就停止售票
            if (tickets<=0) break;//if只有一条语句,大括号可以省略
        }
    }
}

4.多线程安全隐患

4.1多线程安全隐患怎么产生的?

答:线程的随机性+访问延迟造成

4.2以后如何判断程序是否有线程安全问题?

答:在多线程程序中+有共享数据+多条语句操作共享数据

4.3总结:

1)单线程程序不会出现多线程抢占资源的情况

2)如果没有共享数据,互不干涉,也不会出现数据的安全问题

3)多条语句操作了共享数据,而多条语句的执行是需要时间的,存在延迟,所以这个时间差导致了数据的安全问题

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值