3.9 Java之多线程

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/lwz45698752/article/details/82621157


程序,进程与线程

  • 程序运行到内存中(去加载),程序转化为进程
  • 静态的为程序,动态为进程
  • 进程细分,不同执行路径为线程
  • OS多进程
  • 垃圾回收为守护线程(不在程序中显示显现,后台运行,一直存在)

创建线程

法一

这里写图片描述
这里写图片描述

  • JVM允许应用程序在执行过程中并发地创建多个线程
  • 以往程序只有主线程(主方法里执行的线程,仅看前台,不考虑守护线程)
  • 单核CPU同一时间只能执行一个命令(执行一个线程),高主频切换任务,似并行执行

  • run方法与start方法不等效,run方法还是主线程中的对象调方法,没有启动线程
    而start方法既启动线程,又调用run方法
  • 线程start后不能再start,原因见下图

这里写图片描述

  • 线程的状态(不为0,表示被启动)

法二

实现接口,重写run方法,有必要的加上类属性
这里写图片描述


这里写图片描述
这里写图片描述

  • 传入实现接口的类,多态的应用
  • 创建一个实现接口类对象,创建多个Thread类对象(共享一个实现了接口的类对象
  • 启动线程的说明:
    • target是Thread类的属性,Thread类的run方法实质执行target的run方法
    • target在构造器中被实例化,即执行传入形参的run方法

两种方法比较

这里写图片描述

  • 实现更好,避免了只能单继承,因为接口可多实现
  • 此外,对于操作同一份资源,实现方式更好
  • 两者联系:runnable接口是和线程相关的接口,实现方式直面接口,而继承方式间接面对接口(通过Thread类)

线程常用方法

这里写图片描述


这里写图片描述

  • target是个runnable(接口),Thread的run方法无实质执行内容

这里写图片描述

  • yield方法显式地强制释放当前CPU的执行权,重新争抢,有可能继续原本线程或其他线程抢到CPU

这里写图片描述
这里写图片描述

  • join方法使得只有子线程执行完,主线程才接着执行,要异常处理
  • alive判断线程是否存活

这里写图片描述

  • 处理异常只能用try/catch方式,不能用throws,因为Thread声明时没抛出异常,作为其子类,根据重写规则,也不能throws

线程的调度

这里写图片描述

  • 分为时间片(先来后到),抢占式(优先级)两种
  • 优先级高只能确保抢到CPU概率大,不等于先后
  • 抢占式设置优先级,优先级从1到10,默认为5

多线程练习

这里写图片描述

  • 不同任务即对应不同线程类子类的run方法

这里写图片描述

  • 关注匿名类的简写

匿名类对象(继承Thread类)

这里写图片描述

  • 创建继承于Thread类的匿名类对象,括号内重写run方法

窗口售票问题

继承方式

多窗口–》多线程–》多对象


这里写图片描述

  • 涉及到对象的成员变量(属性)
  • static声明变量,称为类变量或类属性,使得所有对象共用该属性
  • 类变量缺点在于生命周期长,回收类时才会从内存中回收类变量
  • 通过实现方式创建线程,不需要声明static变量

这里写图片描述

  • CPU分配资源,执行run方法

这里写图片描述

  • 顺序非依次输出的解释:先抢到值,后输出

实现接口方式

这里写图片描述
这里写图片描述

  • 创建一个实现接口类的对象(w),创建多个Thread类对象(共享一个实现了接口的类对象w
  • 只需创建一个票数对应的对象(w),多个线程共享票数
  • 实现接口的方式适合存在共享数据的问题

多线程优点

  • 省去切换时间使得单线程效率更高
  • 多线程开启,CPU切换运行
  • JVM的后台垃圾回收线程为守护线程
  • 当不存在前台用户线程,守护线程也退出,不执行相应任务,则JVM退出

多线程的生命周期

这里写图片描述

  • start表明可以执行,等待分配到资源后,才会真正执行
  • 阻塞完毕后,先到达就绪状态,去争抢CPU资源,抢占后才能执行
  • suspend挂起当前线程

线程的同步机制(安全机制)

这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述

线程安全问题实例

这里写图片描述

  • 出现-1票,因为ticket为1,大于0,进入了if语句,然后sleep方法使得阻塞,没有立刻打印,其他线程趁机执行,也同上过程,最后打印时出问题了(大家都通过了if语句,但ticket不够减了)
  • 总之,未连贯完成对共享数据操作的锅

同步代码块

实现接口方式改写

这里写图片描述

  • 抽象的方法没有抛出异常,所以重写的方法也不能抛出异常,所以用try/catch处理
  • 同步方法有同步监视器,理解为两种状态(上锁,不上锁)
  • 同步监视器选取的对象可任意(但要求不同线程对应同一对象)
  • 同步方法把操作共享数据的方法封装,多个线程要想执行该代码块内容,需要取得同步监视器(锁),线程进行争抢锁,一线程抢到后,待到封装的语句块全部执行完毕,锁恢复争抢状态
  • 三个线程共用一个锁
  • 同步代码块的内容不要多,也不要少,比如不能把while循环包括在内
  • 使用this:当前对象,即W(启动线程,执行target的run方法,而target为实现runnable的类对象),W只有一个,所以可以使用

继承方式改写

这里写图片描述

  • 不能用this,因为不代表同一对象,同理Object对象也要static

子线程执行run方法,功能封装到run方法中,子线程调用对应run方法执行

同步方法

  • 同步方法同一时间只能有一个线程执行
  • 同步方法是当前对象调用,所以其锁为当前对象,不同于同步代码块,同步方法未显式地指明锁

实现接口方式改写

这里写图片描述

  • 将操作共享数据的代码封装为show方法,修饰为同步方法,从而保证线程安全

继承方式改写(同步方法不可用)

这里写图片描述

  • 使用同步方法,不能保证线程安全,由于同步方法的锁this指当前对象,而当前对象具有多个,没有共用同一把锁
  • 只能用同步代码块方式

互斥锁

懒汉式线程安全问题

这里写图片描述
这里写图片描述

  • 提供公用静态方法访问,返回实例
  • 使用==判断是否为同一对象
  • 若不加入线程安全处理,则线程A创建对象,sleep,最后返回首地址,a线程B趁着sleep时,再次创建对象,之前的对象不存在了,因为instance重新赋值,创建了新对象了,应该只创建一次,同样返回首地址值,比较,此时不相等
  • instance作为共享数据
  • 静态方法里无this,改用XXX.class,返回Class类的对象(反射机制)

  • 因为同步代码块内相当于执行单线程,降低效率
  • 改进方法:添加if语句,当有人进门了,直接宣布有人,不用等都处理完了,再说明有人

锁的机制

释放锁操作

  • 当前线程的同步方法、同步代码块执行结束
  • 当前线程在同步代码块、同步方法中遇到break、return终止了该代码块、该方法的继续执行。
  • 当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束
  • 当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁。

不会释放锁操作

  • 线程执行同步代码块或同步方法时,程序调用Thread.sleep()、Thread.yield()方法暂停当前线程的执行
  • 线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁(同步监视器)。
    • 应尽量避免使用suspend()和resume()来控制线程

  • 重点:wait方法会释放锁,sleep方法不会释放锁

线程同步练习

实现接口方式

这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述


继承方式

这里写图片描述
这里写图片描述
这里写图片描述

  • 多用户:多线程,而用户具有account属性,多个用户共用一个账户,进一步地指账户的余额(balance)
  • sleep是为了让线程安全问题更明显,可省略
  • 共享数据为account,进一步地为balance(抓住本质,操作共享数据的代码块为deposit方法)
  • 保证账户公用,指向一个堆空间实体
  • this在该deposit方法中是account对象,而account公用,所以可以用同步方法(默认this),可知继承方式也可考虑用同步方法(this使用得当)

猜你喜欢

转载自blog.csdn.net/lwz45698752/article/details/82621157
3.9